Merge branch 'verilator:master' into fix/force-unpacked-bitselect

This commit is contained in:
Nikolai Kumar 2026-06-15 19:02:58 -05:00 committed by GitHub
commit a3d517c4bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
207 changed files with 6414 additions and 2416 deletions

View File

@ -28,6 +28,38 @@ concurrency:
jobs:
build-2604-gcc:
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
uses: ./.github/workflows/reusable-build.yml
with:
sha: ${{ github.sha }}
os: ${{ matrix.os }}
os-name: linux
cc: ${{ matrix.cc }}
dev-asan: ${{ matrix.asan }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
- {os: ubuntu-26.04, cc: gcc, asan: 0}
build-2604-clang:
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
uses: ./.github/workflows/reusable-build.yml
with:
sha: ${{ github.sha }}
os: ${{ matrix.os }}
os-name: linux
cc: ${{ matrix.cc }}
dev-asan: ${{ matrix.asan }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
- {os: ubuntu-26.04, cc: clang, asan: 1}
build-2404-gcc:
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
uses: ./.github/workflows/reusable-build.yml
@ -58,7 +90,7 @@ jobs:
fail-fast: false
matrix:
include:
- {os: ubuntu-24.04, cc: clang, asan: 1}
- {os: ubuntu-24.04, cc: clang, asan: 0}
build-2204-gcc:
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
@ -76,22 +108,6 @@ jobs:
include:
- {os: ubuntu-22.04, cc: gcc, asan: 0}
build-2204-clang:
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
uses: ./.github/workflows/reusable-build.yml
with:
sha: ${{ github.sha }}
os: ${{ matrix.os }}
os-name: linux
cc: ${{ matrix.cc }}
dev-asan: ${{ matrix.asan }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
- {os: ubuntu-22.04, cc: clang, asan: 0}
build-osx-gcc:
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
uses: ./.github/workflows/reusable-build.yml
@ -160,6 +176,54 @@ jobs:
path: ${{ github.workspace }}/repo/verilator.zip
name: verilator-win.zip
test-2604-gcc:
name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }}
needs: build-2604-gcc
uses: ./.github/workflows/reusable-test.yml
with:
archive: ${{ needs.build-2604-gcc.outputs.archive }}
os: ${{ matrix.os }}
cc: ${{ matrix.cc }}
reloc: ${{ matrix.reloc }}
suite: ${{ matrix.suite }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
# Ubuntu 26.04 gcc
- {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-0}
- {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-1}
- {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-2}
- {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-3}
- {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-0}
- {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-1}
- {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-2}
test-2604-clang:
name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }}
needs: build-2604-clang
uses: ./.github/workflows/reusable-test.yml
with:
archive: ${{ needs.build-2604-clang.outputs.archive }}
os: ${{ matrix.os }}
cc: ${{ matrix.cc }}
reloc: ${{ matrix.reloc }}
suite: ${{ matrix.suite }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
# Ubuntu 26.04 clang
- {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-0}
- {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-1}
- {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-2}
- {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-3}
- {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-0}
- {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-1}
- {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-2}
test-2404-gcc:
name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }}
needs: build-2404-gcc
@ -232,30 +296,6 @@ jobs:
- {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-1}
- {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-2}
test-2204-clang:
name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }}
needs: build-2204-clang
uses: ./.github/workflows/reusable-test.yml
with:
archive: ${{ needs.build-2204-clang.outputs.archive }}
os: ${{ matrix.os }}
cc: ${{ matrix.cc }}
reloc: ${{ matrix.reloc }}
suite: ${{ matrix.suite }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
# Ubuntu 22.04 clang, also test relocation
- {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-0}
- {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-1}
- {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-2}
- {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-3}
- {os: ubuntu-22.04, cc: clang, reloc: 1, suite: vltmt-0}
- {os: ubuntu-22.04, cc: clang, reloc: 1, suite: vltmt-1}
- {os: ubuntu-22.04, cc: clang, reloc: 1, suite: vltmt-2}
lint-py:
name: Lint Python
uses: ./.github/workflows/reusable-lint-py.yml
@ -264,17 +304,19 @@ jobs:
name: Test suite passed
if: always()
needs:
- build-2604-gcc
- build-2604-clang
- build-2404-gcc
- build-2404-clang
- build-2204-gcc
- build-2204-clang
- build-osx-gcc
- build-osx-clang
- build-windows
- build-2404-gcc
- build-2404-clang
- test-2404-gcc
- test-2404-clang
- test-2204-gcc
- test-2204-clang
- lint-py
runs-on: ubuntu-24.04

View File

@ -90,7 +90,7 @@ jobs:
find obj_coverage -type f | paste -sd, | sed "s/^/files=/" >> "$GITHUB_OUTPUT"
- name: Upload to codecov.io
uses: codecov/codecov-action@v6
uses: codecov/codecov-action@v7
with:
disable_file_fixes: true
disable_search: true
@ -197,7 +197,7 @@ jobs:
id: generate-token
uses: actions/create-github-app-token@v3.2.0
with:
app-id: ${{ vars.VERILATOR_CI_ID }}
client-id: ${{ vars.VERILATOR_CI_ID }}
private-key: ${{ secrets.VERILATOR_CI_KEY }}
owner: verilator
repositories: verilator

View File

@ -76,7 +76,7 @@ jobs:
id: generate-token
uses: actions/create-github-app-token@v3.2.0
with:
app-id: ${{ vars.VERILATOR_CI_ID }}
client-id: ${{ vars.VERILATOR_CI_ID }}
private-key: ${{ secrets.VERILATOR_CI_KEY }}
permission-actions: write
permission-pull-requests: write

View File

@ -292,7 +292,7 @@ jobs:
id: generate-token
uses: actions/create-github-app-token@v3.2.0
with:
app-id: ${{ vars.VERILATOR_CI_ID }}
client-id: ${{ vars.VERILATOR_CI_ID }}
private-key: ${{ secrets.VERILATOR_CI_KEY }}
owner: verilator
repositories: verilator-rtlmeter-results
@ -394,7 +394,7 @@ jobs:
id: generate-token
uses: actions/create-github-app-token@v3.2.0
with:
app-id: ${{ vars.VERILATOR_CI_ID }}
client-id: ${{ vars.VERILATOR_CI_ID }}
private-key: ${{ secrets.VERILATOR_CI_KEY }}
owner: verilator
repositories: verilator

142
AGENTS.md Normal file
View File

@ -0,0 +1,142 @@
<!-- DESCRIPTION: Verilator: Repository-wide guidelines for AI coding agents
SPDX-FileCopyrightText: 2026-2026 Wilson Snyder
SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -->
# Verilator Guidelines for AI Coding Agents
These files are the general layer an agent loads first -- nearest file wins, so
you read this repository-root file plus the one for the directory you are
editing. They stay deliberately high-level: where to start, how the tree is laid
out, and the conventions reviewers otherwise enforce by hand. They are an index,
not the architecture reference -- for depth (how a pass works internally, the
algorithms, node lifetime) they point you to `docs/internals.rst`. When the
guidance here is not enough, that is where to look next.
This file has two parts. **Orientation** gets you productive in the codebase from
a cold start. **Before you open a PR** is the checklist of conventions reviewers
otherwise have to enforce by hand -- read it before submitting any change.
Then read the directory guide for the area you are editing:
- [src/AGENTS.md](src/AGENTS.md) -- compiler C++ sources: AST, visitors, passes, parser, style
- [include/AGENTS.md](include/AGENTS.md) -- runtime library (`verilated*`): C++14, MT-safety, fixed-width types
- [test_regress/AGENTS.md](test_regress/AGENTS.md) -- regression tests: harness, drivers, golden files
- [docs/AGENTS.md](docs/AGENTS.md) -- documentation (`*.rst`)
______________________________________________________________________
# Orientation
## What Verilator is
Verilator is a *compiler*, not an interpreter. It translates synthesizable (and
much behavioral) SystemVerilog into a cycle-accurate C++ model that you then
compile and run. Almost every decision is made at compile ("verilation") time;
the generated C++ just advances state each evaluation. Optimize for verilation-
time work over runtime work.
## The pipeline is the spine
A run is an ordered sequence of passes over one shared AST (abstract syntax
tree). In source order:
| Stage | What it does | Key files |
|---|---|---|
| Preprocess + parse | Lex and parse text into a raw AST -- builds nodes only, no semantic checks | `verilog.l`, `verilog.y` |
| Link / elaborate | Resolve names, scopes, parameters; instantiate the hierarchy | `V3LinkParse`, `V3LinkDot`, `V3Param` |
| Width / type | Assign and check data types and bit widths | `V3Width` |
| Transform / optimize / schedule | Constant fold, lower language features, schedule events | `V3Const`, `V3Randomize`, `V3Assert*`, `V3Sched`, `V3Timing`, `V3Dfg` |
| Emit | Lower the final AST to generated C++ | `V3EmitC*` |
| Runtime | Library the generated model links against | `include/verilated*` |
This table is the map; `docs/internals.rst` has the detail behind each stage.
## Where to make a change
Map the symptom to the pass that owns it, then start by reading that pass's
top-of-file comment.
| Symptom or feature area | Start in |
|---|---|
| Type/width error, "what type is this", implicit conversion | `V3Width` |
| Name/scope/parameter resolution ("Can't find...", hierarchy) | `V3LinkDot`, `V3Param` |
| `randomize` / `constraint` / `rand` / `randc` | `V3Randomize` |
| `assert` / `property` / `sequence` / `cover` | `V3Assert`, `V3AssertPre`, `V3AssertNfa` |
| `fork` / timing / `#delay` / NBA / event scheduling | `V3Sched`, `V3Timing`, `V3Fork` |
| Syntax wrongly accepted or rejected | `verilog.y`, `verilog.l` |
| Wrong generated C++ | `V3EmitC*` |
| Runtime model behavior | `include/verilated*` |
## Build and run a test
- Build in the source tree: `autoconf && ./configure && make -j8`. Configure with
`--enable-ccwarn` so a new compiler warning stops the build.
- Run one test from the repository root: `test_regress/t/t_<name>.py`.
- Run the full regression with `make test`. The complete suite requires
configuring with `--enable-longtests` (works on every OS, including macOS).
______________________________________________________________________
# Before you open a PR
## Scope and process
- [ ] Searched open PRs and issues -- duplicating in-flight work wastes review time.
- [ ] Fixed the general root cause, not just the reported case -- if it also
affects other modules/classes/interfaces, cover them or expect rejection.
- [ ] PR is single-purpose. Refactors, drive-by fixes found along the way, and new
features each go in separate PRs; land standalone cleanups first.
- [ ] Every bug fix has a test that fails *without* the fix; include the issue's
own reproducer when possible.
- [ ] New code aims for 100% line coverage; branch coverage far below line coverage
signals guards callers never violate -- justify or remove them.
- [ ] Ran `make format` (clang-format), `make cppcheck`, and `make lint-py`;
self-reviewed the diff for leftover debug code, stale comments, and
copy-paste errors.
- [ ] Ran the full regression on at least one OS before submitting. Partial runs
are fine during development, but the submitted PR is expected to pass every
test.
- [ ] Did not edit `docs/CONTRIBUTORS` (humans only) or `Changes` (maintainer
updates it near release).
## Pick the right diagnostic (and its required test)
The API you choose determines which test must accompany the change.
| API | Output | Meaning | Required test |
|---|---|---|---|
| `v3error("...")` | `%Error:` | User wrote invalid SystemVerilog | `t_*_bad*.v` + `.out` golden |
| `v3error("Unsupported: ...")` | `%Error-UNSUPPORTED:` | Legal SV that Verilator does not yet support | `t_*_unsup*.v` + `.out` golden |
| `v3warn(CODE, "...")` | `%Warning-CODE:` | Legal but suspicious code | warning test + `.out` golden |
| `v3fatalSrc("...")` | `%Error: Internal Error` | Should-never-happen internal assertion | none -- not user-triggerable |
- Every `v3error`/`v3warn` needs a test in `test_regress/t/` -- enforced by the
warn-coverage distribution test. `v3fatalSrc` is exempt.
- Reserve "Unsupported:" for not-yet-implemented features, never for user mistakes.
- When an error enforces a spec-defined restriction, cite the clause
(`IEEE 1800-2023 11.4.7`) so it is verifiable. Update `docs/guide/warnings.rst`
when adding or changing a warning.
- On error paths, clean up or replace invalid AST (e.g. `AstConst::BitFalse`) so
later passes do not crash after the error.
## Cross-cutting code rules
- [ ] No non-ASCII characters in C++ sources or headers: write `--` (two ASCII
hyphens) rather than a Unicode em-dash, and a plain `'` rather than a smart
quote. At write time, not when CI complains.
- [ ] Lists stay sorted: lexer/parser tokens, option declarations, enum values,
configure feature lists, documented option lists.
- [ ] `bin/` scripts are Python (distributed cross-platform); `nodist/` may use
bash and platform-specific code (developer-only, not packaged).
- [ ] Runtime code in `include/` targets C++14 (`--no-timing` builds must work);
C++20 only in timing code paths.
- [ ] In `include/` public headers, prefix public classes with `Verilated`/`Vl`
and document the API with `///` comments.
- [ ] A new code pattern is applied globally or not at all -- no one-off
convention in a single file.
## Commits
- Subject line is short and imperative and conventionally ends with the PR number:
`Support property case (#7721)`. A body is optional and common for non-trivial
changes.

33
Changes
View File

@ -15,7 +15,7 @@ Verilator 5.049 devel
**Important:**
* Support covergroups, coverpoints, and bins (#784) (#7117). [Matthew Ballance]
* Support covergroups, coverpoints, and bins (#784) (#7117) (#7728). [Matthew Ballance]
* Support new FST writer API (#6871) (#6992). [Yu-Sheng Lin]
Use of FST may requiring installing liblz4 and/or liblz4-dev packages, see docs/install.rst.
@ -27,9 +27,11 @@ Verilator 5.049 devel
* Add `--coverage-per-instance` (#7636). [Yogish Sekhar]
* Add NOTREDOP error on reduction and negation operators (#7417) (#7623) (#7624).
* Add hierarchy-aware reporting to `verilator_coverage` (#7657). [Yogish Sekhar]
* Deprecate isolate_assignments attribute (#7774) (#7144). [Geza Lore, Testorrent USA, Inc.]
* Improve `--coverage-fsm` (#7490) (#7529) (#7561) (#7573) (#7619). [Yogish Sekhar]
* Change `+verilator+seed` to default to 1, and 0 to randomly select (#7325) (#7516). [Miguel]
* Change JSON to include parameter constant mnemonics for FSM Coverage (#7531). [Yogish Sekhar]
* Support assert property 'default disable iff` (#4848) (#7723). [Artur Bieniek, Antmicro Ltd.]
* Support printing enum names for %p and %s (#5523) (#7338 repair) (#7521) (#7527). [Nick Brereton]
* Support weak `until` / `until_with` property operators (#7290) (#7548) (#7685). [Yilou Wang]
* Support `s_eventually` (#7291) (#7508). [Bartłomiej Chmiel, Antmicro Ltd.]
@ -59,6 +61,14 @@ Verilator 5.049 devel
* Support if/if-else in properties (#7692). [Artur Bieniek, Antmicro Ltd.]
* Support process::self().srand() (#7695). [Igor Zaworski, Antmicro Ltd.]
* Support MacOS lldb (#7697). [Tracy Narine]
* Support assoc array methods with wide value types (#7680). [pawelktk]
* Support property case (#7721). [Artur Bieniek, Antmicro Ltd.]
* Support `s_until` and `s_until_with`(#7722). [Artur Bieniek, Antmicro Ltd.]
* Support covergroup runtime model Phase A1 (#7728). [Matthew Ballance]
* Support reduction XOR/AND operations in constraints (#7753). [Kornel Uriasz, Antmicro Ltd.]
* Support assertion control system tasks in classes and interfaces (#7761). [Yilou Wang]
* Support cover sequence statement (#7764). [Yilou Wang]
* Support unpacked struct stream (#7767). [Nick Brereton]
* Optimize emitting to_string() for compiler speedup (#7468). [Jakub Michalski, Antmicro Ltd.]
* Optimize additional DFG peephole cases (#7553). [Varun Koyyalagunta, Testorrent USA, Inc.]
* Optimize forced signal handling (#7554 partial) (#7572) (#7594) (#7596). [Krzysztof Bieganski, Artur Bieniek, Antmicro Ltd.]
@ -71,8 +81,17 @@ Verilator 5.049 devel
* Optimize runtime assertOn() checks (#7707). [Geza Lore, Testorrent USA, Inc.]
* Optimize $countones and $onehot in DFG. [Geza Lore, Testorrent USA, Inc.]
* Optimize procedural loop unrolling. [Geza Lore, Testorrent USA, Inc.]
* Optimize V3Gate inlining heuristic (#7716). [Geza Lore, Testorrent USA, Inc.]
* Optimize reset in DFG (#7737). [Geza Lore, Testorrent USA, Inc.]
* Optimize DFG with relaxed live variable analysis (#7739). [Geza Lore, Testorrent USA, Inc.]
* Optimize conditional patterns sharing common MBSs/LSBs in DfgPeephole (#7760). [Geza Lore, Testorrent USA, Inc.]
* Optimize bit select removal earlier in DFG (#7762). [Geza Lore, Testorrent USA, Inc.]
* Optimize away proven redundant case statement assertions (#7771). [Geza Lore, Testorrent USA, Inc.]
* Optimize table lookups in DFG (#7772). [Geza Lore, Testorrent USA, Inc.]
* Optimize input combinational logic by change detection (#7784). [Geza Lore, Testorrent USA, Inc.]
* Fix TSP variable ordering for mtasks (#5342) (#7610). [Muzaffer Kal]
* Fix inlining static initializer in V3Gate (#5381) (#7503). [Andrew Nolte] [Geza Lore, Testorrent USA, Inc.]
* Fix timed nested fork block with disable (#6720) (#7743). [Marco Bartoli]
* Fix segmentation fault when using --trace with --lib-create (#7299) (#7518). [anonkey]
* Fix destructive event state before dynamic waits (#7340). [Nick Brereton]
* Fix ALWCOMBORDER on variable ordering (#7350) (#7608). [Cookie]
@ -128,6 +147,7 @@ Verilator 5.049 devel
* Fix loss of events due to bit shift (#7670). [Artur Bieniek, Antmicro Ltd.]
* Fix parameter read through locally-declared interface instance (#7679). [Nick Brereton]
* Fix skipping nulls in $sscanf (#7689).
* Fix bounds checks in expressions with read/write references (#7694). [Ryszard Rozak, Antmicro Ltd.]
* Fix (const) ref default task argument handling (#7698). [Nick Brereton]
* Fix `ref` argument type check for packed arrays with differing range directions (#7700). [Nick Brereton]
* Fix ignoring not-found modules with encoded names (#7706). [Igor Zaworski, Antmicro Ltd.]
@ -135,6 +155,17 @@ Verilator 5.049 devel
* Fix Makefile action to not write to ${srcdir} (#7715). [Larry Doolittle]
* Fix splitting functions containing fork logic (#7717). [Mateusz Gancarz, Antmicro Ltd.]
* Fix optimizations of assignments with timing controls (#7718). [Ryszard Rozak, Antmicro Ltd.]
* Fix s_eventually on interface (#7731) (#7733). [Marco Bartoli]
* Fix parameter values in coverage bins widths (#7732) (#7734). [Marco Bartoli]
* Fix configure fall back on dynamic malloc libraries (#7736). [Geza Lore, Testorrent USA, Inc.]
* Fix crash on overlapping priority case. [Geza Lore, Testorrent USA, Inc.]
* Fix s_eventually in parameterized interfaces (#7741). [Nick Brereton]
* Fix dpi export pointers (#7742) (#7751). [Yilin Li]
* Fix FSM detect unchecked casts and variable redeclaration (#7758). [Adam Kostrzewski, Antmicro Ltd.]
* Fix no-scope internal error on virtual interface method calls (#7759). [Yilou Wang]
* Fix 'case (_) inside' with x wildcards (#7766). [Geza Lore, Testorrent USA, Inc.]
* Fix not failing assertion when RHS of a range window rejects once (#7773). [Artur Bieniek, Antmicro Ltd.]
* Fix $fflush and autoflush with --threads (#7782).
Verilator 5.048 2026-04-26

View File

@ -495,6 +495,11 @@ MAKE_FILES = \
src/Makefile*.in \
test_regress/Makefile* \
# Markdown
MD_FILES = \
*.md \
*/*.md \
# Perl programs
PERL_PROGRAMS = \
bin/redirect \
@ -553,7 +558,7 @@ YAML_FILES = \
# Format
format:
$(MAKE) -j 5 format-c format-cmake format-exec format-py format-yaml
$(MAKE) -j 5 format-c format-cmake format-exec format-md format-py format-yaml
BEAUTYSH = beautysh
BEAUTYSH_FLAGS = --indent-size 2
@ -590,6 +595,13 @@ format-make mbake:
$(MBAKE) --version
$(MBAKE) $(MBAKE_FLAGS) $(MAKE_FILES)
MDFORMAT = mdformat
MDFORMAT_FLAGS =
format-md:
$(MDFORMAT) --version
$(MDFORMAT) $(MDFORMAT_FLAGS) $(MD_FILES)
YAPF = yapf
YAPF_FLAGS = -i --parallel

38
docs/AGENTS.md Normal file
View File

@ -0,0 +1,38 @@
<!-- DESCRIPTION: Verilator: docs/ guidelines for AI coding agents
SPDX-FileCopyrightText: 2026-2026 Wilson Snyder
SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -->
# docs/ Guidelines -- Verilator documentation (\*.rst)
When to check: before editing anything under `docs/`.
Read the repository-root [AGENTS.md](../AGENTS.md) first for process and cross-cutting rules.
## Writing rules
- Rewrap paragraphs after editing -- keep consistent line length in `*.rst` files.
- Document only stable, implemented features -- never planned or in-development capabilities; prevents user confusion and support burden.
- Explain WHAT and WHEN, not HOW -- conceptual purpose and practical use cases over implementation mechanics; describe behavior ("optimized as pure", not "treated as pure") and clarify ambiguous referents ("in the internals of Verilator").
- Keep terminology consistent -- one term per concept; update docs when renaming code constructs; spell out full terms, avoiding abbreviations like "sim"/"sims".
- Use "how many" for countable nouns (threads, tasks, workers) and "how much" for uncountable quantities.
- Mark internal or experimental features "for internal use only" -- prevents user dependence and forced deprecation cycles later.
- Use specific IEEE references: `IEEE {number}-{year}` plus the section (e.g. `Annex I`) -- a vague "IEEE spec requires" is unverifiable.
- Document all flags with descriptions, not just syntax.
## reStructuredText mechanics
- Use the `:vlopt:` role for Verilator option references -- makes cross-references clickable and consistent.
- Escape angle brackets (`\<`, `\>`) in link targets -- prevents broken links to command-line options.
- Generate documentation examples with `test.extract` from `test_regress` test files -- examples stay synced with actually tested behavior.
## Hard constraint
- Never edit `docs/CONTRIBUTORS` -- only humans may, after reading and agreeing to its statement; remind the user instead.

View File

@ -9,6 +9,7 @@ Please see the Verilator manual for 200+ additional contributors. Thanks to all.
404allen404
Adam Bagley
Adam Kostrzewski
Adrian Sampson
Adrien Le Masle
أحمد المحمودي (Ahmed El-Mahmoudy)
@ -158,6 +159,7 @@ Kefa Chen
Keith Colbert
Kevin Kiningham
Kevin Nygaard
Kornel Uriasz
Kritik Bhimani
Krzysztof Bieganski
Krzysztof Boronski
@ -325,3 +327,5 @@ Yogish Sekhar
24bit-xjkp
Zubin Jain
Muzaffer Kal
Yilin Li
Shashvat Prabhu

View File

@ -187,6 +187,10 @@ The grammar of control commands is as follows:
.. option:: isolate_assignments -module "<modulename>" [-task "<taskname>"] -var "<signame>"
Deprecated and has no effect (ignored).
In versions before 5.050:
Used to indicate that the assignments to this signal in any blocks
should be isolated into new blocks. Same as
:option:`/*verilator&32;isolate_assignments*/` metacomment.

View File

@ -737,6 +737,21 @@ Summary:
this is not recommended as may cause additional warnings and ordering
issues.
.. option:: -fno-ico-change-detect
Rarely needed. Disable input change detection in the input combinational
('ico') region. With change detection enabled (the default, unless
:vlopt:`--vpi` is passed), the input combinational logic is evaluated only
when a top level input has actually changed, rather than unconditionally on
the first scheduling iteration.
The change detection logic assumes a top level input only ever changes
externally between evaluations. The optimization is automatically disabled
for top level input signals that are written within the design. Accesses via
the VPI cannot be analyzed at compile time, therefore :vlopt:`--vpi`
disables this optimization for all inputs; it may be turned back on by
explicitly passing :vlopt:`-fico-change-detect <-fno-ico-change-detect>`.
.. option:: -fno-inline
.. option:: -fno-inline-funcs

View File

@ -341,6 +341,10 @@ or "`ifdef`"'s may break other tools.
.. option:: /*verilator&32;isolate_assignments*/
Deprecated and has no effect (ignored).
In versions before 5.050:
Used after a signal declaration to indicate the assignments to this
signal in any blocks should be isolated into new blocks. When large
combinatorial block results in a :option:`UNOPTFLAT` warning, attaching

View File

@ -2366,11 +2366,6 @@ List Of Warnings
the conflict. If you run with :vlopt:`--report-unoptflat`, Verilator will
suggest possible candidates for :option:`/*verilator&32;split_var*/`.
The UNOPTFLAT warning may also occur where outputs from a block of logic
are independent, but occur in the same always block. To fix this, use
the :option:`/*verilator&32;isolate_assignments*/` metacomment described
above.
Before version 5.000, the UNOPTFLAT warning may also have been due to
clock enables, identified from the reported path going through a clock
gating instance. To fix these, the clock_enable meta comment was used.

View File

@ -861,6 +861,7 @@ hx
hyperthreading
hyperthreads
icecream
ico
idmap
ifdef
ifdefed
@ -924,6 +925,7 @@ localparams
localtime
logicals
longint
lookups
lossy
lsb
lubc

60
include/AGENTS.md Normal file
View File

@ -0,0 +1,60 @@
<!-- DESCRIPTION: Verilator: include/ guidelines for AI coding agents
SPDX-FileCopyrightText: 2026-2026 Wilson Snyder
SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -->
# include/ -- Verilator runtime library
Covers the C++ runtime under `include/` (`verilated.h/.cpp`, `verilated_*.h/.cpp`)
that every generated model links against. Read the repository-root
[AGENTS.md](../AGENTS.md) first. The rules here differ from `src/`: this code
ships to users, runs every simulation cycle, and must stay portable and fast.
______________________________________________________________________
# Orientation
- **This is the runtime, not the compiler.** The passes in `src/` *emit* C++ that
calls into this library; this code then *runs* during simulation. Optimize it
for execution speed and portability, not for compile-time clarity.
- **Key files:** `verilated.h` (core model API), `verilated_types.h` (data
types), `verilated_random.cpp` (constrained-random runtime), `verilated_cov.*`
(coverage), `verilated_threads.*` (MT runtime), `verilated_timing.*`
(`--timing` runtime), `verilated_vcd_c.*`/`verilated_fst_c.*` (tracing).
- A runtime-only fix lives entirely here and does not rebuild `verilator_bin`.
______________________________________________________________________
# Before you open a PR
- **C++14 baseline.** The runtime must build under `--no-timing` with C++14; C++20
features are allowed only in `--timing` code paths.
- **Public API naming and docs.** Prefix public classes and types with
`Verilated`/`Vl` to avoid collisions with user code, and document the API with
`///` comments -- they feed doc generation.
- **No exceptions in runtime code** -- use error returns or assertions; exceptions
add overhead on every path.
- **Use fixed-width model types** (`CData`/`SData`/`IData`/`QData`/`VlWide`), never
`size_t`, for model data. Process wide data word-by-word (`VL_ZERO_W`,
`VL_MEMCPY_W`), never bit-by-bit or byte-by-byte.
- **Do all string parsing at verilation time** -- never parse strings during
simulation; emit structured data or compile-time hints instead.
- **Keep per-cycle code lean.** Do not add vtables to high-frequency objects
(8 bytes per instance); guard optional features behind
`hasClasses()`/`hasEvents()`-style checks; functions called per cycle must avoid
mutexes -- use atomics or lockless designs.
- **Emit no runtime loops** the compiler could have expanded at verilation time;
prefer a single runtime call.
- **Thread safety.** Annotate with the hierarchy `VL_PURE` > `VL_MT_SAFE` >
`VL_MT_STABLE`; annotations must match the implementation. Annotate
mutex-protected members with `VL_GUARDED_BY` and document acquisition ordering.
Prefer has-a over is-a: a guarded class wrapping the unguarded internal one, with
the guarded version as the default public API.
- **Keep runtime and compiler headers separate** -- never include `verilated.h`
into the compiler in `src/`.
## File-specific rules
| File | Rule |
|---|---|
| `verilated_random.cpp` | Emit only portable SMT-LIB 2.6 -- no solver-specific or MaxSMT extensions; the generated solver text is the model's runtime constraint interface |
| `verilated_cov.cpp` | Coverage runtime is shared by all models -- keep per-point overhead minimal and the on-disk format stable for `verilator_coverage` |

View File

@ -318,6 +318,13 @@ void VL_PRINTF_MT(const char* formatp, ...) VL_MT_SAFE {
}});
}
void VL_FFLUSH_MT() VL_MT_SAFE {
va_list ap;
VerilatedThreadMsgQueue::post(VerilatedMsg{[=]() { //
Verilated::runFlushCallbacks();
}});
}
template <typename... snprintf_args_ts>
static size_t _vl_snprintf_string(std::string& str, const char* format,
snprintf_args_ts... args) VL_MT_SAFE {

View File

@ -0,0 +1,63 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//=============================================================================
//
// Code available from: https://verilator.org
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2024-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//=============================================================================
///
/// \file
/// \brief Verilated functional-coverage model interfaces
///
/// Defines interface classes to runtime covergroup coverage-collection classes.
/// These are used to query coverage achievement at runtime, and (future)
/// when writing coverage to the coverage database.
///
//=============================================================================
#ifndef VERILATOR_VERILATED_COV_MODEL_H_
#define VERILATOR_VERILATED_COV_MODEL_H_
#include "verilatedos.h"
#include <cstdint>
#include <string>
// Per-bin classification. A bin's kind is which set it lives in (structural),
// not a per-bin field. Only Normal feeds coverage(); the rest are recorded.
// Enumerators are 'KIND_'-prefixed because the bare LRM terms collide with
// <windows.h> macros (e.g. IGNORE), which the preprocessor would expand.
enum class VlCovBinKind : uint8_t {
KIND_NORMAL = 0, // Base coverage-collecting bin
KIND_DEFAULT = 1, // Bin declared with 'default' range (which is excluded per LRM)
KIND_IGNORE = 2, // Ignore bin
KIND_ILLEGAL = 3 // Illegal bin
};
//=============================================================================
// VlCoverpointIf
/// Read-side view of a coverpoint. The writer queries bins by index; the
/// implementor computes names/kinds on demand. Bounded bin count, so random
/// access by index is the primary usage.
class VlCoverpointIf VL_NOT_FINAL {
public:
// CONSTRUCTORS
virtual ~VlCoverpointIf() = default;
// METHODS
// All bins, across every set; index range [0, binCount())
virtual int binCount() const = 0;
// Bin name in declaration order (e.g. "myBin" or "b[3]")
virtual std::string binName(int i) const = 0;
virtual VlCovBinKind binKind(int i) const = 0;
// Bins covered / effective total (Normal set only) for the coverage calc
virtual void coverageParts(double& covered, double& total) const = 0;
};
#endif // Guard

View File

@ -0,0 +1,78 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//=============================================================================
//
// Code available from: https://verilator.org
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2024-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//=============================================================================
///
/// \file
/// \brief Verilated functional-coverage collection runtime implementation
///
/// Compiled and linked when "verilator --coverage" is used with covergroups.
///
//=============================================================================
#include "verilatedos.h"
#include "verilated_covergroup.h"
#include "verilated_cov.h"
void VlCoverpoint::init(const char* hier, uint32_t atLeast, int nBins) {
m_hier = hier;
m_atLeast = atLeast;
m_total = nBins;
m_counts.assign(nBins, 0);
}
void VlCoverpoint::addNamer(VlCovBinKind set, int count, VlCovBinNaming naming, const char* name,
const char* file, int line, int col) {
m_namers.emplace_back(set, count, m_nextBase, naming, name, file, line, col);
m_nextBase += count;
if (set == VlCovBinKind::KIND_NORMAL) m_normal += count;
}
const VlCovNamer& VlCoverpoint::namerFor(int i) const {
// Namers are appended in ascending, contiguous index order covering [0, m_total),
// and i is always a valid bin index, so the matching namer always exists.
for (const VlCovNamer& nm : m_namers) {
if (i < nm.base() + nm.count()) return nm;
}
VL_UNREACHABLE;
}
std::string VlCoverpoint::binName(int i) const {
const VlCovNamer& nm = namerFor(i);
std::string name = nm.name();
if (nm.naming() == VlCovBinNaming::Array) name += '[' + std::to_string(i - nm.base()) + ']';
return name;
}
void VlCoverpoint::registerBins(VerilatedCovContext* covcontextp, const char* page) {
for (int i = 0; i < binCount(); ++i) {
const VlCovNamer& nm = namerFor(i);
const VlCovBinKind kind = binKind(i);
const std::string binp = binName(i);
const std::string full = m_hier + "." + binp;
const std::string lineStr = std::to_string(nm.line());
const std::string colStr = std::to_string(nm.col());
if (kind == VlCovBinKind::KIND_NORMAL) {
VL_COVER_INSERT(covcontextp, full.c_str(), &m_counts[i], "page", page, "filename",
nm.file(), "lineno", lineStr.c_str(), "column", colStr.c_str(), "bin",
binp.c_str());
} else {
const char* const binType = kind == VlCovBinKind::KIND_IGNORE ? "ignore"
: kind == VlCovBinKind::KIND_ILLEGAL ? "illegal"
: "default";
VL_COVER_INSERT(covcontextp, full.c_str(), &m_counts[i], "page", page, "filename",
nm.file(), "lineno", lineStr.c_str(), "column", colStr.c_str(), "bin",
binp.c_str(), "bin_type", binType);
}
}
}

View File

@ -0,0 +1,144 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//=============================================================================
//
// Code available from: https://verilator.org
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2024-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//=============================================================================
///
/// \file
/// \brief Verilated functional-coverage collection runtime
///
/// VlCoverpoint owns per-instance bin-count storage for one coverpoint,
/// computes coverage, builds bin names on demand, and registers bins with the
/// coverage database. It implements the VlCoverpointIf read interface.
///
/// Generated covergroup code holds one VlCoverpoint per coverpoint, configures
/// it in the constructor (init + add*Namer), increments bins from sample(),
/// and registers via registerBins().
///
//=============================================================================
#ifndef VERILATOR_VERILATED_COVERGROUP_H_
#define VERILATOR_VERILATED_COVERGROUP_H_
#include "verilatedos.h"
#include "verilated_cov_model.h"
#include <cstdint>
#include <string>
#include <vector>
class VerilatedCovContext;
// How a namer builds the names of the bins it covers.
enum class VlCovBinNaming : uint8_t {
Single, // "<name>" one bin
Array, // "<name>[i]" bins b[N] value array
};
// Specifies the naming scheme for a range of bins, allowing the
// specific name to be computed on-demand.
// All name strings are borrowed literals from the generated code.
class VlCovNamer final {
// MEMBERS
VlCovBinKind m_set; // which set the bins belong to
int m_count; // bins this namer covers (1 for Single)
int m_base; // first bin index (declaration order), assigned on append
VlCovBinNaming m_naming; // how bin names are built
const char* m_name; // bin name (Single) or array base name (Array)
const char* m_file; // declaration file
int m_line; // declaration line
int m_col; // declaration column
public:
// CONSTRUCTORS
VlCovNamer(VlCovBinKind set, int count, int base, VlCovBinNaming naming, const char* name,
const char* file, int line, int col)
: m_set{set}
, m_count{count}
, m_base{base}
, m_naming{naming}
, m_name{name}
, m_file{file}
, m_line{line}
, m_col{col} {}
// METHODS
VlCovBinKind set() const { return m_set; }
int count() const { return m_count; }
int base() const { return m_base; }
VlCovBinNaming naming() const { return m_naming; }
const char* name() const { return m_name; }
const char* file() const { return m_file; }
int line() const { return m_line; }
int col() const { return m_col; }
};
//=============================================================================
// VlCoverpoint
/// Per-instance coverpoint runtime. Bins are stored in declaration order; a
/// bin's set/name come from the owning namer. coverage() is computed on demand
/// by scanning bin counts, keeping the sample() hot path a plain counter bump.
class VlCoverpoint final : public VlCoverpointIf {
// MEMBERS
std::string m_hier; // "covergroup.coverpoint"
uint32_t m_atLeast = 1; // option.at_least (coverpoint-wide)
int m_total = 0; // bins across all sets
int m_normal = 0; // Normal bins (coverage denominator)
int m_nextBase = 0; // running append cursor
std::vector<uint32_t> m_counts; // [m_total], one per bin
std::vector<VlCovNamer> m_namers; // appended in declaration order
// PRIVATE METHODS
const VlCovNamer& namerFor(int i) const; // obtain the bin-specific name producer
void addNamer(VlCovBinKind set, int count, VlCovBinNaming naming, const char* name,
const char* file, int line, int col);
public:
// CONSTRUCTORS
VlCoverpoint() = default;
// METHODS
// ---- configuration (from generated constructor) ----
void init(const char* hier, uint32_t atLeast, int nBins);
void addSingleNamer(VlCovBinKind set, const char* name, const char* file, int line, int col) {
addNamer(set, 1, VlCovBinNaming::Single, name, file, line, col);
}
void addArrayNamer(VlCovBinKind set, int count, const char* name, const char* file, int line,
int col) {
addNamer(set, count, VlCovBinNaming::Array, name, file, line, col);
}
void registerBins(VerilatedCovContext* covcontextp, const char* page);
// ---- hot path (from generated sample()) ----
void incrementBin(int i) { ++m_counts[i]; } // Normal bin: count only
void recordHit(int i) { ++m_counts[i]; } // Ignore/Illegal/Default: count only
// ---- VlCoverpointIf ----
int binCount() const override { return m_total; }
std::string binName(int i) const override;
VlCovBinKind binKind(int i) const override { return namerFor(i).set(); }
void coverageParts(double& covered, double& total) const override {
// Count Normal bins that reached option.at_least on demand, so the hot
// path (incrementBin) stays a plain counter bump.
int numCovered = 0;
for (const VlCovNamer& nm : m_namers) {
if (nm.set() != VlCovBinKind::KIND_NORMAL) continue;
for (int i = nm.base(); i < nm.base() + nm.count(); ++i) {
if (m_counts[i] >= m_atLeast) ++numCovered;
}
}
covered = numCovered;
total = m_normal;
}
};
#endif // Guard

View File

@ -74,10 +74,8 @@ extern void VL_FATAL_MT(const char* filename, int linenum, const char* hier,
extern void VL_WARN_MT(const char* filename, int linenum, const char* hier,
const char* msg) VL_MT_SAFE;
// clang-format off
/// Print a string, multithread safe. Eventually VL_PRINTF will get called.
extern void VL_PRINTF_MT(const char* formatp, ...) VL_ATTR_PRINTF(1) VL_MT_SAFE;
// clang-format on
/// Print a debug message from internals with standard prefix, with printf style format
extern void VL_DBG_MSGF(const char* formatp, ...) VL_ATTR_PRINTF(1) VL_MT_SAFE;
@ -85,6 +83,9 @@ extern void VL_DBG_MSGF(const char* formatp, ...) VL_ATTR_PRINTF(1) VL_MT_SAFE;
/// Print a debug message from string via VL_DBG_MSGF
inline void VL_DBG_MSGS(const std::string& str) VL_MT_SAFE { VL_DBG_MSGF("%s", str.c_str()); }
/// Flush stdout
extern void VL_FFLUSH_MT() VL_MT_SAFE;
// EMIT_RULE: VL_RANDOM: oclean=dirty
inline IData VL_RANDOM_I() VL_MT_SAFE { return vl_rand64(); }
inline QData VL_RANDOM_Q() VL_MT_SAFE { return vl_rand64(); }

View File

@ -97,6 +97,28 @@ struct VlWide final {
bool operator!=(const VlWide<N_Words>& that) const VL_PURE { return !(*this == that); }
EData& operator[](size_t index) VL_MT_SAFE { return m_storage[index]; }
const EData& operator[](size_t index) const VL_MT_SAFE { return m_storage[index]; }
VlWide<N_Words>& operator&=(const VlWide& rhs) {
VL_AND_W(N_Words, *this, *this, rhs);
return *this;
}
VlWide<N_Words>& operator|=(const VlWide& rhs) {
VL_OR_W(N_Words, *this, *this, rhs);
return *this;
}
VlWide<N_Words>& operator^=(const VlWide& rhs) {
VL_XOR_W(N_Words, *this, *this, rhs);
return *this;
}
VlWide<N_Words>& operator+=(const VlWide& rhs) {
VL_ADD_W(N_Words, *this, *this, rhs);
return *this;
}
VlWide<N_Words>& operator*=(const VlWide& rhs) {
VlWide<N_Words> out{};
VL_MUL_W(N_Words, out, *this, rhs);
for (size_t i = 0; i < N_Words; ++i) m_storage[i] = out.m_storage[i];
return *this;
}
// METHODS
EData& at(size_t index) VL_MT_SAFE { return m_storage[index]; }
@ -1280,7 +1302,7 @@ public:
}
T_Value r_sum() const {
T_Value out(0); // Type must have assignment operator
T_Value out = T_Value{};
for (const auto& i : m_map) out += i.second;
return out;
}
@ -1291,8 +1313,9 @@ public:
return out;
}
T_Value r_product() const {
if (m_map.empty()) return T_Value(0); // The big three do it this way
T_Value out = T_Value(1);
// The big three return 0 when assoc array is empty
if (m_map.empty()) return T_Value{};
T_Value out = T_Value{1};
for (const auto& i : m_map) out *= i.second;
return out;
}
@ -1304,8 +1327,9 @@ public:
return out;
}
T_Value r_and() const {
if (m_map.empty()) return T_Value(0); // The big three do it this way
T_Value out = ~T_Value(0);
// The big three return 0 when assoc array is empty
if (m_map.empty()) return T_Value{};
T_Value out = m_map.cbegin()->second;
for (const auto& i : m_map) out &= i.second;
return out;
}
@ -1317,7 +1341,7 @@ public:
return out;
}
T_Value r_or() const {
T_Value out = T_Value(0);
T_Value out = T_Value{};
for (const auto& i : m_map) out |= i.second;
return out;
}
@ -1328,7 +1352,7 @@ public:
return out;
}
T_Value r_xor() const {
T_Value out = T_Value(0);
T_Value out = T_Value{};
for (const auto& i : m_map) out ^= i.second;
return out;
}

View File

@ -21,6 +21,7 @@ compiledb==0.10.7
distro==1.9.0
gersemi==0.23.1
mbake==1.4.3
mdformat==1.0.0
mypy==1.19.0
pylint==3.0.2
ruff==0.14.8

241
src/AGENTS.md Normal file
View File

@ -0,0 +1,241 @@
<!-- DESCRIPTION: Verilator: src/ guidelines for AI coding agents
SPDX-FileCopyrightText: 2026-2026 Wilson Snyder
SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -->
# src/ -- Verilator compiler sources
Covers all C++ under `src/`, including astgen inputs and the parser/lexer
(`verilog.y`, `verilog.l`). Read the repository-root [AGENTS.md](../AGENTS.md)
first. This file has two parts: **Orientation** explains the AST and pass model;
**Before you open a PR** is the style and correctness checklist.
______________________________________________________________________
# Orientation: the AST and the visitor model
- **Everything is an `AstNode`.** Each construct is an `Ast*` subclass (`AstAdd`,
`AstVar`, `AstIf`). The design under analysis is one tree, with statement lists
threaded by sibling `nextp()` links.
- **Children sit in numbered slots `op1p()`..`op4p()`, accessed by name.** Use the
named accessors (`lhsp()`, `condp()`, `thensp()`), not the raw slots -- the
numbering is an implementation detail.
- **`astgen` generates node boilerplate.** Declare children and cross-node
pointers with `@astgen op` / `@astgen ptr` annotations in the `V3AstNode*.h`
headers; it emits accessors, clone, and broken-check code. Do not hand-write
what astgen can generate.
- **A pass is a visitor.** Convention: a class with a private constructor and a
static `apply()` entry point, named after its file (`TimingSuspendableVisitor`
in `V3Timing.cpp`). It walks the tree through `visit(AstFoo*)` handlers and
`iterateChildren()`. To understand a pass, read its top-of-file comment first --
every `.cpp` opens with one describing the algorithm.
- **Scratch state lives on nodes.** Passes stash data in `user1p()`..`user5p()`
(and `user1()`..`user5()`), claimed for the pass lifetime with a `VNUser*InUse`
guard. Save and restore visitor members across recursion with `VL_RESTORER`.
- **Three downcasts, three null behaviors:** `VN_IS` returns bool, `VN_CAST`
returns nullptr on mismatch, `VN_AS` asserts the type. `V3Broken` re-validates
tree invariants between passes, so trust resolved pointers (`dtypep()`,
`varp()`) instead of adding defensive null checks for impossible cases.
- `docs/internals.rst` is the authoritative reference for the AST, the pass list,
and node lifetime.
______________________________________________________________________
# Before you open a PR
## Code style
- Mark every variable, parameter, pointer, and member function `const` where possible.
- Pointer variables take a `p` suffix and pointer locals are doubly const where
possible: `AstVar* const varp`; non-pointers never use the `p` suffix.
- Do not use `auto` except for iterators or genuinely unwieldy types.
- Use pre-increment (`++i`) unless you specifically need post-increment's old value.
- Brace-initialize node and struct construction: `new AstIf{fl, condp, thenp, elsep}`.
- Never use C-style casts; instead use `static_cast<T>` for non-AST types and
`VN_AS`/`VN_CAST` for AST downcasts.
- `static constexpr` for compile-time constants, not `#define` or file-scope const.
- Mark every `class`/`struct` `final` or `VL_NOT_FINAL` -- a distribution test
scans all definitions.
- Keep functions under roughly 100-150 lines; thread shared state through a
context struct rather than long parameter lists.
- Keep headers lean: move implementation to `.cpp`; convert large lambdas into
named member functions -- lambdas are opaque in stack traces and block reuse.
- Start every new `.cpp` with a top-of-file comment explaining the algorithm.
- Comments are capitalized sentences written for an unknown future reader, without
"I"/"we"/"our"; remove commented-out code -- version control preserves history.
- No `using namespace`; prefix non-namespaced symbols with `VL`/`Vl`.
- Prefer semantic predicates over enum comparisons: `varp->isClassMember()`, not
`varp->varType() == VVarType::MEMBER`.
- `new*` functions return a `new` object; `make*` functions do something more
complex -- pick the prefix accordingly.
- Name compiler-created temporaries with a `__V` prefix plus a context suffix
(`__VInside`, `__VCase`); runtime utility functions use a `vl_` prefix.
- Use `VL_*` bit/word macros from `verilatedos.h` (`VL_WORDS_I`, `VL_MASK_I`); do
not include `<cstdint>` directly.
- Replace magic numbers with named `static constexpr` constants.
## AST construction and manipulation
- Build logic as AST nodes, never as raw C text in `AstCStmt` -- later passes
(V3Name, `--protect`) rename AST identifiers but cannot see into raw strings.
- Know the cast forms (above) and never pair `VN_IS` with `VN_AS` on the same
node -- use a single `VN_CAST`:
```cpp
// BAD: redundant double check
if (VN_IS(nodep, VarRef)) { AstVarRef* const refp = VN_AS(nodep, VarRef); }
// GOOD: single conditional cast
if (const AstVarRef* const refp = VN_CAST(nodep, VarRef)) { ... }
```
- Use `UASSERT_OBJ(cond, nodep, "...")` over `UASSERT` when a node is in scope;
use `v3fatalSrc("...")` for unreachable paths, never a silent `if (!ptr) return;`.
- Use `VL_DO_DANGLING(pushDeletep(nodep), nodep)` instead of `deleteTree()` in
visitors -- deferred deletion is safe against re-entry and unlinking order.
`deleteTree()` is only for fresh nodes that never entered the tree.
- Every new AST member needs both `dump()` and `dumpJson()` support -- never wrap
these in `LCOV_EXCL`; cover them by adding a construct to `t_debug_emitv.v`.
Override `isSame()` to include any new semantically meaningful field.
- Pointers to nodes outside op1p-op4p require a `broken()` override and
`cloneRelink()` support -- avoid storing out-of-tree node pointers when possible.
- Never allocate AstNode objects on the stack or by value -- always pointers.
- Prefer a new `visit()` in an existing visitor over `nodep->foreach(...)` --
better for runtime, and handles diverse node types better. Prefer named
accessors over `op1p()`..`op4p()`, and verify traversal order is preserved
when converting manual iteration.
- Prefer `AstForeach` over generating unrolled loop bodies -- constant-size code
instead of O(N); wrap the body in `AstBegin` for scope isolation.
- Always `skipRefp()` when comparing or resolving dtypes -- missing it breaks
typedef support; prove the paths with typedef tests.
- Use `num().isOpaque()` rather than `isDouble() || isString()` when guarding
V3Number comparisons against non-integer types.
- Use `FileLine::operatorCompare` for source-position ordering -- never hand-roll
filename/lineno comparisons.
- Identify compiler-generated constructs by an attribute flag on the node (with
dump support), never by name-pattern matching -- magic names break with escaped
identifiers.
- Use `V3Number` arithmetic for `AstConst` values wider than 32 bits -- `1 << i`
silently overflows at `i >= 32`.
- Use `VMemberMap`/`findMember()` for name lookups in structs, classes, modules,
and packages -- O(1) versus quadratic linear scans.
- Never emit raw source filenames or identifiers in generated code -- pass them
through `protect()`/`putsQuoted` so `--protect` does not leak source.
## Visitors and passes
- `VL_RESTORER` on every visitor member a `visit()` modifies before iterating
children -- even when nesting "cannot happen" today.
- Every pass using `userNp()` needs a `VNUserNInUse` guard, and the header
documents which user fields it uses.
- Use `iterateAndNextNull()` rather than `iterate()` -- the null-safe form
prevents copy-paste errors during refactors.
- Derive read-only visitors from `VNVisitorConst` with `iterateChildrenConst`.
- Reset per-module visitor state in `visit(AstNodeModule*)`, including numeric ID
counters, to keep generated names stable.
- Capture first-occurrence module state inside the node's own `visit()` handler,
not via a `foreach` pre-scan from `visit(AstNodeModule)` -- source order already
matches IEEE declaration-before-use.
- Avoid `backp()` -- it returns parent or prior sibling depending on position and
causes O(n^2) hunts; build maps or capture context during forward traversal.
- When raw node pointers key a map or set, erase entries when the node is deleted
-- allocators reuse addresses, so stale entries alias new nodes.
- Derive graph-shaped passes from V3Graph (`V3GraphVertex`/`V3GraphEdge`) -- it
gives dump, color, rank, topological sort, and reachability for free.
- When a change outgrows local rewrites, create a dedicated pass instead of
growing an existing one.
- State explicitly how side effects are preserved in optimizations involving
purity, expression lifting, or simplification.
## Errors and warnings
(See the root checklist for choosing the diagnostic API and its required test.)
- Append `nodep->prettyNameQ()` for user-facing names; use `name()` only in
debug/UINFO output. Enclose specific values in single quotes: `'value'`.
- End messages with periods, never exclamation marks; do not write "Error:" in the
text -- the macro prints the prefix.
- State what was attempted and what was found: "Instance attempts to connect to
'PARAM' as a parameter, but it is a variable". Add a `warnMore()` suggestion
where possible.
- Name warning codes object-first and short (`ASCRANGE`, not `RANGEASC`); rename
via `renamedTo()` so old suppressions keep working.
- Set warning suppression on `AstVar`, not `AstVarRef` -- VarRefs get recreated
and lose `warnIsOff`.
- "Unsupported:" messages describe the specific unsupported context, not just the
construct name -- each message must be distinct.
- When replacing or refactoring a pass, keep existing user-facing error strings --
`.out` goldens and documentation depend on the wording.
## Performance and memory
- O(n^2) is never acceptable -- build maps for batch lookups; any quadratic loop
needs explicit justification in a comment.
- Prefer `std::map` for per-module structures (many small instances); use
`unordered_map` only for one-per-netlist data, and never let `unordered_*`
iteration order reach generated output.
- Prefer `emplace` over `insert` and check the returned `.second` instead of a
separate `find()`. `reserve()` strings and vectors when the size is estimable.
- Add no new static or global mutable data -- statics are being eliminated for
future parallelism.
- Use Verilator's fixed-width data types for model data (`CData`/`SData`/`IData`/
`QData`/`VlWide`), not `size_t`. Process wide data word-by-word
(`VL_ZERO_W`, `VL_MEMCPY_W`), never bit-by-bit.
- No exceptions in verilated runtime code; do string parsing at verilation time,
never during simulation.
- Wrap unlikely hot-path branches in `VL_UNLIKELY`/`VL_LIKELY`.
- Count what every new pass does via V3Stats -- stats become deterministic
regression anchors.
## Thread safety
- Annotate with the hierarchy `VL_PURE` > `VL_MT_SAFE` > `VL_MT_STABLE`: PURE has
no side effects and calls only PURE; MT_SAFE is safe under locks; MT_STABLE is
safe only while tree topology is stable. Annotations must match the
implementation.
- Never include `verilated.h` in the compiler itself -- use `verilatedos.h`.
- Annotate mutex-protected members with `VL_GUARDED_BY` and document acquisition
ordering. `++` on shared state and container `empty()` are not thread-safe.
## Parser and lexer (verilog.y, verilog.l)
- Preserve IEEE Appendix A BNF comments (`// IEEE: {rule}`); comment explicitly
when accepting syntax beyond IEEE as an extension.
- The parser only builds AST nodes -- defer semantic validation, `VN_IS` checks,
and context-dependent logic to V3LinkParse/V3Width and later passes.
- Represent hierarchical paths as structured nodes (`AstDot`/parse-ref chains via
`idDotted`), never concatenated strings -- preserves per-segment FileLine.
- Prefer tightening a grammar rule's operand type over a runtime cast-chain guard
in a later visitor -- illegal operands then fail with a clean syntax error.
- Solve ambiguities with token-pipeline look-ahead (`tokenPipeScan*`) rather than
limiting grammar rules; mark unsupported rules with `//UNSUP`.
- Sort token declarations alphabetically by string literal; sort `yD_*`
productions by token name.
- Add a test for every `|` alternative and optional clause of a new or changed
grammar rule -- untested alternatives are where parse regressions hide.
## File-specific rules
| File | Rule |
|---|---|
| `src/V3Options.cpp` | Chain `.notForRerun()` onto `DECL_OPTION()` for options that do not affect semantic output |
| `src/V3Ast.cpp` | For composite types (queues, dynamic arrays) use `computeCastableImp()` on subtypes -- shallow `width()`/`similarDType()` checks miss nested incompatibility |
| `src/V3AstNode*.h` | Every node class gets a what-construct comment and every member a semantic-purpose comment; put enum type definitions in `V3AstAttr.h` |
| `src/V3AstNodeExpr.h` | `CCast` is only for basic C types (char/short/int/QData) -- never 4-state logic or structs |
| `src/V3AstNodeOther.h` | `cloneRelink` must propagate all stateful flags (e.g. `maybePointedTo`) and update internal references |
| `src/V3Const.cpp` | Check `keepIfEmpty` before removing empty functions -- flagged functions must survive for codegen/side effects |
| `src/V3Coverage.cpp` | Instrumentation contexts are opt-in (allowlist), never blocklist -- blocklists silently break when new contexts appear |
| `src/V3Inline.cpp` | Preserve `VarXRef::varp()` during passes -- pin-reconnection needs it before V3LinkDot re-resolves |
| `src/V3Sched*.cpp` | Every change needs a test proving necessity; isolate unrelated scheduler changes into separate PRs -- high-risk area |

View File

@ -175,7 +175,6 @@ set(HEADERS
V3Simulate.h
V3Slice.h
V3Split.h
V3SplitAs.h
V3SplitVar.h
V3StackCount.h
V3Stats.h
@ -183,7 +182,6 @@ set(HEADERS
V3String.h
V3Subst.h
V3SymTable.h
V3TSP.h
V3Table.h
V3Task.h
V3ThreadPool.h
@ -351,14 +349,12 @@ set(COMMON_SOURCES
V3Scoreboard.cpp
V3Slice.cpp
V3Split.cpp
V3SplitAs.cpp
V3SplitVar.cpp
V3StackCount.cpp
V3Stats.cpp
V3StatsReport.cpp
V3String.cpp
V3Subst.cpp
V3TSP.cpp
V3Table.cpp
V3Task.cpp
V3ThreadPool.cpp

View File

@ -333,11 +333,9 @@ RAW_OBJS_PCH_ASTNOMT = \
V3Scoreboard.o \
V3Slice.o \
V3Split.o \
V3SplitAs.o \
V3SplitVar.o \
V3StackCount.o \
V3Subst.o \
V3TSP.o \
V3Table.o \
V3Task.o \
V3Timing.o \

View File

@ -23,6 +23,79 @@
VL_DEFINE_DEBUG_FUNCTIONS;
namespace {
class DefaultDisableLocalVisitor final : public VNVisitor {
// STATE
AstNode* m_scopep = nullptr;
// VISITORS
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_scopep);
m_scopep = nodep;
nodep->defaultDisablep(nullptr);
iterateChildren(nodep);
}
void visit(AstGenBlock* nodep) override {
VL_RESTORER(m_scopep);
m_scopep = nodep;
nodep->defaultDisablep(nullptr);
iterateChildren(nodep);
}
void visit(AstDefaultDisable* nodep) override {
UASSERT_OBJ(nodep, m_scopep,
"default disable iff must be inside a module or generate block");
AstDefaultDisable* defaultp = nullptr;
if (const AstNodeModule* const modp = VN_CAST(m_scopep, NodeModule)) {
defaultp = modp->defaultDisablep();
} else {
defaultp = VN_AS(m_scopep, GenBlock)->defaultDisablep();
}
if (VL_UNLIKELY(defaultp)) {
nodep->v3error("Only one 'default disable iff' allowed per "
<< (VN_IS(m_scopep, NodeModule) ? "module" : "generate block")
<< " (IEEE 1800-2023 16.15)");
} else if (AstNodeModule* const modp = VN_CAST(m_scopep, NodeModule)) {
modp->defaultDisablep(nodep);
} else {
VN_AS(m_scopep, GenBlock)->defaultDisablep(nodep);
}
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
explicit DefaultDisableLocalVisitor(AstNetlist* nodep) { iterate(nodep); }
};
class DefaultDisablePropagateVisitor final : public VNVisitor {
// STATE
AstDefaultDisable* m_defaultDisablep = nullptr;
// VISITORS
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_defaultDisablep);
m_defaultDisablep = nodep->defaultDisablep();
iterateChildren(nodep);
}
void visit(AstGenBlock* nodep) override {
VL_RESTORER(m_defaultDisablep);
if (!nodep->defaultDisablep()) nodep->defaultDisablep(m_defaultDisablep);
m_defaultDisablep = nodep->defaultDisablep();
iterateChildren(nodep);
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
explicit DefaultDisablePropagateVisitor(AstNetlist* nodep) { iterate(nodep); }
};
} // namespace
void V3AssertCommon::collectDefaultDisable(AstNetlist* nodep) {
{ DefaultDisableLocalVisitor{nodep}; }
{ DefaultDisablePropagateVisitor{nodep}; }
}
//######################################################################
// AssertDeFutureVisitor
// If any AstFuture, then move all non-future varrefs to be one cycle behind,
@ -586,6 +659,34 @@ class AssertVisitor final : public VNVisitor {
iterateChildren(nodep);
}
if (nodep->user2()) {
// Combine consecutive assertOn checks if possible
if (AstIf* const backp = VN_CAST(nodep->backp(), If)) {
if (backp->nextp() == nodep //
&& backp->user2() //
&& backp->condp()->sameTree(nodep->condp())) {
++m_statAssertOnCombined;
backp->addThensp(nodep->thensp()->unlinkFrBackWithNext());
nodep->unlinkFrBack();
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
}
}
// Combine nested assertOn checks if possible
if (nodep->thensp() && !nodep->thensp()->nextp() && isEmptyStmt(nodep->elsesp())) {
AstIf* const checkp = VN_CAST(nodep->thensp(), If);
if (checkp //
&& checkp->user2() //
&& checkp->condp()->sameTree(nodep->condp())) {
++m_statAssertOnCombined;
nodep->addThensp(checkp->thensp()->unlinkFrBackWithNext());
VL_DO_DANGLING(pushDeletep(checkp->unlinkFrBack()), checkp);
return;
}
}
return;
}
// Swap assertOn check with single statement 'if' statement to bubble up for combining
// Note we can't just swap the conditions as they two Ifs have different flags,
// so swapping the Ifs themeselves then swapping back the bodies.
@ -611,20 +712,6 @@ class AssertVisitor final : public VNVisitor {
}
}
}
// Combine consecutive assertOn checks if possible
if (nodep->user2()) {
if (AstIf* const backp = VN_CAST(nodep->backp(), If)) {
if (backp->nextp() == nodep //
&& backp->user2() //
&& backp->condp()->sameTree(nodep->condp())) {
++m_statAssertOnCombined;
backp->addThensp(nodep->thensp()->unlinkFrBackWithNext());
nodep->unlinkFrBack();
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
}
}
}
//========== Case assertions
@ -914,12 +1001,6 @@ class AssertVisitor final : public VNVisitor {
visitAssertionIterate(nodep, nodep->failsp());
}
void visit(AstAssertCtl* nodep) override {
if (VN_IS(m_modp, Class) || VN_IS(m_modp, Iface)) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: assertcontrols in classes or interfaces");
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return;
}
iterateChildren(nodep);
if (!resolveAssertType(nodep)) {
@ -1007,10 +1088,12 @@ class AssertVisitor final : public VNVisitor {
VL_RESTORER(m_modPastNum);
VL_RESTORER(m_modStrobeNum);
VL_RESTORER(m_modExpr2Sen2DelayedAlwaysp);
VL_RESTORER(m_finalp);
m_modp = nodep;
m_modPastNum = 0;
m_modStrobeNum = 0;
m_modExpr2Sen2DelayedAlwaysp.clear();
m_finalp = nullptr;
iterateChildren(nodep);
}
void visit(AstNodeProcedure* nodep) override {

View File

@ -24,6 +24,11 @@
//============================================================================
class V3AssertCommon final {
public:
static void collectDefaultDisable(AstNetlist* nodep) VL_MT_DISABLED;
};
class V3Assert final {
public:
static void assertAll(AstNetlist* nodep) VL_MT_DISABLED;

View File

@ -26,6 +26,7 @@
#include "V3AssertNfa.h"
#include "V3Assert.h"
#include "V3Const.h"
#include "V3Graph.h"
#include "V3Task.h"
@ -109,6 +110,9 @@ public:
// Reject when source is active and condp is false; set only on
// outermost required-step Link
bool m_rejectOnFail = false;
// Optional dynamic condition vertex for m_rejectOnFail. Used when the
// success condition is another NFA state rather than a static expression.
SvaStateVertex* m_condVtxp = nullptr;
// CONSTRUCTORS
SvaTransEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, AstNodeExpr* condp,
@ -176,6 +180,10 @@ struct BuildResult final {
// Mid-window sources for range delays (pure boolean RHS): match-only (isUnbounded)
std::vector<SvaStateVertex*> midSources;
bool errorEmitted = false; // Builder already emitted specific error; skip generic
// For cover_sequence: when true, midSources already enumerate every
// end-of-match, so wireMatchAndMidSources must NOT add the main
// termVtxp -> matchVertex Link (would double-count via the merge vertex).
bool termIsMidMerge = false;
bool valid() const { return termVertexp != nullptr; }
static BuildResult fail(bool errored = false) { return {nullptr, nullptr, {}, errored}; }
static BuildResult failWithError() { return {nullptr, nullptr, {}, true}; }
@ -198,6 +206,16 @@ class SvaNfaBuilder final {
// (IEEE 1800-2023 16.12.14 outer-wraps-inner).
std::vector<AstNodeExpr*> m_outerAbortStack;
bool m_inUnboundedScope = false; // Sticky: nodes created after inherit liveness
// IEEE 1800-2023 16.14.3 cover sequence: each end-of-match fires the action,
// not just the first. Builder builds parallel-branch (no first-match-wins)
// topology when true. Default false preserves cover_property semantics.
bool m_isCoverSeq = false;
struct RangeDelayRejectInfo final {
SvaStateVertex* startp = nullptr;
int range = 0;
int rhsLen = 0;
};
AstNodeExpr* throughoutCond(AstNodeExpr* baseCondp, FileLine* flp) {
if (m_throughoutStack.empty()) return baseCondp;
@ -365,7 +383,7 @@ class SvaNfaBuilder final {
// failure, sets outErrorEmitted per semantic-error policy and returns false.
bool applyRangeDelay(AstDelay* delayp, AstNodeExpr* rhsExprp, SvaStateVertex*& currentp,
std::vector<SvaStateVertex*>& midSources, FileLine* flp,
bool& outErrorEmitted) {
bool& outErrorEmitted, RangeDelayRejectInfo* rangeRejectInfop = nullptr) {
const int minDelay = getConstInt(delayp->lhsp());
if (minDelay < 0) {
delayp->v3error("Range delay minimum is not a non-negative elaboration-time constant"
@ -405,6 +423,16 @@ class SvaNfaBuilder final {
// count is O(1) in range regardless of user input; no adversarial N
// blowup is possible.
constexpr int kChainLimit = 256;
// IEEE 1800-2023 16.14.3: only a small bounded range before a plain
// boolean enumerates every end-of-match below. The counter FSM drops
// overlapping ends and the nested-sequence merge collapses them, so
// reject those for a cover sequence rather than under-count.
if (m_isCoverSeq && (range > kChainLimit || VN_IS(rhsExprp, SExpr))) {
flp->v3warn(COVERIGN,
"Ignoring unsupported: cover sequence with this ranged cycle delay");
outErrorEmitted = true;
return false;
}
if (range > kChainLimit) {
// Large range: counter FSM. Overlapping triggers during an active
// count are dropped (non-overlapping semantics only).
@ -414,8 +442,14 @@ class SvaNfaBuilder final {
guardedEdge(currentp, counterVtxp, flp);
currentp = counterVtxp;
} else if (VN_IS(rhsExprp, SExpr)) {
// Nested-SExpr RHS: merge all [M,N] positions; continuation is per-attempt.
// Nested-SExpr RHS: merge all [M,N] positions. Candidate-local misses
// are not assertion rejects while a later position can still match.
if (rangeRejectInfop) {
const int rhsLen = fixedLength(rhsExprp);
if (rhsLen >= 0) *rangeRejectInfop = {currentp, range, rhsLen};
}
SvaStateVertex* const mergeVtxp = scopedCreateVertex();
mergeVtxp->m_isUnbounded = true;
guardedLink(currentp, mergeVtxp, flp);
for (int i = 0; i < range; ++i) {
SvaStateVertex* const nextVtxp = scopedCreateVertex();
@ -424,16 +458,25 @@ class SvaNfaBuilder final {
currentp = nextVtxp;
}
currentp = mergeVtxp;
m_inUnboundedScope = true;
} else {
// Pure boolean RHS: register chain. Each mid-position links to
// match (match-only); last position is the reject source.
AstVar* const hoistVarp = tryHoistSampled(rhsExprp, flp, range);
// For cover_sequence (IEEE 1800-2023 16.14.3) the advance edge is
// unconditional so every (start, end) pair fires independently --
// dropping NOT(b) turns "first-match-wins" into "every end fires".
AstVar* const hoistVarp
= m_isCoverSeq ? nullptr : tryHoistSampled(rhsExprp, flp, range);
midSources.push_back(currentp);
for (int i = 0; i < range; ++i) {
SvaStateVertex* const nextVtxp = scopedCreateVertex();
AstNodeExpr* const notExprp
= new AstLogNot{flp, sampledRefOrClone(hoistVarp, rhsExprp, flp)};
guardedEdge(currentp, nextVtxp, notExprp, flp);
if (m_isCoverSeq) {
guardedEdge(currentp, nextVtxp, flp);
} else {
AstNodeExpr* const notExprp
= new AstLogNot{flp, sampledRefOrClone(hoistVarp, rhsExprp, flp)};
guardedEdge(currentp, nextVtxp, notExprp, flp);
}
if (i < range - 1) midSources.push_back(nextVtxp);
currentp = nextVtxp;
}
@ -441,12 +484,44 @@ class SvaNfaBuilder final {
return true;
}
void addFiniteRangeReject(const RangeDelayRejectInfo& info, const BuildResult& result,
FileLine* flp) {
if (!info.startp) return;
SvaStateVertex* const expiryVtxp
= addDelayChain(info.startp, info.range + info.rhsLen, flp);
SvaStateVertex* const expiryMatchp = scopedCreateVertex();
std::vector<SvaStateVertex*> sources = result.midSources;
sources.push_back(result.termVertexp);
for (SvaStateVertex* const srcp : sources) {
AstNodeExpr* const condp
= result.finalCondp ? sampled(result.finalCondp->cloneTreePure(false)) : nullptr;
SvaStateVertex* const successNowp = scopedCreateVertex();
guardedLink(srcp, successNowp, condp, flp);
SvaStateVertex* stagep = successNowp;
guardedLink(stagep, expiryMatchp, flp);
for (int i = 0; i < info.range; ++i) {
SvaStateVertex* const nextp = scopedCreateVertex();
guardedEdge(stagep, nextp, flp);
stagep = nextp;
guardedLink(stagep, expiryMatchp, flp);
}
}
SvaStateVertex* const sinkVtxp = m_graph.createStateVertex();
sinkVtxp->m_isRejectSink = true;
SvaTransEdge* const rejectp = m_graph.addLink(expiryVtxp, sinkVtxp);
rejectp->m_rejectOnFail = true;
rejectp->m_condVtxp = expiryMatchp;
}
BuildResult buildSExpr(AstSExpr* sexprp, SvaStateVertex* entryVtxp,
bool isTopLevelStep = false) {
AstDelay* const delayp = VN_CAST(sexprp->delayp(), Delay);
if (!delayp || !delayp->isCycleDelay()) return BuildResult::fail();
FileLine* const flp = sexprp->fileline();
AstNodeExpr* const exprp = sexprp->exprp();
// Handle LHS (preExpr)
SvaStateVertex* currentp = entryVtxp;
@ -469,10 +544,12 @@ class SvaNfaBuilder final {
// Handle delay
std::vector<SvaStateVertex*> rangeMidSources;
RangeDelayRejectInfo rangeRejectInfo;
const bool addRangeReject = isTopLevelStep && !m_inUnboundedScope;
if (delayp->isRangeDelay()) {
bool errorEmitted = false;
if (!applyRangeDelay(delayp, sexprp->exprp(), currentp, rangeMidSources, flp,
errorEmitted)) {
errorEmitted, addRangeReject ? &rangeRejectInfo : nullptr)) {
return BuildResult::fail(errorEmitted);
}
} else {
@ -487,8 +564,11 @@ class SvaNfaBuilder final {
}
// Multi-cycle RHS: recurse (only plain boolean is returned as finalCondp).
AstNodeExpr* const exprp = sexprp->exprp();
if (exprp->isMultiCycleSva()) return buildExpr(exprp, currentp, isTopLevelStep);
if (exprp->isMultiCycleSva()) {
const BuildResult result = buildExpr(exprp, currentp, isTopLevelStep);
if (result.valid()) addFiniteRangeReject(rangeRejectInfo, result, flp);
return result;
}
return {currentp, exprp, std::move(rangeMidSources)};
}
@ -516,6 +596,10 @@ class SvaNfaBuilder final {
if (exceedsAssertUnrollLimit(repp, totalSites)) return BuildResult::failWithError();
AstVar* const hoistVarp = tryHoistSampled(exprp, flp, totalSites);
// Cover-sequence (IEEE 1800-2023 16.14.3): collect each end-of-match
// position so they all fire the action, not just the merged terminal.
std::vector<SvaStateVertex*> consMidSources;
SvaStateVertex* currentp = entryVtxp;
for (int i = 0; i < minN; ++i) {
if (i > 0) {
@ -531,6 +615,10 @@ class SvaNfaBuilder final {
if (isTopLevelStep && (i == 0 || i == minN - 1)) { linkp->m_rejectOnFail = true; }
currentp = condVtxp;
}
// After minN: currentp is the first valid end-of-match position for [*m:n].
if (m_isCoverSeq && (repp->unbounded() || repp->maxCountp())) {
consMidSources.push_back(currentp);
}
if (repp->unbounded()) {
if (minN == 0) {
@ -564,11 +652,19 @@ class SvaNfaBuilder final {
guardedLink(nextVtxp, checkVtxp, sampledRefOrClone(hoistVarp, exprp, flp), flp);
guardedLink(checkVtxp, mergeVtxp, flp);
currentp = checkVtxp;
if (m_isCoverSeq) consMidSources.push_back(checkVtxp);
}
currentp = mergeVtxp;
}
// finalCond = nullptr (already checked via Links)
return {currentp, nullptr, {}};
BuildResult res;
res.termVertexp = currentp;
res.finalCondp = nullptr;
res.midSources = std::move(consMidSources);
// mergeVtxp is the OR of all the end-positions we already pushed to
// midSources, so the main termVtxp -> matchVertex Link would duplicate.
res.termIsMidMerge = m_isCoverSeq && !res.midSources.empty();
return res;
}
// always[lo:hi] / s_always[lo:hi] (IEEE 1800-2023 16.12.11).
@ -607,6 +703,16 @@ class SvaNfaBuilder final {
UASSERT_OBJ(maxN >= minN, repp, "GotoRep range max < min (V3Width invariant)");
if (exceedsAssertUnrollLimit(repp, maxN)) return BuildResult::failWithError();
// IEEE 1800-2023 16.14.3: a ranged goto repetition b[->M:N] ends at every
// M..N-th match, but only the shared merge vertex below reaches the
// terminal, so a cover sequence would under-count. Reject the ranged form
// (the single-count b[->N] has one end and is enumerated correctly).
if (m_isCoverSeq && hasMax && maxN > minN) {
flp->v3warn(COVERIGN,
"Ignoring unsupported: cover sequence with a ranged goto repetition");
return BuildResult::failWithError();
}
// Wait + match per iter -> 2 sites per iteration; range form needs
// sites for every iteration in [0..maxN). NOT($sampled(x)) matches
// $sampled(NOT(x)) at the value level (IEEE 1800-2023 16.9.9);
@ -661,6 +767,16 @@ class SvaNfaBuilder final {
if (!lhs.valid() || !rhs.valid()) { // LCOV_EXCL_START -- sub-build fail bail
return BuildResult::fail(lhs.errorEmitted || rhs.errorEmitted);
} // LCOV_EXCL_STOP
// IEEE 1800-2023 16.14.3: a cover sequence counts every end-of-match. A
// sequence operand of 'or' can end more than once, but only its final
// end reaches the merge vertex below, so reject sequence operands rather
// than under-count. Plain boolean disjunction has one end per cycle and
// is handled by the OR-fold.
if (m_isCoverSeq && (lhs.termVertexp != entryVtxp || rhs.termVertexp != entryVtxp)) {
flp->v3warn(COVERIGN,
"Ignoring unsupported: cover sequence with a sequence operand of 'or'");
return BuildResult::failWithError();
}
SvaStateVertex* const mergeVtxp = scopedCreateVertex();
if (lhs.finalCondp) {
guardedLink(lhs.termVertexp, mergeVtxp, sampled(lhs.finalCondp->cloneTreePure(false)),
@ -970,10 +1086,12 @@ class SvaNfaBuilder final {
}
public:
SvaNfaBuilder(SvaGraph& graph, AstNodeModule* modp, V3UniqueNames& propTempNames)
SvaNfaBuilder(SvaGraph& graph, AstNodeModule* modp, V3UniqueNames& propTempNames,
bool isCoverSeq = false)
: m_graph{graph}
, m_modp{modp}
, m_propTempNames{propTempNames} {}
, m_propTempNames{propTempNames}
, m_isCoverSeq{isCoverSeq} {}
// Reset scope between antecedent and consequent: liveness must not leak.
void resetScope() {
@ -1346,8 +1464,11 @@ class SvaNfaLowering final {
// throughout-drop reject; clean up intermediate state signals.
// Phase 3: terminalActive and rejectBase from Links to matchVertex.
// Builder only adds Links (non-clocked) to matchVertex via addLink in
// wireMatchAndMidSources.
void computeTerminalMatchAndReject(LowerCtx& c, AstNodeExpr* snapshotOkp, SignalSet& sigs) {
// wireMatchAndMidSources. When outPerMidSrcsp is non-null, also collect
// the per-edge match signal (IEEE 1800-2023 16.14.3 cover sequence: each
// end-of-match fires the action independently, no OR-fold).
void computeTerminalMatchAndReject(LowerCtx& c, AstNodeExpr* snapshotOkp, SignalSet& sigs,
std::vector<AstNodeExpr*>* outPerMidSrcsp = nullptr) {
for (const SvaTransEdge* const tep : c.edges) {
if (tep->toVtxp() != c.graph.m_matchVertexp) continue;
const int fi = tep->fromVtxp()->color();
@ -1359,6 +1480,18 @@ class SvaNfaLowering final {
if (snapshotOkp) {
srcSigp = new AstLogAnd{c.flp, srcSigp, snapshotOkp->cloneTreePure(false)};
}
if (outPerMidSrcsp) {
// Per-mid signal must also AND in matchCondp (the final boolean
// check, e.g. sampled(b) for `a ##[1:3] b`). assembleResult does
// this for the OR-collapsed terminalActivep; we replicate it
// per-edge here so each end-of-match is gated identically.
AstNodeExpr* perMidp = srcSigp->cloneTreePure(false);
if (c.matchCondp) {
perMidp = new AstLogAnd{c.flp, perMidp,
sampled(c.matchCondp->cloneTreePure(false))};
}
outPerMidSrcsp->push_back(perMidp);
}
if (tep->fromVtxp()->m_isCounter) {
sigs.terminalActivep
@ -1409,7 +1542,8 @@ class SvaNfaLowering final {
}
}
SignalSet computeSignals(LowerCtx& c, std::vector<AstNodeExpr*>* outRequiredStepSrcsp) {
SignalSet computeSignals(LowerCtx& c, std::vector<AstNodeExpr*>* outRequiredStepSrcsp,
std::vector<AstNodeExpr*>* outPerMidSrcsp = nullptr) {
SignalSet sigs;
// Snapshot comparison expression for disable-iff counter.
@ -1421,18 +1555,28 @@ class SvaNfaLowering final {
new AstVarRef{c.flp, c.disableCntVarp, VAccess::READ}};
}
computeTerminalMatchAndReject(c, snapshotOkp, sigs);
computeTerminalMatchAndReject(c, snapshotOkp, sigs, outPerMidSrcsp);
// Phase 3a: required-step rejection.
// Builder only sets m_rejectOnFail on non-clocked Links with m_condp,
// and the source always has a resolved stateSig.
// Builder only sets m_rejectOnFail on non-clocked Links with m_condp
// or m_condVtxp, and the source always has a resolved stateSig.
for (const SvaTransEdge* const tep : c.edges) {
if (!tep->m_rejectOnFail) continue;
const int fi = tep->fromVtxp()->color();
UASSERT_OBJ(c.vtx[fi]->datap()->stateSigp && tep->m_condp, tep->fromVtxp(),
"rejectOnFail Link must have condp and source stateSig");
UASSERT_OBJ(c.vtx[fi]->datap()->stateSigp && (tep->m_condp || tep->m_condVtxp),
tep->fromVtxp(),
"rejectOnFail Link must have condp/condVtxp and source stateSig");
AstNodeExpr* const srcSigp = c.vtx[fi]->datap()->stateSigp->cloneTreePure(false);
AstNodeExpr* const notCondp = new AstLogNot{c.flp, tep->m_condp->cloneTreePure(false)};
AstNodeExpr* condp = nullptr;
if (tep->m_condVtxp) {
const int ci = tep->m_condVtxp->color();
UASSERT_OBJ(c.vtx[ci]->datap()->stateSigp, tep->m_condVtxp,
"rejectOnFail condVtxp missing stateSig");
condp = c.vtx[ci]->datap()->stateSigp->cloneTreePure(false);
} else {
condp = tep->m_condp->cloneTreePure(false);
}
AstNodeExpr* const notCondp = new AstLogNot{c.flp, condp};
AstNodeExpr* const failp = new AstLogAnd{c.flp, srcSigp, notCondp};
if (outRequiredStepSrcsp) {
outRequiredStepSrcsp->push_back(failp->cloneTreePure(false));
@ -1648,7 +1792,8 @@ public:
AstNodeExpr* disableExprp = nullptr, bool negated = false,
AstNodeExpr** outMatchpp = nullptr, AstVar* disableCntVarp = nullptr,
AstVar* snapshotVarp = nullptr,
std::vector<AstNodeExpr*>* outRequiredStepSrcsp = nullptr) {
std::vector<AstNodeExpr*>* outRequiredStepSrcsp = nullptr,
std::vector<AstNodeExpr*>* outPerMidSrcsp = nullptr) {
const std::string baseName = m_names.get("");
// Number vertices with sequential colors for array indexing.
@ -1734,7 +1879,7 @@ public:
emitAndCombinerDoneLatchNba(c);
// Phase 3/3a/3b: Compute terminal match/reject signals (cleans up stateSig).
const SignalSet sigs = computeSignals(c, outRequiredStepSrcsp);
const SignalSet sigs = computeSignals(c, outRequiredStepSrcsp, outPerMidSrcsp);
AstNodeExpr* const resultp = assembleResult(
flp, isCover, negated, matchCondp, sigs.terminalActivep, sigs.rejectBasep,
@ -1765,7 +1910,10 @@ class AssertNfaVisitor final : public VNVisitor {
// Wire match vertex and mid-window sources for a successful NFA build.
static void wireMatchAndMidSources(SvaGraph& graph, const BuildResult& result, FileLine* flp) {
graph.createMatchVertex();
graph.addLink(result.termVertexp, graph.m_matchVertexp);
// Skip the main term Link when midSources already cover every
// end-of-match (cover_sequence path); otherwise the per-mid extraction
// double-counts via the merge vertex.
if (!result.termIsMidMerge) { graph.addLink(result.termVertexp, graph.m_matchVertexp); }
for (SvaStateVertex* srcVtxp : result.midSources) {
AstNodeExpr* condp = nullptr;
for (AstNodeExpr* const tc : srcVtxp->m_throughoutConds) {
@ -2234,9 +2382,11 @@ class AssertNfaVisitor final : public VNVisitor {
FileLine* const flp = assertp->fileline();
const bool isCover = VN_IS(assertp, Cover);
AstCover* const coverp = VN_CAST(assertp, Cover);
const bool isCoverSeq = coverp && coverp->isCoverSeq();
SvaGraph graph;
SvaNfaBuilder builder{graph, m_modp, m_propTempNames};
SvaNfaBuilder builder{graph, m_modp, m_propTempNames, isCoverSeq};
const BuildResult result = buildAssertionGraph(builder, graph, seqBodyp, parts, flp);
if (result.valid()) wireMatchAndMidSources(graph, result, flp);
@ -2268,12 +2418,18 @@ class AssertNfaVisitor final : public VNVisitor {
= !isCover && !parts.hasImplication && assertWithFailp && assertWithFailp->failsp();
std::vector<AstNodeExpr*> requiredStepSrcs;
// For `cover sequence` (IEEE 1800-2023 16.14.3) collect per-edge match
// signals so each end-of-match fires the action independently, rather
// than getting OR-folded into a single per-cycle terminalActive.
// coverp / isCoverSeq are computed earlier (passed to SvaNfaBuilder).
std::vector<AstNodeExpr*> perMidSrcs;
AstNodeExpr* const alwaysTriggerp = new AstConst{flp, AstConst::BitTrue{}};
AstNodeExpr* const outputExprp
= m_loweringp->lower(flp, graph, alwaysTriggerp, senTreep, result.finalCondp, isCover,
disableExprp ? disableExprp->cloneTreePure(false) : nullptr,
negated, needMatch ? &matchExprp : nullptr, disableCntVarp,
snapshotVarp, needPerSrcFail ? &requiredStepSrcs : nullptr);
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);
AstSenTree* const perSrcSenTreep
= (requiredStepSrcs.size() >= 2) ? senTreep->cloneTree(false) : nullptr;
@ -2286,9 +2442,38 @@ class AssertNfaVisitor final : public VNVisitor {
attachMatchHandlers(flp, assertAssertp, assertWithFailp, needMatch ? matchExprp : nullptr,
perSrcSenTreep, requiredStepSrcs);
AstNode* const innerPropp = propSpecp->propp();
innerPropp->replaceWith(outputExprp);
VL_DO_DANGLING(pushDeletep(innerPropp), innerPropp);
if (isCoverSeq && perMidSrcs.size() > 1) {
// Clone AstCover (N-1) times, each gated by its own per-mid signal.
// V3Assert sees N independent covers and emits N `if (cond_i) {coverinc;
// userAction}` bodies; the shared AstCoverDecl bucket is incremented
// per fire, matching IEEE "executed each time the sequence matches."
// Clones reuse AstCover->propp's original SVA tree, but we overwrite
// each clone's inner propp with the corresponding per-mid signal
// BEFORE the next iterator step, so hasMultiCycleExpr() returns false
// and processAssertion skips them on revisit.
std::vector<AstCover*> coverList;
coverList.push_back(coverp);
for (size_t i = 1; i < perMidSrcs.size(); ++i) {
AstCover* const clonep = coverp->cloneTree(false);
coverp->addNextHere(clonep);
coverList.push_back(clonep);
}
for (size_t i = 0; i < perMidSrcs.size(); ++i) {
AstPropSpec* const clonePropSpecp = VN_CAST(coverList[i]->propp(), PropSpec);
AstNode* const innerp = clonePropSpecp->propp();
innerp->replaceWith(perMidSrcs[i]);
VL_DO_DANGLING(pushDeletep(innerp), innerp);
}
// Discard the OR-collapsed fallback signal -- cover_sequence path
// does not use it.
VL_DO_DANGLING(outputExprp->deleteTree(), outputExprp);
} else {
AstNode* const innerPropp = propSpecp->propp();
innerPropp->replaceWith(outputExprp);
VL_DO_DANGLING(pushDeletep(innerPropp), innerPropp);
// If we collected per-mid (N==1) but didn't clone, drop the spare.
for (AstNodeExpr* const sp : perMidSrcs) pushDeletep(sp);
}
UINFO(4, "NFA converted assertion at " << flp << endl);
}
@ -2301,7 +2486,7 @@ class AssertNfaVisitor final : public VNVisitor {
VL_RESTORER(m_defaultDisablep);
m_modp = nodep;
m_defaultClockingp = nullptr;
m_defaultDisablep = nullptr;
m_defaultDisablep = nodep->defaultDisablep();
SvaNfaLowering lowering{nodep};
m_loweringp = &lowering;
iterateChildren(nodep);
@ -2310,9 +2495,12 @@ class AssertNfaVisitor final : public VNVisitor {
if (nodep->isDefault() && !m_defaultClockingp) m_defaultClockingp = nodep;
iterateChildren(nodep);
}
void visit(AstDefaultDisable* nodep) override {
if (!m_defaultDisablep) m_defaultDisablep = nodep;
void visit(AstGenBlock* nodep) override {
VL_RESTORER(m_defaultDisablep);
m_defaultDisablep = nodep->defaultDisablep();
iterateChildren(nodep);
}
void visit(AstDefaultDisable* nodep) override {}
void visit(AstAssert* nodep) override { processAssertion(nodep); }
void visit(AstCover* nodep) override { processAssertion(nodep); }
void visit(AstRestrict* nodep) override {

View File

@ -23,6 +23,7 @@
#include "V3AssertPre.h"
#include "V3Assert.h"
#include "V3Const.h"
#include "V3Task.h"
#include "V3UniqueNames.h"
@ -1438,12 +1439,6 @@ private:
}
void visit(AstDefaultDisable* nodep) override {
if (m_defaultDisablep) {
nodep->v3error("Only one 'default disable iff' allowed per module"
" (IEEE 1800-2023 16.15)");
} else {
m_defaultDisablep = nodep;
}
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
void visit(AstInferredDisable* nodep) override {
@ -1565,10 +1560,15 @@ private:
VL_RESTORER(m_modp);
m_defaultClockingp = nullptr;
m_defaultClkEvtVarp = nullptr;
m_defaultDisablep = nullptr;
m_defaultDisablep = nodep->defaultDisablep();
m_modp = nodep;
iterateChildren(nodep);
}
void visit(AstGenBlock* nodep) override {
VL_RESTORER(m_defaultDisablep);
m_defaultDisablep = nodep->defaultDisablep();
iterateChildren(nodep);
}
void visit(AstProperty* nodep) override {
// The body will be visited when will be substituted in place of property reference
// (AstFuncRef)

View File

@ -342,7 +342,6 @@ public:
VAR_PUBLIC_FLAT, // V3LinkParse moves to AstVar::sigPublic
VAR_PUBLIC_FLAT_RD, // V3LinkParse moves to AstVar::sigPublic
VAR_PUBLIC_FLAT_RW, // V3LinkParse moves to AstVar::sigPublic
VAR_ISOLATE_ASSIGNMENTS, // V3LinkParse moves to AstVar::attrIsolateAssign
VAR_SC_BIGUINT, // V3LinkParse moves to AstVar::attrScBigUint
VAR_SC_BV, // V3LinkParse moves to AstVar::attrScBv
VAR_SFORMAT, // V3LinkParse moves to AstVar::attrSFormat
@ -364,7 +363,7 @@ public:
"TYPEID", "TYPENAME",
"VAR_BASE", "VAR_FORCEABLE", "VAR_FSM_ARC_INCLUDE_COND", "VAR_FSM_RESET_ARC",
"VAR_FSM_STATE", "VAR_PORT_DTYPE", "VAR_PUBLIC", "VAR_PUBLIC_FLAT",
"VAR_PUBLIC_FLAT_RD", "VAR_PUBLIC_FLAT_RW", "VAR_ISOLATE_ASSIGNMENTS",
"VAR_PUBLIC_FLAT_RD", "VAR_PUBLIC_FLAT_RW",
"VAR_SC_BIGUINT", "VAR_SC_BV", "VAR_SFORMAT", "VAR_SPLIT_VAR"
};
// clang-format on
@ -1191,6 +1190,19 @@ public:
= {"user", "array", "auto", "ignore", "illegal", "default", "wildcard", "transition"};
return names[m_e];
}
// VlCovBinKind enumerator naming the bin's set
const char* binSetEnum() const {
switch (m_e) {
case BINS_IGNORE: return "VlCovBinKind::KIND_IGNORE";
case BINS_ILLEGAL: return "VlCovBinKind::KIND_ILLEGAL";
case BINS_DEFAULT: return "VlCovBinKind::KIND_DEFAULT";
default: return "VlCovBinKind::KIND_NORMAL";
}
}
// Normal bins (feed coverage) are anything but ignore/illegal/default
bool binIsNormal() const {
return m_e != BINS_IGNORE && m_e != BINS_ILLEGAL && m_e != BINS_DEFAULT;
}
};
constexpr bool operator==(const VCoverBinsType& lhs, VCoverBinsType::en rhs) {
return lhs.m_e == rhs;

View File

@ -170,6 +170,10 @@ public:
void generic(bool flag) { m_generic = flag; }
std::pair<uint32_t, uint32_t> dimensions(bool includeBasic) const;
uint32_t arrayUnpackedElements() const; // 1, or total multiplication of all dimensions
// Fixed aggregate streaming properties
bool isStreamableFixedAggregate() const;
bool containsUnpackedStruct() const;
int widthStream() const;
static int uniqueNumInc() { return ++s_uniqueNum; }
const char* charIQWN() const {
return (isString() ? "N" : isWide() ? "W" : isDouble() ? "D" : isQuad() ? "Q" : "I");

View File

@ -97,7 +97,6 @@ class AstNodeFTask VL_NOT_FINAL : public AstNode {
string m_ifacePortName; // Interface port name for out-of-block definition (IEEE 25.8)
uint64_t m_dpiOpenParent = 0; // DPI import open array, if !=0, how many callees
bool m_taskPublic : 1; // Public task
bool m_attrIsolateAssign : 1; // User isolate_assignments attribute
bool m_classMethod : 1; // Class method
bool m_didProto : 1; // Did prototype processing
bool m_prototype : 1; // Just a prototype
@ -130,7 +129,6 @@ protected:
: AstNode{t, fl}
, m_name{name}
, m_taskPublic{false}
, m_attrIsolateAssign{false}
, m_classMethod{false}
, m_didProto{false}
, m_prototype{false}
@ -179,8 +177,6 @@ public:
uint64_t dpiOpenParent() const { return m_dpiOpenParent; }
bool taskPublic() const { return m_taskPublic; }
void taskPublic(bool flag) { m_taskPublic = flag; }
bool attrIsolateAssign() const { return m_attrIsolateAssign; }
void attrIsolateAssign(bool flag) { m_attrIsolateAssign = flag; }
bool classMethod() const { return m_classMethod; }
void classMethod(bool flag) { m_classMethod = flag; }
bool didProto() const { return m_didProto; }
@ -301,6 +297,7 @@ class AstNodeModule VL_NOT_FINAL : public AstNode {
VLifetime m_lifetime; // Lifetime
VTimescale m_timeunit; // Global time unit
VOptionBool m_unconnectedDrive; // State of `unconnected_drive
AstDefaultDisable* m_defaultDisablep = nullptr; // Default disable iff in this scope
bool m_modPublic : 1; // Module has public references
bool m_modTrace : 1; // Tracing this module
@ -351,6 +348,8 @@ public:
string origName() const override { return m_origName; }
string someInstanceName() const VL_MT_SAFE { return m_someInstanceName; }
void someInstanceName(const string& name) { m_someInstanceName = name; }
AstDefaultDisable* defaultDisablep() const { return m_defaultDisablep; }
void defaultDisablep(AstDefaultDisable* nodep) { m_defaultDisablep = nodep; }
bool inLibrary() const { return m_inLibrary; }
void inLibrary(bool flag) { m_inLibrary = flag; }
void depth(int value) { m_depth = value; }
@ -987,6 +986,7 @@ public:
bool maybePointedTo() const override VL_MT_SAFE { return true; }
void cloneRelink() override { V3ERROR_NA; } // Not cloneable
AstModule* modp() const { return m_modp; }
AstScope* scopep() const { return m_scopep; }
// Find a table (unpacked array) within the constant pool which is initialized with the
// given value, or create one if one does not already exists. The returned VarScope *might*
@ -2130,7 +2130,6 @@ class AstVar final : public AstNode {
bool m_funcReturn : 1; // Return variable for a function
bool m_attrScBv : 1; // User force bit vector attribute
bool m_attrScBigUint : 1; // User force sc_biguint attribute
bool m_attrIsolateAssign : 1; // User isolate_assignments attribute
bool m_attrSFormat : 1; // User sformat attribute
bool m_attrSplitVar : 1; // declared with split_var metacomment
bool m_attrFsmState : 1; // declared with fsm_state metacomment
@ -2139,6 +2138,7 @@ class AstVar final : public AstNode {
bool m_attrFsmArcInclCond : 1; // declared with fsm_arc_include_cond metacomment
bool m_fileDescr : 1; // File descriptor
bool m_gotNansiType : 1; // Linker saw Non-ANSI type declaration
bool m_icoMaybeWritten : 1; // Design might write this input signal - for ico change detect
bool m_isConst : 1; // Table contains constant data
bool m_isContinuously : 1; // Ever assigned continuously (for force/release)
bool m_hasStrengthAssignment : 1; // Is on LHS of assignment with strength specifier
@ -2193,7 +2193,6 @@ class AstVar final : public AstNode {
m_funcReturn = false;
m_attrScBv = false;
m_attrScBigUint = false;
m_attrIsolateAssign = false;
m_attrSFormat = false;
m_attrSplitVar = false;
m_attrFsmState = false;
@ -2202,6 +2201,7 @@ class AstVar final : public AstNode {
m_attrFsmArcInclCond = false;
m_fileDescr = false;
m_gotNansiType = false;
m_icoMaybeWritten = false;
m_isConst = false;
m_isContinuously = false;
m_hasStrengthAssignment = false;
@ -2342,7 +2342,6 @@ public:
void attrFileDescr(bool flag) { m_fileDescr = flag; }
void attrScBv(bool flag) { m_attrScBv = flag; }
void attrScBigUint(bool flag) { m_attrScBigUint = flag; }
void attrIsolateAssign(bool flag) { m_attrIsolateAssign = flag; }
void attrSFormat(bool flag) { m_attrSFormat = flag; }
void attrSplitVar(bool flag) { m_attrSplitVar = flag; }
void attrFsmState(bool flag) { m_attrFsmState = flag; }
@ -2382,6 +2381,8 @@ public:
void hasStrengthAssignment(bool flag) { m_hasStrengthAssignment = flag; }
bool hasUserInit() const { return m_hasUserInit; }
void hasUserInit(bool flag) { m_hasUserInit = flag; }
void icoMaybeWritten(bool flag) { m_icoMaybeWritten = flag; }
bool icoMaybeWritten() const { return m_icoMaybeWritten; }
bool isDpiOpenArray() const VL_MT_SAFE { return m_isDpiOpenArray; }
void isDpiOpenArray(bool flag) { m_isDpiOpenArray = flag; }
bool isHideLocal() const { return m_isHideLocal; }
@ -2461,12 +2462,6 @@ public:
bool isWor() const { return varType().isWor(); }
bool isWiredNet() const { return varType().isWiredNet(); }
bool isTemp() const { return varType().isTemp(); }
bool isToggleCoverable() const {
return ((isIO() || isSignal())
&& (isIO() || isBitLogic())
// Wrapper would otherwise duplicate wrapped module's coverage
&& !isSc() && !isPrimaryIO() && !isConst() && !isDouble() && !isString());
}
bool isClassMember() const { return varType() == VVarType::MEMBER; }
bool isVirtIface() const {
if (AstIfaceRefDType* const dtp = VN_CAST(dtypep(), IfaceRefDType)) {
@ -2518,7 +2513,6 @@ public:
bool attrFsmRegisterWrapper() const { return m_attrFsmRegisterWrapper; }
bool attrFsmResetArc() const { return m_attrFsmResetArc; }
bool attrFsmArcInclCond() const { return m_attrFsmArcInclCond; }
bool attrIsolateAssign() const { return m_attrIsolateAssign; }
AstIface* sensIfacep() const { return m_sensIfacep; }
VRandAttr rand() const { return m_rand; }
string verilogKwd() const override;
@ -2530,7 +2524,6 @@ public:
// This is getting connected to fromp; keep attributes
// Note the method below too
if (fromp->attrFileDescr()) attrFileDescr(true);
if (fromp->attrIsolateAssign()) attrIsolateAssign(true);
if (fromp->isContinuously()) isContinuously(true);
}
void propagateWrapAttrFrom(const AstVar* fromp) {
@ -2809,6 +2802,7 @@ class AstGenBlock final : public AstNodeGen {
std::string m_name; // Name of block
const bool m_unnamed; // Originally unnamed (name change does not affect this)
const bool m_implied; // Not inserted by user
AstDefaultDisable* m_defaultDisablep = nullptr; // Default disable iff in this scope
public:
AstGenBlock(FileLine* fl, const string& name, AstNode* itemsp, bool implied)
@ -2825,6 +2819,8 @@ public:
void name(const std::string& name) override { m_name = name; }
bool unnamed() const { return m_unnamed; }
bool implied() const { return m_implied; }
AstDefaultDisable* defaultDisablep() const { return m_defaultDisablep; }
void defaultDisablep(AstDefaultDisable* nodep) { m_defaultDisablep = nodep; }
};
class AstGenCase final : public AstNodeGen {
// Generate 'case'

View File

@ -59,6 +59,7 @@ protected:
public:
ASTGEN_MEMBERS_AstNodeAssign;
// Clone single node, just get same type back.
void dump(std::ostream& str) const override;
virtual AstNodeAssign* cloneType(AstNodeExpr* lhsp, AstNodeExpr* rhsp) = 0;
bool hasDType() const override VL_MT_SAFE { return true; }
virtual bool cleanRhs() const { return true; }
@ -1606,12 +1607,18 @@ public:
};
class AstCover final : public AstNodeCoverOrAssert {
// @astgen op3 := coverincsp: List[AstNode] // Coverage node
bool m_isCoverSeq = false; // 'cover sequence' (IEEE 1800-2023 16.14.3): fires per
// end-of-match, not per property success
public:
ASTGEN_MEMBERS_AstCover;
AstCover(FileLine* fl, AstNode* propp, AstNode* stmtsp, VAssertType type,
const string& name = "")
: ASTGEN_SUPER_Cover(fl, propp, stmtsp, type, VAssertDirectiveType::COVER, name) {}
string verilogKwd() const override { return "cover"; }
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
bool isCoverSeq() const { return m_isCoverSeq; }
void isCoverSeq(bool flag) { m_isCoverSeq = flag; }
};
class AstRestrict final : public AstNodeCoverOrAssert {
public:

View File

@ -630,6 +630,7 @@ void AstVar::combineType(const AstVar* otherp) {
varType(otherp->varType());
direction(otherp->direction());
}
if (otherp->icoMaybeWritten()) icoMaybeWritten(true);
}
void AstVar::combineType(VVarType type) {
// These flags get combined with the existing settings of the flags.
@ -1259,6 +1260,47 @@ uint32_t AstNodeDType::arrayUnpackedElements() const {
return entries;
}
bool AstNodeDType::isStreamableFixedAggregate() const {
const AstNodeDType* const dtypep = skipRefp();
if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) {
return adtypep->subDTypep()->isStreamableFixedAggregate();
} else if (const AstNodeUOrStructDType* const sdtypep = VN_CAST(dtypep, NodeUOrStructDType)) {
if (sdtypep->packed()) return true;
if (!VN_IS(sdtypep, StructDType)) return false;
for (const AstMemberDType* itemp = sdtypep->membersp(); itemp;
itemp = VN_AS(itemp->nextp(), MemberDType)) {
if (!itemp->dtypep()->isStreamableFixedAggregate()) return false;
}
return true;
}
return dtypep->isIntegralOrPacked() || dtypep->isDouble();
}
bool AstNodeDType::containsUnpackedStruct() const {
const AstNodeDType* const dtypep = skipRefp();
if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) {
return adtypep->subDTypep()->containsUnpackedStruct();
}
const AstStructDType* const sdtypep = VN_CAST(dtypep, StructDType);
return sdtypep && !sdtypep->packed();
}
int AstNodeDType::widthStream() const {
const AstNodeDType* const dtypep = skipRefp();
if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) {
return adtypep->subDTypep()->widthStream() * adtypep->elementsConst();
} else if (const AstNodeUOrStructDType* const sdtypep = VN_CAST(dtypep, NodeUOrStructDType)) {
if (!VN_IS(sdtypep, StructDType) || sdtypep->packed()) return width();
int width = 0;
for (const AstMemberDType* itemp = sdtypep->membersp(); itemp;
itemp = VN_AS(itemp->nextp(), MemberDType)) {
width += itemp->dtypep()->widthStream();
}
return width;
}
return dtypep->width();
}
std::pair<uint32_t, uint32_t> AstNodeDType::dimensions(bool includeBasic) const {
// How many array dimensions (packed,unpacked) does this Var have?
uint32_t packed = 0;
@ -1322,6 +1364,12 @@ AstNode* AstArraySel::baseFromp(AstNode* nodep, bool overMembers) {
} else if (VN_IS(nodep, Sel)) {
nodep = VN_AS(nodep, Sel)->fromp();
continue;
} else if (VN_IS(nodep, AssocSel)) {
nodep = VN_AS(nodep, AssocSel)->fromp();
continue;
} else if (VN_IS(nodep, WildcardSel)) {
nodep = VN_AS(nodep, WildcardSel)->fromp();
continue;
} else if (overMembers && VN_IS(nodep, MemberSel)) {
nodep = VN_AS(nodep, MemberSel)->fromp();
continue;
@ -2133,6 +2181,14 @@ void AstNodeCoverOrAssert::dumpJson(std::ostream& str) const {
dumpJsonBoolFuncIf(str, immediate);
dumpJsonBoolFuncIf(str, senFromAlways);
}
void AstCover::dump(std::ostream& str) const {
this->AstNodeCoverOrAssert::dump(str);
if (isCoverSeq()) str << " [COVERSEQ]";
}
void AstCover::dumpJson(std::ostream& str) const {
dumpJsonBoolFuncIf(str, isCoverSeq);
this->AstNodeCoverOrAssert::dumpJson(str);
}
void AstClocking::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (isDefault()) str << " [DEFAULT]";
@ -2724,6 +2780,10 @@ void AstNodeArrayDType::dumpJson(std::ostream& str) const {
dumpJsonStr(str, "declRange", cvtToStr(declRange()));
dumpJsonGen(str);
}
void AstNodeAssign::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (timingControlp()) str << " [TIMING=" << nodeAddr(timingControlp()) << "]";
}
string AstPackArrayDType::prettyDTypeName(bool full) const {
std::ostringstream os;
if (const auto subp = subDTypep()) os << subp->prettyDTypeName(full);
@ -3158,7 +3218,6 @@ void AstVar::dump(std::ostream& str) const {
if (noReset()) str << " [!RST]";
if (processQueue()) str << " [PROCQ]";
if (sampled()) str << " [SAMPLED]";
if (attrIsolateAssign()) str << " [aISO]";
if (attrFsmState()) str << " [aFSMSTATE]";
if (attrFsmResetArc()) str << " [aFSMRESETARC]";
if (attrFsmArcInclCond()) str << " [aFSMARCCOND]";
@ -3169,6 +3228,7 @@ void AstVar::dump(std::ostream& str) const {
str << " [FUNC]";
}
if (hasUserInit()) str << " [UINIT]";
if (icoMaybeWritten()) str << " [ICOMAYBEWRITTEN]";
if (isDpiOpenArray()) str << " [DPIOPENA]";
if (ignorePostWrite()) str << " [IGNPWR]";
if (ignoreSchedWrite()) str << " [IGNWR]";
@ -3193,11 +3253,11 @@ void AstVar::dumpJson(std::ostream& str) const {
dumpJsonBoolFuncIf(str, noReset);
dumpJsonBoolFuncIf(str, processQueue);
dumpJsonBoolFuncIf(str, sampled);
dumpJsonBoolFuncIf(str, attrIsolateAssign);
dumpJsonBoolFuncIf(str, attrFsmState);
dumpJsonBoolFuncIf(str, attrFsmResetArc);
dumpJsonBoolFuncIf(str, attrFsmArcInclCond);
dumpJsonBoolFuncIf(str, attrFileDescr);
dumpJsonBoolFuncIf(str, icoMaybeWritten);
dumpJsonBoolFuncIf(str, isDpiOpenArray);
dumpJsonBoolFuncIf(str, isFuncReturn);
dumpJsonBoolFuncIf(str, isFuncLocal);

View File

@ -61,7 +61,6 @@ class BeginVisitor final : public VNVisitor {
// NODE STATE
// AstCase::user1 -> bool, if already purified
V3UniqueNames m_caseTempNames; // For generating unique temporary variable names used by cases
// STATE - across all visitors
BeginState* const m_statep; // Current global state
@ -75,7 +74,6 @@ class BeginVisitor final : public VNVisitor {
string m_unnamedScope; // Name of begin blocks, including unnamed blocks
int m_ifDepth = 0; // Current if depth
bool m_keepBegins = false; // True if begins should not be inlined
VDouble0 m_statPurifiedCaseExpr; // Count of purified case expressions
// METHODS
@ -132,7 +130,6 @@ class BeginVisitor final : public VNVisitor {
}
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_modp);
VL_RESTORER(m_caseTempNames);
m_modp = nodep;
// Rename it (e.g. class under a generate)
if (m_unnamedScope != "") {
@ -171,7 +168,6 @@ class BeginVisitor final : public VNVisitor {
VL_RESTORER(m_liftedp);
VL_RESTORER(m_namedScope);
VL_RESTORER(m_unnamedScope);
VL_RESTORER(m_caseTempNames);
m_displayScope = dot(m_displayScope, nodep->name());
m_namedScope = "";
m_unnamedScope = "";
@ -415,29 +411,7 @@ class BeginVisitor final : public VNVisitor {
// any BEGINs, but V3Coverage adds them all under the module itself.
iterateChildren(nodep);
}
void visit(AstCase* nodep) override {
// Introduce temporary variable for AstCase if needed - it is done here and not in V3Case
// because this phase is before V3Scope and V3Case is not. Doing it before V3Scope ensures
// that V3Scope will take care of a scope creation
if (!nodep->exprp()->isPure() && !nodep->user1SetOnce()) {
++m_statPurifiedCaseExpr;
FileLine* const fl = nodep->exprp()->fileline();
AstVar* const varp = new AstVar{fl, VVarType::XTEMP, m_caseTempNames.get(nodep),
nodep->exprp()->dtypep()};
nodep->exprp(new AstExprStmt{fl,
new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE},
nodep->exprp()->unlinkFrBack()},
new AstVarRef{fl, varp, VAccess::READ}});
if (m_ftaskp) {
varp->funcLocal(true);
varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
m_ftaskp->stmtsp()->addHereThisAsNext(varp);
} else {
m_modp->stmtsp()->addHereThisAsNext(varp);
}
}
iterateChildren(nodep);
}
void visit(AstCase* nodep) override { iterateChildren(nodep); }
// VISITORS - LINT CHECK
void visit(AstIf* nodep) override { // not AstNodeIf; other types not covered
VL_RESTORER(m_keepBegins);
@ -464,10 +438,8 @@ class BeginVisitor final : public VNVisitor {
public:
// CONSTRUCTORS
BeginVisitor(AstNetlist* nodep, BeginState* statep)
: m_caseTempNames{"__VCase"}
, m_statep{statep} {
: m_statep{statep} {
iterate(nodep);
V3Stats::addStatSum("Impure case expressions", m_statPurifiedCaseExpr);
}
~BeginVisitor() override = default;
};

View File

@ -42,10 +42,6 @@
VL_DEFINE_DEBUG_FUNCTIONS;
#define CASE_OVERLAP_WIDTH 16 // Maximum width we can check for overlaps in
#define CASE_BARF 999999 // Magic width when non-constant
#define CASE_ENCODER_GROUP_DEPTH 8 // Levels of priority to be ORed together in top IF tree
//######################################################################
class CaseLintVisitor final : public VNVisitorConst {
@ -119,252 +115,302 @@ class CaseLintVisitor final : public VNVisitorConst {
}
void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
public:
// CONSTRUCTORS
explicit CaseLintVisitor(AstCase* nodep) { iterateConst(nodep); }
explicit CaseLintVisitor(AstGenCase* nodep) { iterateConst(nodep); }
~CaseLintVisitor() override = default;
public:
static void apply(AstCase* nodep) { CaseLintVisitor{nodep}; }
static void apply(AstGenCase* nodep) { CaseLintVisitor{nodep}; }
};
//######################################################################
// Case state, as a visitor of each AstNode
class CaseVisitor final : public VNVisitor {
// NODE STATE
// Cleared each Case
// AstIf::user3() -> bool. Set true to indicate clone not needed
const VNUser3InUse m_inuser3;
// Maximum width for detailed analysis
constexpr static int CASE_DETAILS_MAX_WIDTH = 16;
// Levels of priority to be ORed together in top IF tree
constexpr static int CASE_ENCODER_GROUP_DEPTH = 8;
// TYPES
// Record for each case value
struct CaseRecord final {
AstCaseItem* itemp; // Case item that covers value
AstConst* constp; // Expression within 'itemp' that covers value (nullptr for default)
AstNode* stmtsp; // Statements of 'itemp' (might be nullptr if case is empty)
};
// STATE
VDouble0 m_statCaseFast; // Statistic tracking
VDouble0 m_statCaseSlow; // Statistic tracking
// Statistics tracking, as a struct so can be passed to 'const' methods
struct Stats final {
VDouble0 caseFast; // Cases using fast bit tree method
VDouble0 caseGeneric; // Cases using generic if/else tree method
VDouble0 provenAssertions; // Assertions proven to hold
} m_stats;
const AstNode* m_alwaysp = nullptr; // Always in which case is located
// Per-CASE
int m_caseWidth = 0; // Width of valueItems
int m_caseItems = 0; // Number of caseItem unique values
bool m_caseIncomplete = false; // Proven incomplete
bool m_caseNoOverlapsAllCovered = false; // Proven to be synopsys parallel_case compliant
// For each possible value, the case branch we need
std::array<AstNode*, 1 << CASE_OVERLAP_WIDTH> m_valueItem;
bool m_needToClearCache = false; // Whether cache needs to be cleared
// STATE - per AstCase. Update by 'analyzeCase', treat 'const' otherwise
bool m_caseOpaque = false; // Case statement is opaque (non-packed, or non-const conditions)
size_t m_caseNConditions = 0; // Number of conditions in the case statement
bool m_caseDetailsValid = false; // Indicates m_caseDetails is valid
struct final {
bool exhaustive = false; // Proven exhaustive
bool noOverlaps = false; // Proven no overlaps between cases
// Map from value (index) to the CaseRecord that covers this value
std::array<CaseRecord, 1U << CASE_DETAILS_MAX_WIDTH> records;
} m_caseDetails;
// METHODS
//! Determine whether we should check case items are complete
//! @return Enum's dtype if should check, nullptr if shouldn't
const AstEnumDType* getEnumCompletionCheckDType(const AstCase* const nodep) {
// Xs in case or casez are impossible due to two state simulations.
// Returns true if the item is never possible
static bool neverItem(const AstCase* casep, const AstNodeExpr* itemExprp) {
const AstConst* const constp = VN_CAST(itemExprp, Const);
if (!constp) return false;
if (casep->casex() || casep->caseInside()) return false;
if (casep->casez()) return constp->num().isAnyX();
return constp->num().isFourState();
}
// Determine whether we should check case items are complete
// Returns enum's dtype if should check, nullptr if shouldn't
static const AstEnumDType* getEnumCompletionCheckDType(const AstCase* const nodep) {
if (!nodep->uniquePragma() && !nodep->unique0Pragma()) return nullptr;
const AstEnumDType* const enumDtp
= VN_CAST(nodep->exprp()->dtypep()->skipRefToEnump(), EnumDType);
if (!enumDtp) return nullptr; // Case isn't enum
const AstBasicDType* const basicp = enumDtp->subDTypep()->basicp();
if (!basicp) return nullptr; // Not simple type (perhaps IEEE illegal)
if (basicp->width() > 32) return nullptr;
return enumDtp;
}
//! @return True if case items are complete, false if there are uncovered enums
bool checkCaseEnumComplete(const AstCase* const nodep, const AstEnumDType* const dtype) {
const uint32_t numCases = 1UL << m_caseWidth;
for (AstEnumItem* itemp = dtype->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), EnumItem)) {
AstConst* const econstp = VN_AS(itemp->valuep(), Const);
V3Number nummask{itemp, econstp->width()};
// Check and warn if case items are not complete over the given enum type.
// Returns true iff the case items cover all enum values/patterns.
bool checkExhaustiveEnum(const AstCase* const nodep, const AstEnumDType* const enump) {
const uint32_t numCases = 1UL << nodep->exprp()->width();
for (AstEnumItem* eip = enump->itemsp(); eip; eip = VN_AS(eip->nextp(), EnumItem)) {
AstConst* const econstp = VN_AS(eip->valuep(), Const);
V3Number nummask{eip, econstp->width()};
nummask.opBitsNonX(econstp->num());
const uint32_t mask = nummask.toUInt();
V3Number numval{itemp, econstp->width()};
V3Number numval{eip, econstp->width()};
numval.opBitsOne(econstp->num());
const uint32_t mask = nummask.toUInt();
const uint32_t val = numval.toUInt();
// Check all cases to see if they cover this enum value/pattern
for (uint32_t i = 0; i < numCases; ++i) {
if ((i & mask) == val) {
if (!m_valueItem[i]) {
if (!nodep->unique0Pragma())
nodep->v3warn(CASEINCOMPLETE, "Enum item "
<< itemp->prettyNameQ()
<< " not covered by case\n");
m_caseIncomplete = true;
return false; // enum has uncovered value by case items
}
if ((i & mask) != val) continue; // This case is not for this enum value
if (m_caseDetails.records[i].itemp) continue; // Covered case
// Warn unless unique0 case which allows no-match
if (!nodep->unique0Pragma()) {
nodep->v3warn(CASEINCOMPLETE,
"Enum item " << eip->prettyNameQ() << " not covered by case");
}
// TODO: warn for all uncovered enum values, not just the first
return false; // enum has uncovered value by case items
}
}
return true; // enum is fully covered
}
bool isCaseTreeFast(AstCase* nodep) {
int width = 0;
bool opaque = false;
m_caseItems = 0;
m_caseNoOverlapsAllCovered = true;
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), CaseItem)) {
for (AstNode* icondp = itemp->condsp(); icondp; icondp = icondp->nextp()) {
if (icondp->width() > width) width = icondp->width();
if (icondp->isDouble()) opaque = true;
if (!VN_IS(icondp, Const)) width = CASE_BARF; // Can't parse; not a constant
m_caseItems++;
// Check and warn if case items are not complete over all possible values.
// Returns true iff the case items cover all values of the case expression.
bool checkExhaustivePacked(AstCase* nodep) {
const uint32_t numCases = 1UL << nodep->exprp()->width();
for (uint32_t i = 0; i < numCases; ++i) {
if (m_caseDetails.records[i].itemp) continue; // Covered case
if (!nodep->unique0Pragma()) {
nodep->v3warn(CASEINCOMPLETE,
"Case values incompletely covered (example pattern 0x" << std::hex
<< i << ")");
}
// TODO: warn for more than one uncovered case, not just the first
return false;
}
m_caseWidth = width;
if (width == 0 || width > CASE_OVERLAP_WIDTH || opaque) {
m_caseNoOverlapsAllCovered = false;
return false; // Too wide for analysis
// It's an exhaustive case statement
return true;
}
bool checkExhaustive(AstCase* nodep) {
if (const AstEnumDType* const enump = getEnumCompletionCheckDType(nodep)) {
return checkExhaustiveEnum(nodep, enump);
}
return checkExhaustivePacked(nodep);
}
// Analyze each value in the case statement. Updates 'm_caseDetails' and issues warnings.
void analyzeCaseDetails(AstCase* nodep) {
const uint32_t numValues = 1UL << nodep->exprp()->width();
// Clear case records
for (uint32_t i = 0; i < numValues; ++i) {
m_caseDetails.records[i].itemp = nullptr;
m_caseDetails.records[i].constp = nullptr;
m_caseDetails.records[i].stmtsp = nullptr;
}
UINFO(8, "Simple case statement: " << nodep);
const uint32_t numCases = 1UL << m_caseWidth;
// Zero list of items for each value
for (uint32_t i = 0; i < numCases; ++i) m_valueItem[i] = nullptr;
// Now pick up the values for each assignment
// We can cheat and use uint32_t's because we only support narrow case's
bool reportedOverlap = false;
bool reportedSubcase = false;
bool hasDefaultCase = false;
std::map<AstNode*, AstCaseItem*> caseItemMap; // case condition -> case item
bool hasDefault = false;
m_caseDetails.noOverlaps = true;
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), CaseItem)) {
for (AstNode* icondp = itemp->condsp(); icondp; icondp = icondp->nextp()) {
// UINFOTREE(9, icondp, "", "caseitem");
AstConst* const iconstp = VN_AS(icondp, Const);
UASSERT_OBJ(iconstp, nodep, "above 'can't parse' should have caught this");
if (neverItem(nodep, iconstp)) {
// X in casez can't ever be executed
// Default case
if (itemp->isDefault()) {
// Default was moved to be the last item by V3LinkDot. Fill remaining cases
for (uint32_t i = 0; i < numValues; ++i) {
CaseRecord& caseRecord = m_caseDetails.records[i];
if (!caseRecord.itemp) {
caseRecord.itemp = itemp;
caseRecord.stmtsp = itemp->stmtsp();
}
}
hasDefault = true;
continue;
}
for (AstConst* iconstp = VN_AS(itemp->condsp(), Const); iconstp;
iconstp = VN_AS(iconstp->nextp(), Const)) {
// Some items can never match due to 2-state simulation
if (neverItem(nodep, iconstp)) continue;
V3Number nummask{itemp, iconstp->width()};
nummask.opBitsNonX(iconstp->num());
V3Number numval{itemp, iconstp->width()};
numval.opBitsOne(iconstp->num());
const uint32_t mask = nummask.toUInt();
const uint32_t val = numval.toUInt();
bool foundNewCase = false;
const AstConst* firstOverlapConstp = nullptr;
uint32_t firstOverlapValue = 0;
for (uint32_t i = 0; i < numValues; ++i) {
if ((i & mask) != val) continue;
CaseRecord& caseRecord = m_caseDetails.records[i];
// If this is the first case that covers this value, record it
if (!caseRecord.itemp) {
caseRecord.itemp = itemp;
caseRecord.constp = iconstp;
caseRecord.stmtsp = itemp->stmtsp();
foundNewCase = true;
continue;
}
// There is an overlap. If it's within the same CaseItem, it is legal
if (caseRecord.itemp == itemp) continue;
// Otherwise record the first overlapping case
if (!firstOverlapConstp) {
firstOverlapConstp = caseRecord.constp;
firstOverlapValue = i;
m_caseDetails.noOverlaps = false;
}
}
// Only report first overlap
if (reportedOverlap || !firstOverlapConstp) continue;
// Report first overlap
if (nodep->priorityPragma()) {
// If this is a priority case, we only want to complain if every possible
// value for this item is already hit by some other item. This is true if
// 'foundNewCase' is false. 'firstOverlapConstp' is null when the only
// covering item is this item itself, which is legal overlap within one
// item.
if (!foundNewCase) {
iconstp->v3warn(CASEOVERLAP,
"Case item ignored: every matching value is covered "
"by an earlier condition\n"
<< iconstp->warnContextPrimary() << '\n'
<< firstOverlapConstp->warnOther()
<< "... Location of previous condition\n"
<< firstOverlapConstp->warnContextPrimary());
reportedOverlap = true;
}
} else {
const bool isCondWildcard = iconstp->num().isAnyXZ();
V3Number nummask{itemp, iconstp->width()};
nummask.opBitsNonX(iconstp->num());
const uint32_t mask = nummask.toUInt();
V3Number numval{itemp, iconstp->width()};
numval.opBitsOne(iconstp->num());
const uint32_t val = numval.toUInt();
uint32_t firstOverlap = 0;
const AstNode* overlappedCondp = nullptr;
bool foundHit = false;
for (uint32_t i = 0; i < numCases; ++i) {
if ((i & mask) == val) {
if (!m_valueItem[i]) {
m_valueItem[i] = icondp;
caseItemMap[icondp] = itemp;
foundHit = true;
} else if (!overlappedCondp) {
// Overlapping case item expressions in the
// same case item are legal
if (caseItemMap[m_valueItem[i]] != itemp) {
firstOverlap = i;
overlappedCondp = m_valueItem[i];
m_caseNoOverlapsAllCovered = false;
}
}
}
}
if (!nodep->priorityPragma()) {
// If this case statement doesn't have the priority
// keyword, we want to warn on any overlap.
if (!reportedOverlap && overlappedCondp) {
std::ostringstream examplePattern;
if (isCondWildcard) {
examplePattern << " (example pattern 0x" << std::hex
<< firstOverlap << ")";
}
icondp->v3warn(CASEOVERLAP,
"Case conditions overlap"
<< examplePattern.str() << "\n"
<< icondp->warnContextPrimary() << '\n'
<< overlappedCondp->warnOther()
<< "... Location of overlapping condition\n"
<< overlappedCondp->warnContextSecondary());
reportedOverlap = true;
}
} else {
// If this is a priority case, we only want to complain
// if every possible value for this item is already hit
// by some other item. This is true if foundHit is
// false.
if (!reportedSubcase && !foundHit) {
icondp->v3warn(CASEOVERLAP,
"Case item ignored: every matching value is covered "
"by an earlier condition\n"
<< icondp->warnContextPrimary() << '\n'
<< overlappedCondp->warnOther()
<< "... Location of previous condition\n"
<< overlappedCondp->warnContextPrimary());
reportedSubcase = true;
}
}
}
}
// Defaults were moved to last in the caseitem list by V3LinkDot
if (itemp->isDefault()) { // Case statement's default... Fill the table
for (uint32_t i = 0; i < numCases; ++i) {
if (!m_valueItem[i]) m_valueItem[i] = itemp;
}
caseItemMap[itemp] = itemp;
hasDefaultCase = true;
}
}
if (!hasDefaultCase) {
const AstEnumDType* const dtype = getEnumCompletionCheckDType(nodep);
if (dtype) {
if (!checkCaseEnumComplete(nodep, dtype)) {
// checkCaseEnumComplete has already warned of incompletion
m_caseNoOverlapsAllCovered = false;
return false;
}
} else {
for (uint32_t i = 0; i < numCases; ++i) {
if (!m_valueItem[i]) { // has uncovered case
if (!nodep->unique0Pragma())
nodep->v3warn(CASEINCOMPLETE, "Case values incompletely covered "
"(example pattern 0x"
<< std::hex << i << ")");
m_caseIncomplete = true;
m_caseNoOverlapsAllCovered = false;
return false;
// If this case statement doesn't have the priority keyword,
// we want to warn on any overlap.
std::ostringstream examplePattern;
if (iconstp->num().isAnyXZ()) {
examplePattern << " (example pattern 0x" << std::hex << firstOverlapValue
<< ")";
}
iconstp->v3warn(CASEOVERLAP,
"Case conditions overlap"
<< examplePattern.str() << "\n"
<< iconstp->warnContextPrimary() << '\n'
<< firstOverlapConstp->warnOther()
<< "... Location of overlapping condition\n"
<< firstOverlapConstp->warnContextSecondary());
reportedOverlap = true;
}
}
}
if (m_caseItems <= 3
// Avoid e.g. priority expanders from going crazy in expansion
|| (m_caseWidth >= 8 && (m_caseItems <= (m_caseWidth + 1)))) {
return false; // Not worth simplifying
}
// Convert valueItem from AstCaseItem* to the expression
// Not done earlier, as we may now have a nullptr because it's just a ";" NOP branch
for (uint32_t i = 0; i < numCases; ++i) {
if (AstNode* const condp = m_valueItem[i]) {
const AstCaseItem* const caseItemp = caseItemMap[condp];
UASSERT_OBJ(caseItemp, condp, "caseItemp should exist");
m_valueItem[i] = caseItemp->stmtsp();
}
}
return true; // All is fine
// If there was no default, check exhaustiveness
m_caseDetails.exhaustive = hasDefault || checkExhaustive(nodep);
// Records now valid
m_caseDetailsValid = true;
}
AstNode* replaceCaseFastRecurse(AstNodeExpr* cexprp, int msb, uint32_t upperValue) {
if (msb < 0) {
// There's no space for a IF. We know upperValue is thus down to a specific
// exact value, so just return the tree value
// Note can't clone here, as we're going to check for equivalence above
AstNode* const foundp = m_valueItem[upperValue];
return foundp;
} else {
// Make left and right subtrees
// cexpr[msb:lsb] == 1
AstNode* tree0p = replaceCaseFastRecurse(cexprp, msb - 1, upperValue);
AstNode* tree1p = replaceCaseFastRecurse(
cexprp, msb - 1, upperValue | (1UL << static_cast<uint32_t>(msb)));
// Analyze case statement. Updates 'm_case*' members. Reports warnings.
void analyzeCase(AstCase* nodep) {
// Reset all analysis results
m_caseOpaque = false;
m_caseNConditions = 0;
m_caseDetailsValid = false;
if (tree0p == tree1p) {
// Same logic on both sides, so we can just return one of 'em
return tree0p;
AstNode* const caseExprp = nodep->exprp();
// Mark opaque if not a packed value - TODO: can this be a class?
if (caseExprp->isDouble() || caseExprp->isString()) m_caseOpaque = true;
// Check each condition expression
for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) {
for (AstNode* condp = cip->condsp(); condp; condp = condp->nextp()) {
// Count conditions
++m_caseNConditions;
// Mark opaque if non-constant condition
if (!VN_IS(condp, Const)) m_caseOpaque = true;
}
// We could have a "checkerboard" with A B A B, we can use the same IF on both edges
}
// Nothing else to do if not a packed type, or non-const conditions
if (m_caseOpaque) return;
// If small enough, analyse details
if (caseExprp->width() <= CASE_DETAILS_MAX_WIDTH) analyzeCaseDetails(nodep);
}
// TODO: should return AstNodeStmt after #6280
AstNode* convertCaseFastRecurse(AstNodeExpr* cexprp, int msb, uint32_t upperValue) const {
// Base case: If reached the last bit, upperValue equals an exact value, just return
// the statements from that CaseItem. Note: Not cloning here as the caller will do
// an identity check.
if (msb < 0) return m_caseDetails.records[upperValue].stmtsp;
// Recursive case:
// Make left and right subtrees assuming cexpr[msb] is 0 and 1 respectively
const uint32_t upperValue0 = upperValue;
const uint32_t upperValue1 = upperValue | (1UL << msb);
AstNode* tree0p = convertCaseFastRecurse(cexprp, msb - 1, upperValue0);
AstNode* tree1p = convertCaseFastRecurse(cexprp, msb - 1, upperValue1);
// If same logic on both sides, we can just return one of them
if (tree0p == tree1p) return tree0p;
// We could have a "checkerboard" with A B A B, we can use the same IF on both edges
{
bool same = true;
for (uint32_t a = upperValue, b = (upperValue | (1UL << msb));
a < (upperValue | (1UL << msb)); a++, b++) {
if (m_valueItem[a] != m_valueItem[b]) {
for (uint32_t a = upperValue0, b = upperValue1; a < upperValue1; ++a, ++b) {
if (m_caseDetails.records[a].stmtsp != m_caseDetails.records[b].stmtsp) {
same = false;
break;
}
@ -373,161 +419,120 @@ class CaseVisitor final : public VNVisitor {
VL_DO_DANGLING(tree1p->deleteTree(), tree1p);
return tree0p;
}
// Must have differing logic, so make a selection
// Case expressions can't be linked twice, so clone them
if (tree0p && !tree0p->user3()) tree0p = tree0p->cloneTree(true);
if (tree1p && !tree1p->user3()) tree1p = tree1p->cloneTree(true);
// Alternate scheme if we ever do multiple bits at a time:
// V3Number nummask (cexprp, cexprp->width(), (1UL<<msb));
// AstNode* and1p = new AstAnd(cexprp->fileline(), cexprp->cloneTreePure(false),
// new AstConst(cexprp->fileline(), nummask));
AstNodeExpr* const and1p
= new AstSel{cexprp->fileline(), cexprp->cloneTreePure(false), msb, 1};
AstNodeExpr* const eqp
= new AstNeq{cexprp->fileline(), new AstConst{cexprp->fileline(), 0}, and1p};
AstIf* const ifp = new AstIf{cexprp->fileline(), eqp, tree1p, tree0p};
ifp->user3(1); // So we don't bother to clone it
return ifp;
}
// Must have differing logic. Test the bit and convert to an If.
// Clone if needed
if (tree0p && tree0p->backp()) tree0p = tree0p->cloneTree(true);
if (tree1p && tree1p->backp()) tree1p = tree1p->cloneTree(true);
// Create the If statement
FileLine* const flp = cexprp->fileline();
AstNodeExpr* const condp = new AstSel{flp, cexprp->cloneTreePure(false), msb, 1};
AstIf* const ifp = new AstIf{flp, condp, tree1p, tree0p};
return ifp;
}
void replaceCaseFast(AstCase* nodep) {
// CASEx(cexpr,....
// -> tree of IF(msb, IF(msb-1, 11, 10)
// IF(msb-1, 01, 00))
AstNodeExpr* cexprp;
AstExprStmt* cexprStmtp = nullptr;
if (nodep->exprp()->isPure()) {
cexprp = nodep->exprp()->unlinkFrBack();
} else {
cexprStmtp = VN_AS(nodep->exprp()->unlinkFrBack(), ExprStmt);
cexprp = cexprStmtp->resultp()->cloneTreePure(false);
}
if (debug() >= 9) { // LCOV_EXCL_START
for (uint32_t i = 0; i < (1UL << m_caseWidth); ++i) {
if (const AstNode* const itemp = m_valueItem[i]) {
UINFO(9, "Value " << std::hex << i << " " << itemp);
}
}
} // LCOV_EXCL_STOP
// Handle any assertions
replaceCaseParallel(nodep, m_caseNoOverlapsAllCovered);
AstNode::user3ClearTree();
AstNode* ifrootp = replaceCaseFastRecurse(cexprp, m_caseWidth - 1, 0UL);
if (cexprStmtp) {
cexprStmtp->resultp()->unlinkFrBack()->deleteTree();
AstIf* const ifp = VN_AS(ifrootp, If);
cexprStmtp->resultp(ifp->condp()->unlinkFrBack());
ifp->condp(cexprStmtp);
m_needToClearCache = true;
}
// Case expressions can't be linked twice, so clone them
if (ifrootp && !ifrootp->user3()) ifrootp = ifrootp->cloneTree(true);
if (ifrootp) {
nodep->replaceWith(ifrootp);
} else {
nodep->unlinkFrBack();
}
VL_DO_DANGLING(nodep->deleteTree(), nodep);
VL_DO_DANGLING(cexprp->deleteTree(), cexprp);
UINFOTREE(9, ifrootp, "", "_simp");
// CASEx(cexpr,....
// -> tree of IF(msb, IF(msb-1, 11, 10)
// IF(msb-1, 01, 00))
// TODO: should return AstNodeStmt after #6280
AstNode* convertCaseFast(AstCase* nodep) const {
const int caseWidth = nodep->exprp()->width();
AstNode* const ifrootp = convertCaseFastRecurse(nodep->exprp(), caseWidth - 1, 0UL);
return ifrootp && ifrootp->backp() ? ifrootp->cloneTree(true) : ifrootp;
}
void replaceCaseComplicated(AstCase* nodep) {
// CASEx(cexpr,ITEM(icond1,istmts1),ITEM(icond2,istmts2),ITEM(default,istmts3))
// -> IF((cexpr==icond1),istmts1,
// IF((EQ (AND MASK cexpr) (AND MASK icond1)
// ,istmts2, istmts3
AstNodeExpr* cexprp;
AstExprStmt* cexprStmtp = nullptr;
if (nodep->exprp()->isPure()) {
cexprp = nodep->exprp()->unlinkFrBack();
} else {
cexprStmtp = VN_AS(nodep->exprp(), ExprStmt)->unlinkFrBack();
cexprp = cexprStmtp->resultp()->cloneTreePure(false);
}
// We'll do this in two stages. First stage, convert the conditions to
// the appropriate IF AND terms.
UINFOTREE(9, nodep, "", "_comp_IN::");
bool hadDefault = false;
// Convet case statement using generic if/else tree
// CASEx(cexpr,ITEM(icond1,istmts1),ITEM(icond2,istmts2),ITEM(default,istmts3))
// -> IF((cexpr==icond1),istmts1,
// IF((EQ (AND MASK cexpr) (AND MASK icond1)
// ,istmts2, istmts3
// TODO: should return AstNodeStmt after #6280
AstNode* convertCaseGeneric(AstCase* nodep) const {
// We'll do this in two stages.
// First stage, convert the conditions to the appropriate IF AND terms.
bool hasDefault = false;
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), CaseItem)) {
if (!itemp->condsp()) {
// Default clause. Just make true, we'll optimize it away later
itemp->addCondsp(new AstConst{itemp->fileline(), AstConst::BitTrue{}});
hadDefault = true;
} else {
// Expressioned clause
AstNodeExpr* icondNextp = nullptr;
AstNodeExpr* ifexprp = nullptr; // If expression to test
for (AstNodeExpr* icondp = itemp->condsp(); icondp; icondp = icondNextp) {
icondNextp = VN_AS(icondp->nextp(), NodeExpr);
icondp->unlinkFrBack();
AstNodeExpr* condp = nullptr; // Default is to use and1p/and2p
AstConst* const iconstp = VN_CAST(icondp, Const);
if (iconstp && neverItem(nodep, iconstp)) {
// X in casez can't ever be executed
VL_DO_DANGLING(icondp->deleteTree(), icondp);
VL_DANGLING(iconstp);
// For simplicity, make expression that is not equal, and let later
// optimizations remove it
condp = new AstConst{itemp->fileline(), AstConst::BitFalse{}};
} else if (AstInsideRange* const irangep = VN_CAST(icondp, InsideRange)) {
// Similar logic in V3Width::visit(AstInside)
condp = irangep->newAndFromInside(cexprp->cloneTreePure(true),
irangep->lhsp()->unlinkFrBack(),
irangep->rhsp()->unlinkFrBack());
VL_DO_DANGLING2(icondp->deleteTree(), icondp, irangep);
} else if (iconstp && iconstp->num().isFourState()
&& (nodep->casex() || nodep->casez() || nodep->caseInside())) {
V3Number nummask{itemp, iconstp->width()};
nummask.opBitsNonX(iconstp->num());
V3Number numval{itemp, iconstp->width()};
numval.opBitsOne(iconstp->num());
AstNodeExpr* const and1p
= new AstAnd{itemp->fileline(), cexprp->cloneTreePure(false),
new AstConst{itemp->fileline(), nummask}};
AstNodeExpr* const and2p = new AstAnd{
itemp->fileline(), new AstConst{itemp->fileline(), numval},
new AstConst{itemp->fileline(), nummask}};
VL_DO_DANGLING(icondp->deleteTree(), icondp);
VL_DANGLING(iconstp);
condp = AstEq::newTyped(itemp->fileline(), and1p, and2p);
} else {
// Not a caseX mask, we can build CASEEQ(cexpr icond)
AstNodeExpr* const and1p = cexprp->cloneTreePure(false);
AstNodeExpr* const and2p = icondp;
condp = AstEq::newTyped(itemp->fileline(), and1p, and2p);
}
if (!ifexprp) {
ifexprp = condp;
} else {
ifexprp = new AstLogOr{itemp->fileline(), ifexprp, condp};
}
}
// Replace expression in tree
itemp->addCondsp(ifexprp);
FileLine* const flp = itemp->fileline();
// Default clause. Just make true, we'll optimize it away later
if (itemp->isDefault()) {
itemp->addCondsp(new AstConst{flp, AstConst::BitTrue{}});
hasDefault = true;
continue;
}
// Regular clause. Construct the condition expression.
AstNodeExpr* newCondp = nullptr;
for (AstNodeExpr *itemExprp = itemp->condsp(), *nextp; itemExprp; itemExprp = nextp) {
nextp = VN_AS(itemExprp->nextp(), NodeExpr);
itemExprp->unlinkFrBack();
// If case never matches, ignore it
if (neverItem(nodep, itemExprp)) {
VL_DO_DANGLING(itemExprp->deleteTree(), itemExprp);
continue;
}
// Compute the term to add to the condition expression
AstNodeExpr* const termp = [&]() -> AstNodeExpr* {
// Will need a copy of the caseExpr regardless
AstNodeExpr* const caseExprp = nodep->exprp()->cloneTreePure(false);
// InsideRange: Similar logic in V3Width::visit(AstInside)
if (AstInsideRange* const itemRangep = VN_CAST(itemExprp, InsideRange)) {
AstNodeExpr* const resultp = itemRangep->newAndFromInside( //
caseExprp, //
itemRangep->lhsp()->unlinkFrBack(),
itemRangep->rhsp()->unlinkFrBack());
VL_DO_DANGLING2(itemExprp->deleteTree(), itemExprp, itemRangep);
return resultp;
}
// Check if we need to perform a wildcard match, this needs masking
if (AstConst* const itemConstp = VN_CAST(itemExprp, Const)) {
// TODO: 4-state will need to fix this
if (itemConstp->num().isFourState()
&& (nodep->casex() || nodep->casez() || nodep->caseInside())) {
// Wildcard match, make 'caseExpr' & 'mask' == 'itemExpr' & 'mask'
V3Number numMask{itemp, itemConstp->width()};
numMask.opBitsNonX(itemConstp->num());
V3Number numOne{itemp, itemConstp->width()};
numOne.opBitsOne(itemConstp->num());
V3Number numRhs{itemp, itemConstp->width()};
numRhs.opAnd(numOne, numMask);
VL_DO_DANGLING2(itemExprp->deleteTree(), itemExprp, itemConstp);
return AstEq::newTyped(
flp, //
new AstConst{flp, numRhs},
new AstAnd{flp, caseExprp, new AstConst{flp, numMask}});
}
}
// Regular case, use simple equality comparison
return AstEq::newTyped(flp, caseExprp, itemExprp);
}();
// 'Or' new term with previous terms
newCondp = newCondp ? new AstLogOr{flp, newCondp, termp} : termp;
}
// Replace expression in tree. Needs to be non-null, so add a constant false if needed
if (!newCondp) newCondp = new AstConst{flp, AstConst::BitFalse{}};
itemp->addCondsp(newCondp);
}
VL_DO_DANGLING(cexprp->deleteTree(), cexprp);
if (!hadDefault) {
// If there was no default, add a empty one, this greatly simplifies below code
// and constant propagation will just eliminate it for us later.
// If there was no default, add a empty one, this greatly simplifies below code
// and constant propagation will just eliminate it for us later.
if (!hasDefault) {
nodep->addItemsp(new AstCaseItem{
nodep->fileline(), new AstConst{nodep->fileline(), AstConst::BitTrue{}}, nullptr});
}
UINFOTREE(9, nodep, "", "_comp_COND");
// Now build the IF statement tree
// The tree can be quite huge. Pull ever group of 8 out, and make a OR tree.
// The tree can be quite huge. Pull every group of 8 out, and make a OR tree.
// This reduces the depth for the bottom elements, at the cost of
// some of the top elements. If we ever have profiling data, we
// should pull out the most common item from here and instead make
@ -538,8 +543,11 @@ class CaseVisitor final : public VNVisitor {
AstIf* itemnextp = nullptr;
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), CaseItem)) {
AstNode* const istmtsp = itemp->stmtsp(); // Maybe null -- no action.
// Grab the statements from this item. May be empty.
AstNode* const istmtsp = itemp->stmtsp();
if (istmtsp) istmtsp->unlinkFrBackWithNext();
// Expressioned clause
AstNodeExpr* const ifexprp = itemp->condsp()->unlinkFrBack();
{ // Prepare for next group
@ -575,65 +583,76 @@ class CaseVisitor final : public VNVisitor {
itemnextp = newp;
}
}
UINFOTREE(9, nodep, "", "_comp_TREE");
// Handle any assertions
replaceCaseParallel(nodep, false);
// Replace the CASE... with IF...
if (grouprootp) {
UINFOTREE(9, grouprootp, "", "_new");
nodep->replaceWith(grouprootp);
if (cexprStmtp) {
pushDeletep(cexprStmtp->resultp()->unlinkFrBack());
cexprStmtp->resultp(grouprootp->condp()->unlinkFrBack());
grouprootp->condp(cexprStmtp);
m_needToClearCache = true;
}
} else {
nodep->unlinkFrBack();
}
VL_DO_DANGLING(nodep->deleteTree(), nodep);
return grouprootp;
}
void replaceCaseParallel(AstCase* nodep, bool noOverlapsAllCovered) {
// Take the notParallelp tree under the case statement created by V3Assert
// If the statement was proven to have no overlaps and all cases
// covered, we're done with it.
// Else, convert to a normal statement parallel with the case statement.
if (nodep->notParallelp() && !noOverlapsAllCovered) {
AstNode* const parp = nodep->notParallelp()->unlinkFrBackWithNext();
nodep->addNextHere(parp);
// Convert the given case statement to a representation not using AstCase
// TODO: should return AstNodeStmt after #6280
AstNode* convertCase(AstCase* nodep, Stats& stats) const {
// Determine if we should use the fast bitwise branching tree method
const bool useFastBitTree = [&]() {
// Not if disabled
if (!v3Global.opt.fCase()) return false;
// Can't do it without the detailed analysis
if (!m_caseDetailsValid) return false;
// Can't do it if not exhaustive
if (!m_caseDetails.exhaustive) return false;
// Not worth doing if there are few conditions
if (m_caseNConditions <= 3) return false;
// Avoid e.g. priority expanders from going crazy in expansion
const size_t caseWidth = nodep->exprp()->width();
if (caseWidth >= 8 && m_caseNConditions <= (caseWidth + 1)) return false;
// Otherwise use the bit tree
return true;
}();
if (useFastBitTree) {
++stats.caseFast;
return convertCaseFast(nodep);
}
}
bool neverItem(const AstCase* casep, const AstConst* itemp) {
// Xs in case or casez are impossible due to two state simulations
if (casep->casex()) {
} else if (casep->casez() || casep->caseInside()) {
if (itemp->num().isAnyX()) return true;
} else {
if (itemp->num().isFourState()) return true;
// Convert using the generic if/else tree method
++stats.caseGeneric;
// If a case statement is exhaustive, presume signals involved aren't forming a latch
// TODO: this is broken, but it is as was before
if (m_alwaysp && (!m_caseDetailsValid || m_caseDetails.exhaustive)) {
m_alwaysp->fileline()->warnOff(V3ErrorCode::LATCH, true);
}
return false;
return convertCaseGeneric(nodep);
}
// VISITORS
void visit(AstCase* nodep) override {
VL_RESTORER(m_caseIncomplete);
{ CaseLintVisitor{nodep}; }
UASSERT_OBJ(nodep->exprp()->isPure(), nodep,
"Impure case expression should have been removed by V3LiftExpr");
CaseLintVisitor::apply(nodep);
// Convert any children first
iterateChildren(nodep);
UINFOTREE(9, nodep, "", "case_old");
if (isCaseTreeFast(nodep) && v3Global.opt.fCase()) {
// It's a simple priority encoder or complete statement
// we can make a tree of statements to avoid extra comparisons
++m_statCaseFast;
VL_DO_DANGLING(replaceCaseFast(nodep), nodep);
} else {
// If a case statement is whole, presume signals involved aren't forming a latch
if (m_alwaysp && !m_caseIncomplete)
m_alwaysp->fileline()->warnOff(V3ErrorCode::LATCH, true);
++m_statCaseSlow;
VL_DO_DANGLING(replaceCaseComplicated(nodep), nodep);
// Analyze this case statement
analyzeCase(nodep);
// Take the 'notParallelp' statements under the case statement created by V3Assert.
// If the statement was proven to have no overlaps and all cases covered,
// it can be removed. Otherwise insert the assertion after the case statement.
if (AstNode* const stmtp = nodep->notParallelp()) {
stmtp->unlinkFrBackWithNext();
if (m_caseDetailsValid && m_caseDetails.exhaustive && m_caseDetails.noOverlaps) {
++m_stats.provenAssertions;
VL_DO_DANGLING(stmtp->deleteTree(), stmtp);
} else {
nodep->addNextHere(stmtp);
}
}
// Convert the case statement and replace the original
if (AstNode* const replacementp = convertCase(nodep, m_stats)) {
nodep->replaceWith(replacementp);
} else {
nodep->unlinkFrBack();
}
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
//--------------------
void visit(AstAlways* nodep) override {
@ -645,14 +664,11 @@ class CaseVisitor final : public VNVisitor {
public:
// CONSTRUCTORS
explicit CaseVisitor(AstNetlist* nodep) {
for (auto& itr : m_valueItem) itr = nullptr;
iterate(nodep);
if (m_needToClearCache) VIsCached::clearCacheTree();
}
explicit CaseVisitor(AstNetlist* nodep) { iterate(nodep); }
~CaseVisitor() override {
V3Stats::addStat("Optimizations, Cases parallelized", m_statCaseFast);
V3Stats::addStat("Optimizations, Cases complex", m_statCaseSlow);
V3Stats::addStat("Optimizations, Cases parallelized", m_stats.caseFast);
V3Stats::addStat("Optimizations, Cases complex", m_stats.caseGeneric);
V3Stats::addStat("Optimizations, Cases proven assertions", m_stats.provenAssertions);
}
};
@ -666,5 +682,5 @@ void V3Case::caseAll(AstNetlist* nodep) {
}
void V3Case::caseLint(AstGenCase* nodep) {
UINFO(4, __FUNCTION__ << ": ");
{ CaseLintVisitor{nodep}; }
CaseLintVisitor::apply(nodep);
}

View File

@ -51,28 +51,18 @@ class CfgLiveVariables final : VNVisitorConst {
// METHODS
// This is to match DFG, but can be extended independently - eventually should handle all
static bool isSupportedPackedDType(const AstNodeDType* dtypep) {
dtypep = dtypep->skipRefp();
static bool isPacked(const AstVarScope* vscp) {
AstNodeDType* const dtypep = vscp->dtypep()->skipRefp();
if (const AstBasicDType* const typep = VN_CAST(dtypep, BasicDType)) {
return typep->keyword().isIntNumeric();
}
if (const AstPackArrayDType* const typep = VN_CAST(dtypep, PackArrayDType)) {
return isSupportedPackedDType(typep->subDTypep());
}
if (VN_IS(dtypep, PackArrayDType)) return true;
if (const AstNodeUOrStructDType* const typep = VN_CAST(dtypep, NodeUOrStructDType)) {
return typep->packed();
}
return false;
}
// Check and return if variable is incompatible
bool incompatible(AstVarScope* vscp) {
if (!isSupportedPackedDType(vscp->dtypep())) return true;
if (vscp->varp()->ignoreSchedWrite()) return true;
return false;
}
void updateGen(AstNode* nodep) {
UASSERT_OBJ(!m_abort, nodep, "Doing useless work");
m_abort = nodep->exists([&](const AstVarRef* refp) {
@ -80,8 +70,6 @@ class CfgLiveVariables final : VNVisitorConst {
if (refp->access().isWriteOnly()) return false;
// Grab referenced variable
AstVarScope* const vscp = refp->varScopep();
// Bail if not a compatible type
if (incompatible(vscp)) return true;
// Add to gen set, if not killed - assume whole variable read
if (m_currp->m_kill.count(vscp)) return false;
m_currp->m_gen.emplace(vscp);
@ -96,11 +84,13 @@ class CfgLiveVariables final : VNVisitorConst {
if (refp->access().isReadOnly()) return false;
// Grab referenced variable
AstVarScope* const vscp = refp->varScopep();
// Bail if not a compatible type
if (incompatible(vscp)) return true;
// If whole written, add to kill set
if (refp->nextp()) return false;
if (VN_IS(refp->abovep(), Sel)) return false;
// Safety case: bail on variables with unusual semantics
if (vscp->varp()->ignoreSchedWrite()) return true;
// If not a packed variable, assume it's not a whole update, don't add to kill set
if (!isPacked(vscp)) return false;
// If packed, and updated partially, don't add to kill set
if (refp->backp()->nextp() != refp && VN_IS(refp->abovep(), Sel)) return false;
// Whole variable is writen, add to kill set
m_currp->m_kill.emplace(vscp);
return false;
});
@ -198,7 +188,7 @@ public:
CfgLiveVariables analysis{cfg};
// If failed, return nullptr
if (analysis.m_abort) return nullptr;
// Gather variables live in to the entry blockstd::unique_ptr<std::vector<AstVarScope*>>
// Gather variables live in to the entry block
const std::unordered_set<AstVarScope*>& lin = analysis.m_blockState[cfg.enter()].m_liveIn;
std::vector<AstVarScope*>* const resultp
= new std::vector<AstVarScope*>{lin.begin(), lin.end()};

View File

@ -973,6 +973,96 @@ class ConstVisitor final : public VNVisitor {
return !numv.isNumber() ? numv : V3Number{nodep, nodep->width(), numv};
}
static bool lowerAsFixedAggregate(const AstNodeDType* const dtypep) {
return dtypep->isStreamableFixedAggregate() && dtypep->containsUnpackedStruct();
}
AstStructSel* newStructSel(AstNodeExpr* const fromp, const AstMemberDType* const itemp) {
AstStructSel* const selp = new AstStructSel{fromp->fileline(), fromp, itemp->name()};
selp->dtypeFrom(itemp->dtypep());
return selp;
}
void collectFixedAggregateTerms(AstNodeExpr* const fromp, std::vector<AstNodeExpr*>& termps,
const bool packReal) {
const AstNodeDType* const dtypep = fromp->dtypep()->skipRefp();
if (const AstUnpackArrayDType* const unpackDtypep = VN_CAST(dtypep, UnpackArrayDType)) {
const int left = unpackDtypep->left();
const int right = unpackDtypep->right();
const int step = left <= right ? 1 : -1;
for (int idx = left;; idx += step) {
AstArraySel* const selp
= new AstArraySel{fromp->fileline(), fromp->cloneTreePure(false), idx};
collectFixedAggregateTerms(selp, termps, packReal);
if (idx == right) break;
}
VL_DO_DANGLING(pushDeletep(fromp), fromp);
} else if (const AstNodeUOrStructDType* const sdtypep
= VN_CAST(dtypep, NodeUOrStructDType)) {
if (sdtypep->packed()) {
termps.push_back(fromp);
return;
}
for (const AstMemberDType* itemp = sdtypep->membersp(); itemp;
itemp = VN_AS(itemp->nextp(), MemberDType)) {
collectFixedAggregateTerms(newStructSel(fromp->cloneTreePure(false), itemp),
termps, packReal);
}
VL_DO_DANGLING(pushDeletep(fromp), fromp);
} else if (packReal && dtypep->isDouble()) {
termps.push_back(new AstRealToBits{fromp->fileline(), fromp});
} else {
termps.push_back(fromp);
}
}
AstNodeExpr* packFixedAggregate(AstNodeExpr* const fromp) {
std::vector<AstNodeExpr*> termps;
collectFixedAggregateTerms(fromp, termps, true);
UASSERT(!termps.empty(), "No stream terms");
AstNodeExpr* resultp = termps[0];
for (size_t i = 1; i < termps.size(); ++i) {
resultp = new AstConcat{resultp->fileline(), resultp, termps[i]};
}
return resultp;
}
void replaceAssignToFixedAggregate(AstNodeAssign* const nodep, AstNodeExpr* const dstp,
AstNodeExpr* srcp) {
const int dstWidth = dstp->dtypep()->widthStream();
std::vector<AstNodeExpr*> termps;
collectFixedAggregateTerms(dstp, termps, false);
int srcWidth = srcp->width();
if (srcWidth < dstWidth) {
AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp};
extendp->dtypeSetLogicSized(dstWidth, VSigning::UNSIGNED);
srcp = new AstShiftL{
extendp->fileline(), extendp,
new AstConst{extendp->fileline(), static_cast<uint32_t>(dstWidth - srcWidth)},
dstWidth};
srcWidth = dstWidth;
}
AstNodeAssign* newp = nullptr;
int offset = 0;
for (size_t i = 0; i < termps.size(); ++i) {
AstNodeExpr* const termp = termps[i];
const int width = termp->dtypep()->widthStream();
const int lsb = srcWidth - offset - width;
offset += width;
AstNodeExpr* rhsp
= new AstSel{srcp->fileline(), srcp->cloneTreePure(false), lsb, width};
if (termp->dtypep()->skipRefp()->isDouble()) {
rhsp = new AstBitsToRealD{rhsp->fileline(), rhsp};
}
AstNodeAssign* const assignp = nodep->cloneType(termp, rhsp);
assignp->dtypeFrom(termp);
newp = AstNode::addNext(newp, assignp);
}
nodep->addNextHere(newp);
VL_DO_DANGLING(pushDeletep(srcp), srcp);
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
bool operandConst(const AstNode* nodep) { return VN_IS(nodep, Const); }
bool operandAsvConst(const AstNode* nodep) {
// BIASV(CONST, BIASV(CONST,...)) -> BIASV( BIASV_CONSTED(a,b), ...)
@ -2393,6 +2483,25 @@ class ConstVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(conp), conp);
// Further reduce, either node may have more reductions.
return true;
} else if (m_doV && VN_IS(nodep->rhsp(), CvtPackedToArray)
&& lowerAsFixedAggregate(nodep->lhsp()->dtypep())) {
AstCvtPackedToArray* const cvtp
= VN_AS(nodep->rhsp(), CvtPackedToArray)->unlinkFrBack();
AstNodeExpr* srcp = cvtp->fromp()->unlinkFrBack();
if (lowerAsFixedAggregate(srcp->dtypep())) {
srcp = packFixedAggregate(srcp);
} else if (AstNodeStream* const streamp = VN_CAST(srcp, NodeStream)) {
AstNodeExpr* const streamSrcp = streamp->lhsp();
if (lowerAsFixedAggregate(streamSrcp->dtypep())) {
AstNodeExpr* const packedp = packFixedAggregate(streamSrcp->unlinkFrBack());
streamp->lhsp(packedp);
streamp->dtypeSetLogicUnsized(packedp->width(), packedp->widthMin(),
VSigning::UNSIGNED);
}
}
VL_DO_DANGLING(pushDeletep(cvtp), cvtp);
replaceAssignToFixedAggregate(nodep, nodep->lhsp()->unlinkFrBack(), srcp);
return true;
} else if (m_doV && VN_IS(nodep->rhsp(), StreamR)
&& !VN_IS(nodep->lhsp()->dtypep()->skipRefp(), QueueDType)) {
// The right-streaming operator on rhs of assignment does not
@ -2402,7 +2511,9 @@ class ConstVisitor final : public VNVisitor {
AstNodeExpr* srcp = streamp->lhsp()->unlinkFrBack();
AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp();
const AstNodeDType* const dstDTypep = nodep->lhsp()->dtypep()->skipRefp();
if (VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)) {
if (lowerAsFixedAggregate(srcDTypep)) {
srcp = packFixedAggregate(srcp);
} else if (VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)) {
if (VN_IS(dstDTypep, QueueDType) || VN_IS(dstDTypep, DynArrayDType)) {
int srcElementBits = 0;
if (const AstNodeDType* const elemDtp = srcDTypep->subDTypep()) {
@ -2429,6 +2540,19 @@ class ConstVisitor final : public VNVisitor {
srcp = new AstShiftL{srcp->fileline(), srcp,
new AstConst{srcp->fileline(), offset}, packedBits};
}
if (!VN_IS(dstDTypep, UnpackArrayDType) && !VN_IS(dstDTypep, QueueDType)
&& !VN_IS(dstDTypep, DynArrayDType)) {
const int sWidth = srcp->width();
const int dWidth = nodep->lhsp()->width();
if (sWidth < dWidth) {
AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp};
extendp->dtypeSetLogicSized(dWidth, VSigning::UNSIGNED);
srcp = new AstShiftL{
srcp->fileline(), extendp,
new AstConst{srcp->fileline(), static_cast<uint32_t>(dWidth - sWidth)},
dWidth};
}
}
nodep->rhsp(srcp);
VL_DO_DANGLING(pushDeletep(streamp), streamp);
// Further reduce, any of the nodes may have more reductions.
@ -2438,7 +2562,7 @@ class ConstVisitor final : public VNVisitor {
AstNodeExpr* streamp = nodep->lhsp()->unlinkFrBack();
AstNodeExpr* const dstp = VN_AS(streamp, StreamL)->lhsp()->unlinkFrBack();
AstNodeDType* const dstDTypep = dstp->dtypep()->skipRefp();
AstNodeExpr* const srcp = nodep->rhsp()->unlinkFrBack();
AstNodeExpr* srcp = nodep->rhsp()->unlinkFrBack();
const AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp();
// Handle unpacked/queue/dynarray source -> queue/dynarray dest via
// CvtArrayToArray (StreamL reverses, so reverse=true)
@ -2466,6 +2590,7 @@ class ConstVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(streamp), streamp);
return true;
}
if (lowerAsFixedAggregate(srcDTypep)) srcp = packFixedAggregate(srcp);
const int sWidth = srcp->width();
const int dWidth = dstp->width();
// Connect the rhs to the stream operator and update its width
@ -2556,7 +2681,7 @@ class ConstVisitor final : public VNVisitor {
} else {
// Source narrower than destination: left-justify by shifting left.
// The right stream operator packs left-to-right, so remaining
// LSBs are zero-filled (IEEE 1800-2023 11.4.14.2).
// LSBs are zero-filled (IEEE 1800-2023 11.4.14.3).
if (!VN_IS(srcp->dtypep()->skipRefp(), QueueDType)) {
AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp};
extendp->dtypeSetLogicSized(dWidth, VSigning::UNSIGNED);
@ -2580,6 +2705,13 @@ class ConstVisitor final : public VNVisitor {
AstNodeExpr* srcp = streamp->lhsp();
const AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp();
AstNodeDType* const dstDTypep = nodep->lhsp()->dtypep()->skipRefp();
if (lowerAsFixedAggregate(srcDTypep)) {
AstNodeExpr* const packedp = packFixedAggregate(srcp->unlinkFrBack());
streamp->lhsp(packedp);
streamp->dtypeSetLogicUnsized(packedp->width(), packedp->widthMin(),
VSigning::UNSIGNED);
srcp = packedp;
}
if ((VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)
|| VN_IS(srcDTypep, UnpackArrayDType))) {
if (VN_IS(dstDTypep, QueueDType) || VN_IS(dstDTypep, DynArrayDType)) {

View File

@ -182,7 +182,6 @@ class V3ControlFTask final {
V3ControlVarResolver m_params; // Parameters in function/task
V3ControlVarResolver m_ports; // Ports in function/task
V3ControlVarResolver m_vars; // Variables in function/task
bool m_isolate = false; // Isolate function return
bool m_noinline = false; // Don't inline function/task
bool m_public = false; // Public function/task
@ -190,7 +189,6 @@ public:
V3ControlFTask() = default;
void update(const V3ControlFTask& f) {
// Don't overwrite true with false
if (f.m_isolate) m_isolate = true;
if (f.m_noinline) m_noinline = true;
if (f.m_public) m_public = true;
m_params.update(f.m_params);
@ -203,7 +201,6 @@ public:
V3ControlVarResolver& ports() { return m_ports; }
V3ControlVarResolver& vars() { return m_vars; }
void setIsolate(bool set) { m_isolate = set; }
void setNoInline(bool set) { m_noinline = set; }
void setPublic(bool set) { m_public = set; }
@ -212,8 +209,6 @@ public:
ftaskp->addStmtsp(new AstPragma{ftaskp->fileline(), VPragmaType::NO_INLINE_TASK});
if (m_public)
ftaskp->addStmtsp(new AstPragma{ftaskp->fileline(), VPragmaType::PUBLIC_TASK});
// Only functions can have isolate (return value)
if (VN_IS(ftaskp, Func)) ftaskp->attrIsolateAssign(m_isolate);
}
};
@ -981,13 +976,7 @@ void V3Control::addVarAttr(FileLine* fl, const string& module, const string& fta
// Semantics: Most of the attributes operate on signals
if (pattern.empty()) {
if (attr == VAttrType::VAR_ISOLATE_ASSIGNMENTS) {
if (ftask.empty()) {
fl->v3error("isolate_assignments only applies to signals or functions/tasks");
} else {
V3ControlResolver::s().modules().at(module).ftasks().at(ftask).setIsolate(true);
}
} else if (attr == VAttrType::VAR_PUBLIC) {
if (attr == VAttrType::VAR_PUBLIC) {
if (ftask.empty()) {
// public module, this is the only exception from var here
V3ControlResolver::s().modules().at(module).addModulePragma(

View File

@ -166,10 +166,13 @@ class CoverageVisitor final : public VNVisitor {
// METHODS
// Return non-nullptr reason if this variable shouldn't have toggle coverage
const char* varIgnoreToggle(const AstVar* nodep) {
// Return true if this shouldn't be traced
// See also similar rule in V3TraceDecl::varIgnoreTrace
if (!nodep->isToggleCoverable()) return "Not relevant signal type";
const bool cover = nodep->isIO() || (nodep->isSignal() && nodep->isBitLogic());
if (!cover) return "Not relevant signal";
if (nodep->isConst()) return "Signal is constant";
if (nodep->isDouble()) return "Signal is double";
if (nodep->isString()) return "Signal is string";
if (!v3Global.opt.coverageUnderscore()) {
const string prettyName = nodep->prettyName();
if (prettyName[0] == '_') return "Leading underscore";
@ -530,8 +533,10 @@ class CoverageVisitor final : public VNVisitor {
newent.cleanup();
}
}
} else if (VN_IS(dtypep, QueueDType)) {
} else if (VN_IS(dtypep, QueueDType) || VN_IS(dtypep, AssocArrayDType)
|| VN_IS(dtypep, WildcardArrayDType)) {
// Not covered
varp->v3warn(COVERIGN, "Coverage ignored for type " << dtypep->prettyTypeName());
} else {
dtypep->v3fatalSrc("Unexpected node data type in toggle coverage generation: "
<< dtypep->prettyTypeName());

View File

@ -68,6 +68,9 @@ class FunctionalCoverageVisitor final : public VNVisitor {
, crossBins{cb} {}
};
std::vector<BinInfo> m_binInfos; // All bins in current covergroup
std::set<std::string> m_crossedCpNames; // Coverpoints referenced by a cross (kept legacy)
std::vector<AstVar*> m_convCpVars; // VlCoverpoint members of converted coverpoints
AstCDType* m_vlCoverpointDTypep = nullptr; // Shared "VlCoverpoint" C++ member type
VMemberMap m_memberMap; // Member names cached for fast lookup
@ -88,6 +91,17 @@ class FunctionalCoverageVisitor final : public VNVisitor {
// Clear bin info for this covergroup (deleting any orphaned cross pseudo-bins)
clearBinInfos();
// Coverpoints referenced by a cross keep the legacy per-bin-member path (the cross
// reads those members); collect their names before they are consumed by the cross.
m_crossedCpNames.clear();
m_convCpVars.clear();
for (AstCoverCross* crossp : m_coverCrosses) {
for (AstNode* itemp = crossp->itemsp(); itemp; itemp = itemp->nextp()) {
if (const AstCoverpointRef* const refp = VN_CAST(itemp, CoverpointRef))
m_crossedCpNames.insert(refp->name());
}
}
// For each coverpoint, generate sampling code
for (AstCoverpoint* cpp : m_coverpoints) generateCoverpointCode(cpp);
@ -504,6 +518,13 @@ class FunctionalCoverageVisitor final : public VNVisitor {
// Create implicit automatic bins if no regular bins exist
createImplicitAutoBins(coverpointp, exprp, autoBinMax);
// Eligible coverpoints route through the VlCoverpoint runtime; the rest (cross-fed or
// transition-bearing) keep the legacy per-bin-member path below.
if (coverpointConvertible(coverpointp)) {
generateConvertedCoverpoint(coverpointp, exprp, atLeastValue);
return;
}
// Generate member variables and matching code for each bin
// Process in two passes: first non-default bins, then default bins
std::vector<AstCoverBin*> defaultBins;
@ -595,47 +616,179 @@ class FunctionalCoverageVisitor final : public VNVisitor {
UINFO(4, " Successfully added if statement for bin: " << binp->name());
}
// Build the condition under which a default bin matches: NOT(OR of all normal bins).
AstNodeExpr* buildDefaultCondition(AstCoverpoint* coverpointp, AstNodeExpr* exprp,
FileLine* fl) {
AstNodeExpr* anyBinMatchp = nullptr;
for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) {
AstCoverBin* const cbinp = VN_AS(binp, CoverBin);
if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT
|| cbinp->binsType() == VCoverBinsType::BINS_IGNORE
|| cbinp->binsType() == VCoverBinsType::BINS_ILLEGAL)
continue;
AstNodeExpr* const binCondp = buildBinCondition(cbinp, exprp);
UASSERT_OBJ(binCondp, cbinp,
"buildBinCondition returned nullptr for non-ignore/non-illegal bin");
anyBinMatchp = anyBinMatchp ? new AstOr{fl, anyBinMatchp, binCondp} : binCondp;
}
return anyBinMatchp ? static_cast<AstNodeExpr*>(new AstNot{fl, anyBinMatchp})
: static_cast<AstNodeExpr*>(new AstConst{fl, AstConst::BitTrue{}});
}
//====================================================================
// VlCoverpoint conversion (eligible coverpoints)
// True if a coverpoint routes through the VlCoverpoint runtime. Cross-fed coverpoints
// (the cross reads their per-bin members) and transition-bearing ones stay legacy.
bool coverpointConvertible(AstCoverpoint* coverpointp) {
if (m_crossedCpNames.count(coverpointp->name())) return false;
for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) {
if (VN_AS(binp, CoverBin)->transp()) return false;
}
return true;
}
// A 'this->m_member' reference for embedding in an AstCStmt
AstVarRef* memberRef(FileLine* fl, AstVar* varp) {
AstVarRef* const refp = new AstVarRef{fl, varp, VAccess::READ};
refp->selfPointer(VSelfPointerText{VSelfPointerText::This{}});
return refp;
}
// Individual equality targets of an array bin (bins b[N] = {values/ranges}), in order.
std::vector<AstNodeExpr*> extractArrayValues(AstCoverBin* arrayBinp, AstNodeExpr* exprp) {
std::vector<AstNodeExpr*> values;
for (AstNode* rangep = arrayBinp->rangesp(); rangep; rangep = rangep->nextp()) {
if (AstInsideRange* const irp = VN_CAST(rangep, InsideRange)) {
AstConst* const minp = VN_CAST(V3Const::constifyEdit(irp->lhsp()), Const);
AstConst* const maxp = VN_CAST(V3Const::constifyEdit(irp->rhsp()), Const);
if (!minp || !maxp) {
arrayBinp->v3error("Non-constant expression in array bins range; "
"range bounds must be constants");
return values;
}
for (int val = minp->toSInt(); val <= maxp->toSInt(); ++val)
values.push_back(new AstConst{irp->fileline(), AstConst::WidthedValue{},
static_cast<int>(exprp->width()),
static_cast<uint32_t>(val)});
} else {
values.push_back(VN_AS(rangep->cloneTree(false), NodeExpr));
}
}
return values;
}
// Emit a 'this->m_cp.addSingleNamer/addArrayNamer(...)' statement for one bin
AstCStmt* makeNamer(AstVar* cpVarp, AstCoverBin* binp, int count) {
FileLine* const fl = binp->fileline();
AstCStmt* const cs = new AstCStmt{fl};
cs->add(memberRef(fl, cpVarp));
const std::string loc = "\"" + std::string{fl->filename()} + "\", "
+ std::to_string(fl->lineno()) + ", "
+ std::to_string(fl->firstColumn()) + ");";
if (count < 0) { // single bin
cs->add(".addSingleNamer(" + std::string{binp->binsType().binSetEnum()} + ", \""
+ binp->name() + "\", " + loc);
} else { // value array bin
cs->add(".addArrayNamer(" + std::string{binp->binsType().binSetEnum()} + ", "
+ std::to_string(count) + ", \"" + binp->name() + "\", " + loc);
}
return cs;
}
// Emit 'if (iff && cond) m_cp.incrementBin(idx);' (or recordHit, + illegal action) in sample()
void emitConvHitIf(AstCoverpoint* coverpointp, AstCoverBin* binp, AstVar* cpVarp, int idx,
AstNodeExpr* condp) {
FileLine* const fl = binp->fileline();
AstCStmt* const hitp = new AstCStmt{fl};
hitp->add(memberRef(fl, cpVarp));
hitp->add((binp->binsType().binIsNormal() ? ".incrementBin(" : ".recordHit(")
+ std::to_string(idx) + ");");
AstNode* actionp = hitp;
if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) {
actionp->addNext(makeIllegalBinAction(fl, "Illegal bin " + binp->prettyNameQ()
+ " hit in coverpoint "
+ coverpointp->prettyNameQ()));
}
AstNodeExpr* const guardedp = applyCoverpointIffCondition(coverpointp, fl, condp);
UASSERT_OBJ(m_sampleFuncp, binp, "sample() CFunc not set in converted coverpoint");
m_sampleFuncp->addStmtsp(new AstIf{fl, guardedp, actionp, nullptr});
}
// Route an eligible coverpoint through a VlCoverpoint member: emit the member, its
// sample() increments, the constructor configuration (init + namers), and registration.
void generateConvertedCoverpoint(AstCoverpoint* coverpointp, AstNodeExpr* exprp,
int atLeastValue) {
FileLine* const fl = coverpointp->fileline();
UINFO(4, " Converting coverpoint to VlCoverpoint: " << coverpointp->name());
if (!m_vlCoverpointDTypep) {
m_vlCoverpointDTypep = new AstCDType{fl, "VlCoverpoint"};
v3Global.rootp()->typeTablep()->addTypesp(m_vlCoverpointDTypep);
}
AstVar* const cpVarp = new AstVar{fl, VVarType::MEMBER, "__Vcp_" + coverpointp->name(),
m_vlCoverpointDTypep};
cpVarp->isStatic(false);
m_covergroupp->addMembersp(cpVarp);
m_convCpVars.push_back(cpVarp);
// Walk bins (non-default, then default), assigning sequential indices that match the
// namer append order; emit sample increments and collect namer statements.
std::vector<AstCStmt*> namerStmts;
std::vector<AstCoverBin*> defaultBins;
int idx = 0;
for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) {
AstCoverBin* const cbinp = VN_AS(binp, CoverBin);
if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT) {
defaultBins.push_back(cbinp);
continue;
}
if (cbinp->isArray()) { // value array: bins b[N] = {...} -> b[0]..b[N-1]
std::vector<AstNodeExpr*> values = extractArrayValues(cbinp, exprp);
namerStmts.push_back(makeNamer(cpVarp, cbinp, static_cast<int>(values.size())));
for (AstNodeExpr* valuep : values) {
emitConvHitIf(coverpointp, cbinp, cpVarp, idx++,
new AstEq{cbinp->fileline(), exprp->cloneTree(false), valuep});
}
} else {
namerStmts.push_back(makeNamer(cpVarp, cbinp, -1));
// buildBinCondition is null for 'ignore_bins = default' (no ranges); the bin
// still gets a reserved slot (recorded, never incremented).
if (AstNodeExpr* const condp = buildBinCondition(cbinp, exprp))
emitConvHitIf(coverpointp, cbinp, cpVarp, idx, condp);
++idx;
}
}
for (AstCoverBin* const defBinp : defaultBins) {
namerStmts.push_back(makeNamer(cpVarp, defBinp, -1));
emitConvHitIf(coverpointp, defBinp, cpVarp, idx++,
buildDefaultCondition(coverpointp, exprp, defBinp->fileline()));
}
// Constructor: init (allocates), namers, then registration (under --coverage)
const std::string hier = m_covergroupp->name() + "." + coverpointp->name();
AstCStmt* const initp = new AstCStmt{fl};
initp->add(memberRef(fl, cpVarp));
initp->add(".init(\"" + hier + "\", " + std::to_string(atLeastValue) + ", "
+ std::to_string(idx) + ");");
m_constructorp->addStmtsp(initp);
for (AstCStmt* const ns : namerStmts) m_constructorp->addStmtsp(ns);
if (v3Global.opt.coverage()) {
AstCStmt* const regp = new AstCStmt{fl};
regp->add(memberRef(fl, cpVarp));
regp->add(".registerBins(vlSymsp->_vm_contextp__->coveragep(), \"v_covergroup/"
+ m_covergroupp->name() + "\");");
m_constructorp->addStmtsp(regp);
}
}
// Generate matching code for default bins
// Default bins match when value doesn't match any other explicit bin
void generateDefaultBinMatchCode(AstCoverpoint* coverpointp, AstCoverBin* defBinp,
AstNodeExpr* exprp, AstVar* hitVarp) {
UINFO(4, " Generating default bin match for: " << defBinp->name());
// Build OR of all non-default, non-ignore bins
AstNodeExpr* anyBinMatchp = nullptr;
for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) {
AstCoverBin* const cbinp = VN_AS(binp, CoverBin);
// Skip default, ignore, and illegal bins
if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT
|| cbinp->binsType() == VCoverBinsType::BINS_IGNORE
|| cbinp->binsType() == VCoverBinsType::BINS_ILLEGAL) {
continue;
}
// Build condition for this bin
AstNodeExpr* const binCondp = buildBinCondition(cbinp, exprp);
UASSERT_OBJ(binCondp, cbinp,
"buildBinCondition returned nullptr for non-ignore/non-illegal bin");
// OR with previous conditions
if (anyBinMatchp) {
anyBinMatchp = new AstOr{defBinp->fileline(), anyBinMatchp, binCondp};
} else {
anyBinMatchp = binCondp;
}
}
// Default matches when NO explicit bin matches
AstNodeExpr* defaultCondp = nullptr;
if (anyBinMatchp) {
// NOT (bin1 OR bin2 OR ... OR binN)
defaultCondp = new AstNot{defBinp->fileline(), anyBinMatchp};
} else {
// No other bins - default always matches (shouldn't happen in practice)
defaultCondp = new AstConst{defBinp->fileline(), AstConst::BitTrue{}};
}
AstNodeExpr* defaultCondp = buildDefaultCondition(coverpointp, exprp, defBinp->fileline());
// Apply iff condition if present
if (AstNodeExpr* iffp = coverpointp->iffp()) {
@ -1320,15 +1473,50 @@ class FunctionalCoverageVisitor final : public VNVisitor {
void generateCoverageMethodBody(AstFunc* funcp) {
FileLine* const fl = funcp->fileline();
AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var);
// Count total bins (excluding ignore_bins and illegal_bins)
// Converted coverpoints hold their bins in VlCoverpoint. Combine their contributions
// (via coverageParts) with any remaining legacy cross/cross-fed bins as the same flat
// covered/total ratio the all-legacy path below computes. Normal bins only: ignore,
// illegal, and default are excluded (LRM 19.5).
if (!m_convCpVars.empty()) {
AstCStmt* const headp = new AstCStmt{fl};
headp->add("double __Vcov = 0.0; double __Vtot = 0.0;");
funcp->addStmtsp(headp);
for (AstVar* const cpVarp : m_convCpVars) {
AstCStmt* const cs = new AstCStmt{fl};
cs->add("{ double __Vc = 0.0; double __Vt = 0.0; ");
cs->add(memberRef(fl, cpVarp));
cs->add(".coverageParts(__Vc, __Vt); __Vcov += __Vc; __Vtot += __Vt; }");
funcp->addStmtsp(cs);
}
int legacyRegular = 0;
for (const BinInfo& bi : m_binInfos) {
if (!bi.binp->binsType().binIsNormal()) continue;
++legacyRegular;
AstCStmt* const cs = new AstCStmt{fl};
cs->add("if (");
cs->add(memberRef(fl, bi.varp));
cs->add(" >= " + std::to_string(bi.atLeast) + ") __Vcov += 1.0;");
funcp->addStmtsp(cs);
}
if (legacyRegular) {
AstCStmt* const cs = new AstCStmt{fl};
cs->add("__Vtot += " + std::to_string(legacyRegular) + ".0;");
funcp->addStmtsp(cs);
}
AstCStmt* const retp = new AstCStmt{fl};
retp->add(new AstVarRef{fl, returnVarp, VAccess::WRITE});
retp->add(" = (__Vtot != 0.0) ? (100.0 * __Vcov / __Vtot) : 100.0;");
funcp->addStmtsp(retp);
return;
}
// Count total bins (Normal only: excludes ignore/illegal/default)
int totalBins = 0;
for (const BinInfo& bi : m_binInfos) {
UINFO(6, " Bin: " << bi.binp->name() << " type=" << bi.binp->binsType().ascii());
if (bi.binp->binsType() != VCoverBinsType::BINS_IGNORE
&& bi.binp->binsType() != VCoverBinsType::BINS_ILLEGAL) {
totalBins++;
}
if (bi.binp->binsType().binIsNormal()) totalBins++;
}
UINFO(4, " Total regular bins: " << totalBins << " of " << m_binInfos.size());
@ -1337,7 +1525,6 @@ class FunctionalCoverageVisitor final : public VNVisitor {
// No coverage to compute - return 100%.
// Any parser-generated initialization of returnVar is overridden by our assignment.
UINFO(4, " Empty covergroup, returning 100.0");
AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var);
funcp->addStmtsp(new AstAssign{fl, new AstVarRef{fl, returnVarp, VAccess::WRITE},
new AstConst{fl, AstConst::RealDouble{}, 100.0}});
UINFO(4, " Added assignment to return 100.0");
@ -1356,11 +1543,8 @@ class FunctionalCoverageVisitor final : public VNVisitor {
// For each regular bin, if count > 0, increment covered_count
for (const BinInfo& bi : m_binInfos) {
// Skip ignore_bins and illegal_bins in coverage calculation
if (bi.binp->binsType() == VCoverBinsType::BINS_IGNORE
|| bi.binp->binsType() == VCoverBinsType::BINS_ILLEGAL) {
continue;
}
// Skip ignore/illegal/default bins in coverage calculation
if (!bi.binp->binsType().binIsNormal()) continue;
// if (bin_count >= at_least) covered_count++;
AstIf* ifp = new AstIf{
@ -1375,9 +1559,6 @@ class FunctionalCoverageVisitor final : public VNVisitor {
funcp->addStmtsp(ifp);
}
// Find the return variable
AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var);
// Calculate coverage: (covered_count / total_bins) * 100.0
// return_var = (double)covered_count / (double)total_bins * 100.0

View File

@ -906,17 +906,20 @@ AstScope* DfgVertex::scopep(ScopeCache& cache, bool tryResultVar) VL_MT_DISABLED
}
}
AstScope* const rootp = v3Global.rootp()->topScopep()->scopep();
AstScope* const constPoolp = v3Global.rootp()->constPoolp()->scopep();
// Note: the recursive invocation can cause a re-hash but that will not invalidate references
AstScope*& resultr = cache[this];
if (!resultr) {
// Mark to prevent infinite recursion on circular graphs - should never be called on such
resultr = reinterpret_cast<AstScope*>(1);
// Find scope based on sources, falling back on the root scope
AstScope* const rootp = v3Global.rootp()->topScopep()->scopep();
// Find scope based on sources, falling back on the root scope,
// also make sure it's not the constant pool scope, which is special.
AstScope* foundp = nullptr;
foreachSource([&](DfgVertex& src) {
AstScope* const scp = src.scopep(cache, true);
if (scp != rootp) {
if (scp != rootp && scp != constPoolp) {
foundp = scp;
return true;
}

View File

@ -234,6 +234,26 @@ private:
addStat("temporaries introduced", m_temporariesIntroduced);
}
};
class V3DfgRemoveSelectsContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
public:
// STATE
VDouble0 m_removedFullWidth; // Number of full width selects removed
VDouble0 m_replacedWithSelFromFull; // Number of selects replaced with sel from full driver
VDouble0 m_replacedWithSelFromPart; // Number of selects replaced with sel from partial driver
VDouble0 m_replacedWithPart; // Number of selects replaced with part of driver
private:
V3DfgRemoveSelectsContext()
: V3DfgSubContext{"RemoveSelects"} {}
~V3DfgRemoveSelectsContext() {
addStat("full width selects removed", m_removedFullWidth);
addStat("replaced with sel from full driver", m_replacedWithSelFromFull);
addStat("replaced with sel from partial driver", m_replacedWithSelFromPart);
addStat("replaced with partial driver", m_replacedWithPart);
}
};
class V3DfgRemoveUnobservableContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
@ -399,6 +419,7 @@ public:
V3DfgPeepholeContext m_peepholeContext;
V3DfgPushDownSelsContext m_pushDownSelsContext;
V3DfgRegularizeContext m_regularizeContext;
V3DfgRemoveSelectsContext m_removeSelectsContext;
V3DfgRemoveUnobservableContext m_removeUnobservableContext;
V3DfgSynthesisContext m_synthContext;

View File

@ -139,6 +139,7 @@ public:
// Returns a Packed type of the given width
static const DfgDataType& packed(uint32_t width) {
UASSERT(width > 0, "Width must be positive");
// Find or create the right sized packed type
const DfgDataType*& entryr = s_packedTypes[width];
if (!entryr) entryr = new DfgDataType{width};

View File

@ -139,6 +139,13 @@ class DataflowOptimize final {
dfg.mergeGraphs(std::move(madeAcyclicComponents));
endOfStage("breakCycles", dfg, cyclicComps);
// Remove redundant selects
V3DfgPasses::removeSelects(dfg, m_ctx.m_removeSelectsContext);
for (std::unique_ptr<DfgGraph>& compp : cyclicComps) {
V3DfgPasses::removeSelects(*compp, m_ctx.m_removeSelectsContext);
}
endOfStage("removeSelects", dfg, cyclicComps);
// Split the acyclic DFG into [weakly] connected components
std::vector<std::unique_ptr<DfgGraph>> acyclicComps = dfg.splitIntoComponents("acyclic");
UASSERT(dfg.size() == 0, "DfgGraph should have become empty");

View File

@ -108,6 +108,78 @@ void V3DfgPasses::removeUnobservable(DfgGraph& dfg, V3DfgContext& dfgCtx) {
}
}
void V3DfgPasses::removeSelects(DfgGraph& dfg, V3DfgRemoveSelectsContext& ctx) {
std::vector<DfgSel*> selps;
for (DfgVertex& vtx : dfg.opVertices()) {
DfgSel* const selp = vtx.cast<DfgSel>();
if (!selp) continue;
selps.push_back(selp);
}
for (DfgSel* const selp : selps) {
FileLine* const flp = selp->fileline();
const DfgDataType& dtype = selp->dtype();
// Remove full width selects
if (selp->fromp()->dtype() == dtype) {
++ctx.m_removedFullWidth;
selp->replaceWith(selp->fromp());
continue;
}
// Push selects through synthesis temporaries only
DfgVarPacked* const varp = selp->fromp()->cast<DfgVarPacked>();
if (!varp || !varp->tmpForp()) continue;
DfgVertex* const srcp = varp->srcp();
if (!srcp) continue;
// Don't inline CReset
if (srcp->is<DfgCReset>()) continue;
const uint32_t lsb = selp->lsb();
const uint32_t msb = lsb + selp->width() - 1;
// If driven whole, select from the driver
if (!srcp->is<DfgSplicePacked>()) {
++ctx.m_replacedWithSelFromFull;
DfgSel* const newSelp = new DfgSel{dfg, flp, dtype};
newSelp->lsb(lsb);
newSelp->fromp(srcp);
selp->replaceWith(newSelp);
continue;
}
// Otherwise attemt to select from the partial driver
DfgSplicePacked* const splicep = srcp->as<DfgSplicePacked>();
DfgVertex* driverp = nullptr;
uint32_t driverLsb = 0;
splicep->foreachDriver([&](DfgVertex& src, const uint32_t dLsb) {
const uint32_t dMsb = dLsb + src.width() - 1;
// If it does not cover the whole searched bit range, move on
if (lsb < dLsb || dMsb < msb) return false;
// Save the driver
driverp = &src;
driverLsb = dLsb;
return true;
});
if (!driverp) continue;
// If partial driver is the whole thing we are looking for, just replace with the driver
if (driverp->dtype() == dtype) {
++ctx.m_replacedWithPart;
selp->replaceWith(driverp);
continue;
}
// Otherwise create a new select from the partial driver
++ctx.m_replacedWithSelFromPart;
DfgSel* const newSelp = new DfgSel{dfg, flp, dtype};
newSelp->lsb(lsb - driverLsb);
newSelp->fromp(driverp);
selp->replaceWith(newSelp);
}
}
void V3DfgPasses::inlineVars(DfgGraph& dfg) {
for (DfgVertexVar& vtx : dfg.varVertices()) {
// Nothing to inline it into

View File

@ -41,6 +41,8 @@ void removeUnobservable(DfgGraph&, V3DfgContext&) VL_MT_DISABLED;
// Synthesize DfgLogic vertices into primitive operations.
// Removes all DfgLogic (even those that were not synthesized).
void synthesize(DfgGraph&, V3DfgContext&) VL_MT_DISABLED;
// Remove redundant selects
void removeSelects(DfgGraph& dfg, V3DfgRemoveSelectsContext& ctx) VL_MT_DISABLED;
// Attempt to make the given cyclic graph into an acyclic, or "less cyclic"
// equivalent. If the returned pointer is null, then no improvement was
// possible on the input graph. Otherwise the returned graph is an improvement

View File

@ -1188,6 +1188,102 @@ class V3DfgPeephole final : public DfgVisitor {
return {fromp, lsb};
}
// Given a pair of vertices, returns a vertex representing the common LSBs of the two,
// and the number of common LSBs. Returns {nullptr, 0} if no common LSBs are found.
std::pair<DfgVertex*, uint32_t> commonLSBs(DfgVertex* ap, DfgVertex* bp) {
if (ap == bp) return {ap, ap->width()};
// If both constants, check LSBs
if (DfgConst* const aConstp = ap->cast<DfgConst>()) {
if (DfgConst* const bConstp = bp->cast<DfgConst>()) {
const V3Number& aNum = aConstp->num();
const V3Number& bNum = bConstp->num();
// Max match is the shorter constant
const uint32_t maxMatch = std::min(aConstp->width(), bConstp->width());
// Check all bits
uint32_t matchWidth = 0;
for (; matchWidth < maxMatch; ++matchWidth) {
if (aNum.bitIs0(matchWidth) != bNum.bitIs0(matchWidth)) break;
}
// Will always return the shorter constant in case it can be used directly
DfgConst* const shorterp = aConstp->width() < bConstp->width() ? aConstp : bConstp;
return {matchWidth ? shorterp : nullptr, matchWidth};
}
}
// If Concat, check against the RHS
if (DfgConcat* const catp = ap->cast<DfgConcat>()) return commonLSBs(catp->rhsp(), bp);
if (DfgConcat* const catp = bp->cast<DfgConcat>()) return commonLSBs(ap, catp->rhsp());
// If selecting the LSBs, check against the source of the Sel
if (DfgSel* const selp = ap->cast<DfgSel>()) {
if (selp->lsb() == 0) {
DfgVertex* const fromp = selp->fromp();
const std::pair<DfgVertex*, uint32_t> common = commonLSBs(fromp, bp);
return {common.first, std::min(common.second, selp->width())};
}
}
if (DfgSel* const selp = bp->cast<DfgSel>()) {
if (selp->lsb() == 0) {
DfgVertex* const fromp = selp->fromp();
const std::pair<DfgVertex*, uint32_t> common = commonLSBs(ap, fromp);
return {common.first, std::min(common.second, selp->width())};
}
}
// Otherwise no common LSBs
return {nullptr, 0};
}
// Given a pair of vertices, returns a vertex representing the common MSBs of the two,
// and the number of common LSBs. Returns {nullptr, 0} if no common LSBs are found.
std::pair<DfgVertex*, uint32_t> commonMSBs(DfgVertex* ap, DfgVertex* bp) {
if (ap == bp) return {ap, ap->width()};
// If both constants, check MSBs
if (DfgConst* const aConstp = ap->cast<DfgConst>()) {
if (DfgConst* const bConstp = bp->cast<DfgConst>()) {
const uint32_t aMsb = aConstp->width() - 1;
const uint32_t bMsb = bConstp->width() - 1;
const V3Number& aNum = aConstp->num();
const V3Number& bNum = bConstp->num();
// Max match is the shorter constant
const uint32_t maxMatch = std::min(aConstp->width(), bConstp->width());
// Check all bits
uint32_t matchWidth = 0;
for (; matchWidth < maxMatch; ++matchWidth) {
if (aNum.bitIs0(aMsb - matchWidth) != bNum.bitIs0(bMsb - matchWidth)) break;
}
// Will always return the shorter constant in case it can be used directly
DfgConst* const shorterp = aMsb < bMsb ? aConstp : bConstp;
return {matchWidth ? shorterp : nullptr, matchWidth};
}
}
// If Concat, check against the LHS
if (DfgConcat* const catp = ap->cast<DfgConcat>()) return commonMSBs(catp->lhsp(), bp);
if (DfgConcat* const catp = bp->cast<DfgConcat>()) return commonMSBs(ap, catp->lhsp());
// If selecting the MSBs, check against the source of the Sel
if (DfgSel* const selp = ap->cast<DfgSel>()) {
DfgVertex* const fromp = selp->fromp();
if (selp->msb() == fromp->width() - 1) {
const std::pair<DfgVertex*, uint32_t> common = commonMSBs(fromp, bp);
return {common.first, std::min(common.second, selp->width())};
}
}
if (DfgSel* const selp = bp->cast<DfgSel>()) {
DfgVertex* const fromp = selp->fromp();
if (selp->msb() == fromp->width() - 1) {
const std::pair<DfgVertex*, uint32_t> common = commonMSBs(ap, fromp);
return {common.first, std::min(common.second, selp->width())};
}
}
// Otherwise no common MSBs
return {nullptr, 0};
}
// VISIT methods
void visit(DfgVertex*) override {}
@ -1789,10 +1885,24 @@ class V3DfgPeephole final : public DfgVisitor {
if (!idxp) return;
DfgVarArray* const varp = vtxp->fromp()->cast<DfgVarArray>();
if (!varp) return;
if (varp->vscp()->varp()->isForced()) return;
if (varp->vscp()->varp()->isSigUserRWPublic()) return;
AstVar* const astVarp = varp->vscp()->varp();
if (astVarp->isForced()) return;
if (astVarp->isSigUserRWPublic()) return;
DfgVertex* const srcp = varp->srcp();
if (!srcp) return;
if (!srcp) {
if (vtxp->isPacked()) {
if (AstInitArray* const iap = VN_CAST(astVarp->valuep(), InitArray)) {
if (AstConst* const valp
= VN_CAST(iap->getIndexDefaultedValuep(idxp->toSizeT()), Const)) {
APPLYING(FOLD_ARRAYSEL_TABLE) {
replace(new DfgConst{m_dfg, valp->fileline(), valp->num()});
return;
}
}
}
}
return;
}
if (DfgSpliceArray* const splicep = srcp->cast<DfgSpliceArray>()) {
DfgVertex* const driverp = splicep->driverAt(idxp->toSizeT());
@ -2834,56 +2944,62 @@ class V3DfgPeephole final : public DfgVisitor {
}
}
if (DfgConcat* const tConcatp = thenp->cast<DfgConcat>()) {
if (DfgConcat* const eConcatp = elsep->cast<DfgConcat>()) {
DfgVertex* const tRhsp = tConcatp->rhsp();
DfgVertex* const tLhsp = tConcatp->lhsp();
DfgVertex* const eRhsp = eConcatp->rhsp();
DfgVertex* const eLhsp = eConcatp->lhsp();
if (isSame(tRhsp, eRhsp)) {
APPLYING(REPLACE_COND_SAME_CAT_RHS) {
DfgCond* const newCondp
= make<DfgCond>(flp, tLhsp->dtype(), condp, tLhsp, eLhsp);
replace(make<DfgConcat>(vtxp, newCondp, tRhsp));
return;
}
}
if (isSame(tLhsp, eLhsp)) {
APPLYING(REPLACE_COND_SAME_CAT_LHS) {
DfgCond* const newCondp
= make<DfgCond>(flp, tRhsp->dtype(), condp, tRhsp, eRhsp);
replace(make<DfgConcat>(vtxp, tLhsp, newCondp));
if (!thenp->is<DfgConst>() && !elsep->is<DfgConst>()) {
const std::pair<DfgVertex*, uint32_t> cLSBs = commonLSBs(thenp, elsep);
if (cLSBs.first) {
APPLYING(REPLACE_COND_COMMON_LSBS) {
// Create new RHS
const uint32_t rWidth = cLSBs.second;
DfgVertex* rhsp = cLSBs.first;
if (rWidth != rhsp->width()) {
const DfgDataType& rDtype = DfgDataType::packed(rWidth);
rhsp = make<DfgSel>(flp, rDtype, rhsp, 0U);
}
// If it's all the same, just replace
if (rhsp->dtype() == vtxp->dtype()) {
// Note this branch can only be hit if rules run in the right order,
// it might have missing code coverage after a refactor.
replace(rhsp);
return;
}
// Create new LHS
const uint32_t lWidth = vtxp->width() - rWidth;
const DfgDataType& lDtype = DfgDataType::packed(lWidth);
DfgVertex* const lThenp = make<DfgSel>(flp, lDtype, thenp, rWidth);
DfgVertex* const lElsep = make<DfgSel>(flp, lDtype, elsep, rWidth);
DfgVertex* const lhsp = make<DfgCond>(flp, lDtype, condp, lThenp, lElsep);
// Replace with concat
replace(make<DfgConcat>(vtxp, lhsp, rhsp));
return;
}
}
if (!tConcatp->hasMultipleSinks()) {
if (DfgConcat* const tRCatp = tConcatp->rhsp()->cast<DfgConcat>()) {
if (!tRCatp->hasMultipleSinks()) {
if (DfgSel* const tLSelp = tConcatp->lhsp()->cast<DfgSel>()) {
if (DfgSel* const tRRSelp = tRCatp->rhsp()->cast<DfgSel>()) {
if (tLSelp->lsb() == tRCatp->width() //
&& tRRSelp->lsb() == 0 //
&& isSame(tLSelp->fromp(), elsep) //
&& isSame(tRRSelp->fromp(), elsep)) {
APPLYING(REPLACE_COND_INSERT) {
DfgVertex* const newTp = tRCatp->lhsp();
DfgVertex* const newEp = make<DfgSel>(
flp, newTp->dtype(), elsep, tRRSelp->width());
DfgCond* const newCp = make<DfgCond>(flp, newTp->dtype(),
condp, newTp, newEp);
replace(make<DfgConcat>(
vtxp, tLSelp,
make<DfgConcat>(tRCatp, newCp, tRRSelp)));
return;
}
}
}
}
const std::pair<DfgVertex*, uint32_t> cMSBs = commonMSBs(thenp, elsep);
if (cMSBs.first) {
APPLYING(REPLACE_COND_COMMON_MSBS) {
// Create new LHS
const uint32_t lWidth = cMSBs.second;
DfgVertex* lhsp = cMSBs.first;
if (lWidth != lhsp->width()) {
const DfgDataType& lDtype = DfgDataType::packed(lWidth);
lhsp = make<DfgSel>(flp, lDtype, lhsp, lhsp->width() - lWidth);
}
// If it's all the same, just replace
if (lhsp->dtype() == vtxp->dtype()) {
// Note this branch can only be hit if rules run in the right order,
// it might have missing code coverage after a refactor.
replace(lhsp);
return;
}
// Create new RHS
const uint32_t rWidth = vtxp->width() - lWidth;
const DfgDataType& rDtype = DfgDataType::packed(rWidth);
DfgVertex* const rThenp = make<DfgSel>(flp, rDtype, thenp, 0U);
DfgVertex* const rElsep = make<DfgSel>(flp, rDtype, elsep, 0U);
DfgVertex* const rhsp = make<DfgCond>(flp, rDtype, condp, rThenp, rElsep);
// Replace with concat
replace(make<DfgConcat>(vtxp, lhsp, rhsp));
return;
}
}
}

View File

@ -27,6 +27,7 @@
// Enumeration of each peephole optimization. Must be kept in sorted order (enforced by tests).
// clang-format off
#define FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(macro) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ARRAYSEL_TABLE) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY_LHS_OF_RHS) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY_RHS_OF_LHS) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_BINARY) \
@ -112,17 +113,16 @@
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_SAME_REP_ON_RHS) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_COMMON_LSBS) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_COMMON_MSBS) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ONES_ZERO) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ONE_ZERO) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ZERO_ONE) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ZERO_ONES) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_DEC) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_INC) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_INSERT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_OR_THEN_COND_LHS) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_OR_THEN_COND_RHS) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_CAT_LHS) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_CAT_RHS) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_COND_ELSE) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_COND_THEN) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_THEN_OR_LHS) \

View File

@ -299,6 +299,7 @@ public:
void fromp(DfgVertex* vtxp) { srcp(vtxp); }
uint32_t lsb() const { return m_lsb; }
void lsb(uint32_t value) { m_lsb = value; }
uint32_t msb() const { return m_lsb + width() - 1; }
};
class DfgUnitArray final : public DfgVertexUnary {

View File

@ -18,8 +18,6 @@
#include "V3EmitCFunc.h"
#include "V3TSP.h"
#include <map>
#include <vector>

View File

@ -573,8 +573,14 @@ public:
+ "\", " + std::to_string(nodep->fileline()->lineno()) + ")->"),
memberVarp, resetp->constructing());
} else {
AstVar* const varp = VN_AS(fromp, NodeVarRef)->varp();
emitVarReset("", varp, resetp->constructing());
AstNodeVarRef* const fromVarRefp = VN_AS(fromp, NodeVarRef);
AstVar* const varp = fromVarRefp->varp();
const string prefix
= fromVarRefp->selfPointer().isEmpty()
? ""
: dereferenceString(
VN_AS(fromp, NodeVarRef)->selfPointerProtect(m_useSelfForThis));
emitVarReset(prefix, varp, resetp->constructing());
}
return;
}
@ -1128,7 +1134,7 @@ public:
}
void visit(AstFFlush* nodep) override {
if (!nodep->filep()) {
putns(nodep, "Verilated::runFlushCallbacks();\n");
putns(nodep, "VL_FFLUSH_MT();");
} else {
putns(nodep, "VL_FFLUSH_I(");
iterateAndNextConstNull(nodep->filep());

View File

@ -689,6 +689,7 @@ class EmitCHeader final : public EmitCConstInit {
if (v3Global.opt.mtasks()) puts("#include \"verilated_threads.h\"\n");
if (v3Global.opt.savable()) puts("#include \"verilated_save.h\"\n");
if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n");
if (v3Global.opt.coverage()) puts("#include \"verilated_covergroup.h\"\n");
if (v3Global.usesTiming()) puts("#include \"verilated_timing.h\"\n");
if (v3Global.useRandomizeMethods()) puts("#include \"verilated_random.h\"\n");
if (v3Global.usesForce()) puts("#include \"verilated_force.h\"\n");

View File

@ -70,6 +70,7 @@ class EmitCModel final : public EmitCFunc {
if (v3Global.opt.mtasks()) puts("#include \"verilated_threads.h\"\n");
if (v3Global.opt.savable()) puts("#include \"verilated_save.h\"\n");
if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n");
if (v3Global.opt.coverage()) puts("#include \"verilated_covergroup.h\"\n");
if (v3Global.dpi()) puts("#include \"svdpi.h\"\n");
// Declare foreign instances up front to make C++ happy

View File

@ -30,6 +30,7 @@
#include "V3Ast.h"
#include "V3Control.h"
#include "V3Graph.h"
#include "V3UniqueNames.h"
#include <algorithm>
#include <cctype>
@ -987,14 +988,23 @@ class FsmDetectVisitor final : public VNVisitor {
return assp;
}
static AstVarRef* tryExtractVarRef(AstNodeExpr* const exprp) {
AstVarRef* const varp = VN_CAST(AstArraySel::baseFromp(exprp, true), VarRef);
if (!varp) {
exprp->v3warn(COVERIGN,
"Ignoring unsupported: FSM coverage with " << exprp->prettyTypeName());
return nullptr;
}
return varp;
}
static AstNodeAssign* nodeStateVarAssign(AstNode* nodep, AstVarScope*& stateVscp,
AstVarScope*& fromVscp) {
AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign);
if (!assp) return nullptr;
AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef);
UASSERT_OBJ(lhsp, assp, "register commit lhs should be normalized to a VarRef");
AstVarRef* const lhsp = tryExtractVarRef(assp->lhsp());
AstVarRef* const rhsp = VN_CAST(assp->rhsp(), VarRef);
if (!rhsp) return nullptr;
if (!rhsp || !lhsp) return nullptr;
stateVscp = lhsp->varScopep();
fromVscp = rhsp->varScopep();
return assp;
@ -1006,11 +1016,9 @@ class FsmDetectVisitor final : public VNVisitor {
FsmStateValue& resetValue) {
AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign);
if (!assp) return nullptr;
AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef);
UASSERT_OBJ(lhsp, assp,
"conditional register commit lhs should be normalized to a VarRef");
AstVarRef* const lhsp = tryExtractVarRef(assp->lhsp());
AstCond* const rhsp = VN_CAST(assp->rhsp(), Cond);
if (!rhsp) return nullptr;
if (!rhsp || !lhsp) return nullptr;
if (AstVarRef* const elsep = VN_CAST(rhsp->elsep(), VarRef)) {
if (constValueStatus(rhsp->thenp(), resetValue) != ConstValueStatus::OK)
return nullptr;
@ -1033,7 +1041,7 @@ class FsmDetectVisitor final : public VNVisitor {
FsmStateValue& value) {
AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign);
if (!assp) return nullptr;
AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef);
AstVarRef* const lhsp = VN_CAST(AstArraySel::baseFromp(assp->lhsp(), true), VarRef);
UASSERT_OBJ(lhsp, assp,
"direct constant state assignment lhs should be normalized to a VarRef");
if (constValueStatus(assp->rhsp(), value) != ConstValueStatus::OK) return nullptr;
@ -1220,7 +1228,8 @@ class FsmDetectVisitor final : public VNVisitor {
AstVarRef* vrefp = VN_CAST(eqp->lhsp(), VarRef);
AstNodeExpr* valuep = eqp->rhsp();
if (!vrefp) {
vrefp = VN_AS(eqp->rhsp(), VarRef);
vrefp = tryExtractVarRef(eqp->rhsp());
if (!vrefp) { return false; }
valuep = eqp->lhsp();
}
@ -2082,6 +2091,7 @@ public:
class FsmLowerVisitor final {
// STATE - across all visitors
const FsmState& m_state;
V3UniqueNames m_fsmBuildNames;
// METHODS
// Rebuild a state-typed constant using the tracked state variable
@ -2133,8 +2143,8 @@ class FsmLowerVisitor final {
AstNodeModule* const modp = scopep->modp();
AstNodeDType* const prevDTypep = scopep->findLogicDType(
sampleVscp->width(), sampleVscp->width(), sampleVscp->dtypep()->numeric());
AstVarScope* const prevVscp
= scopep->createTemp("__Vfsmcov_prev__" + stateVscp->varp()->shortName(), prevDTypep);
const std::string tmpName = m_fsmBuildNames.get(stateVscp->varp()->shortName());
AstVarScope* const prevVscp = scopep->createTemp(tmpName, prevDTypep);
// The saved previous-state temp crosses the scheduler's pre/post split
// in the same way as Verilator's built-in NBA shadow variables, so keep
// both vars marked as post-life participants for stable MT ordering.
@ -2283,7 +2293,8 @@ public:
// concrete coverage instrumentation while the saved scoped pointers are
// still valid in the same pass.
explicit FsmLowerVisitor(const FsmState& state)
: m_state{state} {
: m_state{state}
, m_fsmBuildNames{"__Vfsmcov_prev"} {
for (const DetectedFsm& fsm : m_state.fsms()) { buildOne(*fsm.graphp); }
}
};

View File

@ -543,89 +543,62 @@ class GateInline final {
// Logic block with pending substitutions are stored in this map, together with their ordinal
std::unordered_map<AstNode*, size_t> m_hasPending;
size_t m_statInlined = 0; // Statistic tracking - signals inlined
size_t m_statRefs = 0; // Statistic tracking
size_t m_statExcluded = 0; // Statistic tracking
size_t m_statNotInlined = 0; // Statistic tracking - signals not inlined due to cost
size_t m_statRefs = 0; // Statistic tracking - number of input variable references replaced
// METHODS
static bool isCheapWide(const AstNodeExpr* exprp) {
static bool isCheap(const AstNodeExpr* exprp) {
// Constant is cheap
if (VN_IS(exprp, Const)) return true;
// Variable reference is cheap
if (VN_IS(exprp, NodeVarRef)) return true;
// AstSel is cheap if the fromp is cheap, and not a wide needing bit swizzling
if (const AstSel* const selp = VN_CAST(exprp, Sel)) {
if (!isCheap(selp->fromp())) return false;
if (!selp->isWide()) return true;
if (!VN_IS(selp->lsbp(), Const)) return false;
if (selp->lsbConst() % VL_EDATASIZE != 0) return false;
exprp = selp->fromp();
return true;
}
if (const AstArraySel* const aselp = VN_CAST(exprp, ArraySel)) exprp = aselp->fromp();
return VN_IS(exprp, Const) || VN_IS(exprp, NodeVarRef);
}
static bool excludedWide(GateVarVertex* const vVtxp, const AstNodeExpr* const rhsp) {
// Handle wides with logic drivers that are too wide for V3Expand.
if (!vVtxp->varScp()->isWide() //
|| vVtxp->varScp()->widthWords() <= v3Global.opt.expandLimit() //
|| vVtxp->inEmpty() //
|| isCheapWide(rhsp))
return false;
const GateLogicVertex* const lVtxp
= vVtxp->inEdges().frontp()->fromp()->as<GateLogicVertex>();
// Exclude from inlining variables READ multiple times.
// To decouple actives thus simplifying scheduling, exclude only those
// VarRefs that are referenced under the same active as they were assigned.
if (const AstActive* const primaryActivep = lVtxp->activep()) {
size_t reads = 0;
for (const V3GraphEdge& edge : vVtxp->outEdges()) {
const GateLogicVertex* const lvp = edge.top()->as<GateLogicVertex>();
if (lvp->activep() != primaryActivep) continue;
reads += edge.weight();
if (reads > 1) return true;
}
// AstArraySel is cheap if the fromp is cheap
if (const AstArraySel* const aselp = VN_CAST(exprp, ArraySel)) {
return isCheap(aselp->fromp());
}
// Otherwise it is not cheap
return false;
}
bool shouldInline(GateVarVertex* vVtxp, GateLogicVertex* lVtxp, size_t nReads,
AstNodeExpr* substp, bool allowMultiIn) {
AstVarScope* const vscp = vVtxp->varScp();
// Always inline constants
if (VN_IS(substp, Const)) return true;
// Don't inline non-constant static initializers
// Don't inline non-constant static initializers - these are scheduled differently
if (lVtxp->staticInit()) return false;
// Inline simple variable references
if (VN_IS(substp, VarRef)) return true;
// Only inline arrays if a simple variable or constant
if (VN_IS(vscp->dtypep()->skipRefp(), UnpackArrayDType)) return false;
// Inline constant array selects
if (VN_IS(substp, ArraySel) && nReads <= 1) return true;
// Don't inline expensive wide operations
if (excludedWide(vVtxp, substp)) {
++m_statExcluded;
UINFO(9, "Gate inline exclude '" << vVtxp->name() << "'");
vVtxp->clearReducible("Excluded wide"); // Check once.
return false;
}
if (nReads == 0) {
// Reads no variables, likely unfolded constant expression
return true;
} else if (nReads == 1) {
// Reads one variable
return true;
} else {
// Reads more two or more variables
if (!allowMultiIn) return false;
// Do it if not used, or used only once, ignoring slow code
int n = 0;
for (V3GraphEdge& edge : vVtxp->outEdges()) {
const GateLogicVertex* const dstVtxp = edge.top()->as<GateLogicVertex>();
// Ignore slow code, or if the destination is not used
if (dstVtxp->slow()) continue;
if (dstVtxp->outEmpty() && !dstVtxp->consumed()) continue;
n += edge.weight();
if (n > 1) return false;
if (VN_IS(vVtxp->varScp()->dtypep()->skipRefp(), UnpackArrayDType)) return false;
// Inline if reads no variables - unfolded constant expression, nullary builtin e.g.: $time
if (nReads == 0) return true;
// If it reads one variable, inline if not wide, or if cheap
if (nReads == 1 && (!substp->isWide() || isCheap(substp))) return true;
// Don't inline on first round if reads more than one variable
if (nReads > 1 && !allowMultiIn) return false;
// Reads multiple variables, or is expensive to compute.
// Inline if used only once, ignoring slow code, or dead code that can be deleted.
int n = 0;
for (V3GraphEdge& edge : vVtxp->outEdges()) {
const GateLogicVertex* const dstVtxp = edge.top()->as<GateLogicVertex>();
// Ignore slow code, or if the destination is not used
if (dstVtxp->slow()) continue;
if (dstVtxp->outEmpty() && !dstVtxp->consumed()) continue;
n += edge.weight();
if (n > 1) {
++m_statNotInlined;
return false;
}
return true;
}
return true;
}
void recordSubstitution(AstVarScope* vscp, AstNodeExpr* substp, AstNode* logicp) {
@ -724,7 +697,7 @@ class GateInline final {
if (!okVisitor.varAssigned(vVtxp->varScp())) continue;
// Expression we are considering to substitute with
AstNodeExpr* const substp = okVisitor.substitutionp();
AstNodeExpr* const substp = V3Const::constifyEdit(okVisitor.substitutionp());
// Number of variables read by the substitution
const size_t nReads = okVisitor.readVscps().size();
@ -832,9 +805,9 @@ class GateInline final {
}
~GateInline() {
V3Stats::addStat("Optimizations, Gate sigs deleted", m_statInlined);
V3Stats::addStat("Optimizations, Gate inputs replaced", m_statRefs);
V3Stats::addStat("Optimizations, Gate excluded wide expressions", m_statExcluded);
V3Stats::addStat("Optimizations, Gate signals inlined", m_statInlined);
V3Stats::addStat("Optimizations, Gate signals not inlined due to cost", m_statNotInlined);
V3Stats::addStat("Optimizations, Gate reads replaced", m_statRefs);
}
public:

View File

@ -245,6 +245,7 @@ std::vector<std::string> V3Global::verilatedCppFiles() {
if (v3Global.opt.vpi()) result.emplace_back("verilated_vpi.cpp");
if (v3Global.opt.savable()) result.emplace_back("verilated_save.cpp");
if (v3Global.opt.coverage()) result.emplace_back("verilated_cov.cpp");
if (v3Global.opt.coverage()) result.emplace_back("verilated_covergroup.cpp");
for (const string& base : v3Global.opt.traceSourceBases())
result.emplace_back(base + "_c.cpp");
if (v3Global.usesProbDist()) result.emplace_back("verilated_probdist.cpp");

View File

@ -60,7 +60,7 @@ public:
class LifeVarEntry final {
// Last assignment to this varscope, nullptr if no longer relevant
AstNodeStmt* m_assignp = nullptr;
AstNodeStmt* m_assignp = nullptr; // Last simple assignment
AstConst* m_constp = nullptr; // Known constant value
bool m_isNew = true; // Is just created
// First access was a set (and thus block above may have a set that can be deleted
@ -77,6 +77,17 @@ public:
m_isNew = false;
m_setBeforeUse = setBeforeUse;
}
string ascii() const { // LCOV_EXCL_START
std::ostringstream os;
os << "[Life ";
if (m_isNew) os << " isNew";
if (m_setBeforeUse) os << " setBeforeUse";
if (m_everSet) os << " everSet";
if (m_assignp) os << " assignp=" << AstNode::nodeAddr(m_assignp);
if (m_constp) os << " constp=" << AstNode::nodeAddr(m_constp);
os << "]";
return os.str();
} // LCOV_EXCL_STOP
void simpleAssign(AstNodeStmt* nodep) { // New simple A=.... assignment
UASSERT_OBJ(!m_isNew, nodep, "Uninitialized new entry");
@ -84,7 +95,10 @@ public:
m_constp = nullptr;
m_everSet = true;
if (AstNodeAssign* const assp = VN_CAST(nodep, Assign)) {
if (VN_IS(assp->rhsp(), Const)) m_constp = VN_AS(assp->rhsp(), Const);
if (VN_IS(assp->rhsp(), Const)) {
m_constp = VN_AS(assp->rhsp(), Const);
UINFO(9, "assign-const " << assp->rhsp() << " = " << m_constp);
}
}
}
void complexAssign() { // A[x]=... or some complicated assignment
@ -179,6 +193,7 @@ public:
// Aha, variable is constant; substitute in.
// We'll later constant propagate
UINFO(4, " replaceconst: " << varrefp);
UINFO(9, " replaceval: " << constp);
varrefp->replaceWith(constp->cloneTree(false));
m_replacedVref = true;
VL_DO_DANGLING(varrefp->deleteTree(), varrefp);
@ -264,7 +279,9 @@ class LifeVisitor final : public VNVisitor {
LifeBlock* m_lifep = nullptr; // Current active lifetime map for current scope
// METHODS
void setNoopt() {
void setNoopt(const char* reasonp) {
(void)reasonp;
// UINFO(9, "setNoopt " << reasonp);
m_noopt = true;
m_lifep->clear();
}
@ -310,7 +327,7 @@ class LifeVisitor final : public VNVisitor {
void visit(AstNodeAssign* nodep) override {
if (nodep->isTimingControl() || VN_IS(nodep, AssignForce)) {
// V3Life doesn't understand time sense nor force assigns - don't optimize
setNoopt();
setNoopt("timing|force");
if (nodep->isTimingControl()) m_containsTiming = true;
iterateChildren(nodep);
return;
@ -325,7 +342,7 @@ class LifeVisitor final : public VNVisitor {
// V3Life doesn't understand time sense
if (nodep->isTimingControl()) {
// Don't optimize
setNoopt();
setNoopt("assigndly");
m_containsTiming = true;
}
// Don't treat as normal assign
@ -337,19 +354,19 @@ class LifeVisitor final : public VNVisitor {
UINFO(4, " IF " << nodep);
// Condition is part of PREVIOUS block
iterateAndNextNull(nodep->condp());
LifeBlock* const prevLifep = m_lifep;
LifeBlock* const ifLifep = new LifeBlock{prevLifep, m_statep};
LifeBlock* const elseLifep = new LifeBlock{prevLifep, m_statep};
LifeBlock* const ifLifep = new LifeBlock{m_lifep, m_statep};
LifeBlock* const elseLifep = new LifeBlock{m_lifep, m_statep};
{
VL_RESTORER(m_lifep);
m_lifep = ifLifep;
iterateAndNextNull(nodep->thensp());
}
{
VL_RESTORER(m_lifep);
m_lifep = elseLifep;
iterateAndNextNull(nodep->elsesp());
}
m_lifep = prevLifep;
UINFO(4, " join ");
UINFO(4, " join " << nodep);
// Find sets on both flows
m_lifep->dualBranch(ifLifep, elseLifep);
// For the next assignments, clear any variables that were read or written in the block
@ -357,6 +374,7 @@ class LifeVisitor final : public VNVisitor {
elseLifep->lifeToAbove();
VL_DO_DANGLING(delete ifLifep, ifLifep);
VL_DO_DANGLING(delete elseLifep, elseLifep);
UINFO(4, " if-done " << nodep);
}
void visit(AstLoop* nodep) override {
// Similar problem to AstJumpBlock, don't optimize loop bodies - most are unrolled
@ -366,14 +384,14 @@ class LifeVisitor final : public VNVisitor {
VL_RESTORER(m_noopt);
VL_RESTORER(m_lifep);
m_lifep = new LifeBlock{m_lifep, m_statep};
setNoopt();
setNoopt("loop");
iterateAndNextNull(nodep->stmtsp());
UINFO(4, " joinloop");
// For the next assignments, clear any variables that were read or written in the block
m_lifep->lifeToAbove();
VL_DO_DANGLING(delete m_lifep, m_lifep);
}
if (m_containsTiming) setNoopt();
if (m_containsTiming) setNoopt("timing");
}
void visit(AstJumpBlock* nodep) override {
// As with Loop's we can't predict if a JumpGo will kill us or not
@ -384,14 +402,14 @@ class LifeVisitor final : public VNVisitor {
VL_RESTORER(m_noopt);
VL_RESTORER(m_lifep);
m_lifep = new LifeBlock{m_lifep, m_statep};
setNoopt();
setNoopt("jumpblock");
iterateAndNextNull(nodep->stmtsp());
UINFO(4, " joinjump");
// For the next assignments, clear any variables that were read or written in the block
m_lifep->lifeToAbove();
VL_DO_DANGLING(delete m_lifep, m_lifep);
}
if (m_containsTiming) setNoopt();
if (m_containsTiming) setNoopt("timing");
}
void visit(AstNodeCCall* nodep) override {
// UINFO(4, " CCALL " << nodep);
@ -399,7 +417,7 @@ class LifeVisitor final : public VNVisitor {
// Enter the function and trace it
// else is non-inline or public function we optimize separately
if (nodep->funcp()->entryPoint()) {
setNoopt();
setNoopt("ccall");
} else {
m_tracingCall = true;
iterate(nodep->funcp());
@ -409,8 +427,8 @@ class LifeVisitor final : public VNVisitor {
// UINFO(4, " CFUNC " << nodep);
if (!m_tracingCall && !nodep->entryPoint()) return;
m_tracingCall = false;
if (nodep->recursive()) setNoopt();
if (nodep->noLife()) setNoopt();
if (nodep->recursive()) setNoopt("recursive");
if (nodep->noLife()) setNoopt("nolife");
if (nodep->dpiImportPrototype() && !nodep->dpiPure()) {
m_sideEffect = true; // If appears on assign RHS, don't ever delete the assignment
}
@ -429,7 +447,7 @@ class LifeVisitor final : public VNVisitor {
void visit(AstNode* nodep) override {
if (nodep->isTimingControl()) {
// V3Life doesn't understand time sense - don't optimize
setNoopt();
setNoopt("timing");
m_containsTiming = true;
}
iterateChildren(nodep);

View File

@ -1727,7 +1727,6 @@ class LinkDotFindVisitor final : public VNVisitor {
newvarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
newvarp->funcReturn(true);
newvarp->trace(false); // Not user visible
newvarp->attrIsolateAssign(nodep->attrIsolateAssign());
nodep->fvarp(newvarp);
// Explicit insert required, as the var name shadows the upper level's task name
m_statep->insertSym(m_curSymp, newvarp->name(), newvarp, nullptr /*classOrPackagep*/);

View File

@ -262,6 +262,9 @@ class LinkJumpVisitor final : public VNVisitor {
if (it != m_beginDisableBegins.end()) return it->second;
AstBegin* const beginBodyp = new AstBegin{fl, "", nullptr, false};
// Disable-by-name rewrites kill this detached block-body process, so mark it as process
// backed to ensure fork/join kill-accounting hooks are always emitted.
beginBodyp->setNeedProcess();
if (beginp->stmtsp()) beginBodyp->addStmtsp(beginp->stmtsp()->unlinkFrBackWithNext());
AstFork* const forkp = new AstFork{fl, VJoinType::JOIN};

View File

@ -291,6 +291,7 @@ void V3LinkLevel::wrapTopCell(AstNetlist* rootp) {
varp->sigPublic(true); // User needs to be able to get to it...
oldvarp->primaryIO(false);
varp->primaryIO(true);
varp->icoMaybeWritten(oldvarp->icoMaybeWritten());
if (varp->isRef() || varp->isConstRef()) {
varp->v3warn(E_UNSUPPORTED,
"Unsupported: ref/const ref as primary input/output: "

View File

@ -606,10 +606,6 @@ class LinkParseVisitor final : public VNVisitor {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
m_varp->sigUserRWPublic(true);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->attrType() == VAttrType::VAR_ISOLATE_ASSIGNMENTS) {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
m_varp->attrIsolateAssign(true);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->attrType() == VAttrType::VAR_SFORMAT) {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
m_varp->attrSFormat(true);

View File

@ -1077,6 +1077,9 @@ void V3Options::notify() VL_MT_DISABLED {
// Preprocessor defines based on options used
if (timing().isSetTrue()) V3PreShell::defineCmdLine("VERILATOR_TIMING", "1");
// If VPI is used, and no explicit ico change detect option was passed, disable it by default
if (m_vpi && m_fIcoChangeDetect.isDefault()) m_fIcoChangeDetect.setTrueOrFalse(false);
// === Leave last
// Mark options as available
m_available = true;
@ -1483,6 +1486,9 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc,
DECL_OPTION("-ffunc-opt-balance-cat", FOnOff, &m_fFuncBalanceCat);
DECL_OPTION("-ffunc-opt-split-cat", FOnOff, &m_fFuncSplitCat);
DECL_OPTION("-fgate", FOnOff, &m_fGate);
DECL_OPTION("-fico-change-detect", CbFOnOff, [this](bool flag) { //
m_fIcoChangeDetect.setTrueOrFalse(flag);
});
DECL_OPTION("-finline", FOnOff, &m_fInline);
DECL_OPTION("-finline-funcs", FOnOff, &m_fInlineFuncs);
DECL_OPTION("-finline-funcs-eager", FOnOff, &m_fInlineFuncsEager);

View File

@ -410,6 +410,8 @@ private:
bool m_fFuncBalanceCat = true; // main switch: -fno-func-balance-cat: expansion of C macros
bool m_fFuncSplitCat = true; // main switch: -fno-func-split-cat: expansion of C macros
bool m_fGate; // main switch: -fno-gate: gate wire elimination
// main switch: -fno-ico-change-detect: input change detection optimization
VOptionBool m_fIcoChangeDetect{VOptionBool::OPT_DEFAULT_TRUE};
bool m_fInline; // main switch: -fno-inline: module inlining
bool m_fInlineFuncs = true; // main switch: -fno-inline-funcs: function inlining
bool m_fInlineFuncsEager = true; // main switch: -fno-inline-funcs-eager: don't inline eagerly
@ -745,6 +747,7 @@ public:
bool fFuncSplitCat() const { return m_fFuncSplitCat; }
bool fFunc() const { return fFuncSplitCat() || fFuncBalanceCat(); }
bool fGate() const { return m_fGate; }
VOptionBool fIcoChangeDetect() const { return m_fIcoChangeDetect; }
bool fInline() const { return m_fInline; }
bool fInlineFuncs() const { return m_fInlineFuncs; }
bool fInlineFuncsEager() const { return m_fInlineFuncsEager; }

View File

@ -1509,6 +1509,41 @@ class ConstraintExprVisitor final : public VNVisitor {
VL_DO_DANGLING(nodep->deleteTree(), nodep);
iterate(sump);
}
void visit(AstRedXor* nodep) override {
if (editFormat(nodep)) return;
// Build popcount expansion: (extract x 1 1) ^ (extract x 2 2) ^ ...
FileLine* const fl = nodep->fileline();
AstNodeExpr* const argp = nodep->lhsp()->unlinkFrBack();
AstNodeExpr* redxorp = new AstSel{fl, argp, 0, 1};
redxorp->user1(true);
for (int i = 1; i < argp->width(); i++) {
AstSel* const selp = new AstSel{fl, argp->cloneTreePure(false), i, 1};
selp->user1(true);
redxorp = new AstXor{fl, redxorp, selp};
redxorp->user1(true);
}
nodep->replaceWith(redxorp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
iterate(redxorp);
}
void visit(AstRedAnd* nodep) override {
if (editFormat(nodep)) return;
// Convert to (~x == 0)
FileLine* const fl = nodep->fileline();
AstNodeExpr* const argp = nodep->lhsp()->unlinkFrBack();
const V3Number numZero{fl, argp->width(), 0};
AstNodeExpr* const negp = new AstNot{fl, argp};
negp->user1(true);
AstNodeExpr* const eqp = new AstEq{fl, negp, new AstConst{fl, numZero}};
eqp->user1(true);
nodep->replaceWith(eqp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
iterate(eqp);
}
void visit(AstRedOr* nodep) override {
if (editFormat(nodep)) return;
// Convert to (x != 0)

View File

@ -51,8 +51,6 @@ class SampledVisitor final : public VNVisitor {
AstVar* const newvarp = new AstVar{flp, VVarType::MODULETEMP, newvarname, varp->dtypep()};
m_scopep->modp()->addStmtsp(newvarp);
AstVarScope* const newvscp = new AstVarScope{flp, m_scopep, newvarp};
newvarp->direction(VDirection::INPUT); // Inform V3Sched that it will be driven later
newvarp->primaryIO(true);
newvarp->sampled(true);
vscp->user1p(newvscp);
m_scopep->addVarsp(newvscp);

View File

@ -529,8 +529,46 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
+ entry.m_memberp->name());
}
// Create the input change detect SenTrees.
// If there is a lot of combinationallogic hanging of the top level inputs, we can save
// a lot of work by only evaluating it if an input has actually changed. This in
// paticular helps hierarchical models partitioned across combinaitonal boundaries.
// The change detect itself should be fairly cheap otherwise so alway do it.
// For correctness, don't create a change detect for top level inputs also written
// by the design, as the change detect 'previous value' would get out of sync.
// Also omit a SenTree for types that don't have the required '!=' operator.
// Any signal that does not have an explicit change detect trigger will fall back to
// using the 'first iteration' trigger, same as if this optimization was disabled.
std::unordered_map<const AstVarScope*, AstSenTree*> inp2changedp;
std::vector<AstSenTree*> icoChangeSenTreeps;
if (v3Global.opt.fIcoChangeDetect().isTrue()) {
FileLine* const flp = netlistp->fileline();
AstScope* const scopep = netlistp->topScopep()->scopep();
for (AstVarScope* vscp = scopep->varsp(); vscp; vscp = VN_AS(vscp->nextp(), VarScope)) {
// Only for top level ports, assume outputs don't change externally
if (!vscp->varp()->isPrimaryInish()) continue;
// Don't do if written by the design - wouldn't update the change detect 'prev' value
if (vscp->varp()->icoMaybeWritten()) continue;
// Don't do if forceable, as we can't see the actual value - this is belt and braces
if (vscp->varp()->isForced()) continue;
// Can't handle unpacked arrays (they have special types when primary input)
if (VN_IS(vscp->dtypep()->skipRefp(), UnpackArrayDType)) continue;
// Similarly to arrays, can't handle SystemC types
if (vscp->varp()->isSc()) continue;
// Create a sen tree triggered when this input changes
AstSenTree*& senTreepr = inp2changedp[vscp];
UASSERT_OBJ(!senTreepr, vscp, "Duplicate input change detect trigger");
AstVarRef* const refp = new AstVarRef{flp, vscp, VAccess::READ};
AstSenItem* const senItemp = new AstSenItem{flp, VEdgeType::ET_CHANGED, refp};
senTreepr = new AstSenTree{flp, senItemp};
icoChangeSenTreeps.push_back(senTreepr);
}
}
V3Stats::addStat("Scheduling, 'ico' change detect triggers", icoChangeSenTreeps.size());
// Gather the relevant sensitivity expressions and create the trigger kit
const auto& senTreeps = getSenTreesUsedBy({&logic});
std::vector<const AstSenTree*> senTreeps = getSenTreesUsedBy({&logic});
senTreeps.insert(senTreeps.end(), icoChangeSenTreeps.begin(), icoChangeSenTreeps.end());
const TriggerKit trigKit = TriggerKit::create(netlistp, initFuncp, senExprBuilder, {},
senTreeps, "ico", extraTriggers, false, false);
std::ignore = senExprBuilder.getAndClearResults();
@ -543,14 +581,14 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
// Remap sensitivities
remapSensitivities(logic, trigKit.mapVec());
for (auto& pair : inp2changedp) pair.second = trigKit.mapVec().at(pair.second);
// Create the inverse map from trigger ref AstSenTree to original AstSenTree
V3Order::TrigToSenMap trigToSen;
invertAndMergeSenTreeMap(trigToSen, trigKit.mapVec());
// The trigger top level inputs (first iteration)
AstSenTree* const inputChanged
= trigKit.newExtraTriggerSenTree(trigKit.vscp(), firstIterationTrigger);
// The 'first iteration' trigger for top level inputs - lazy constructed only if needed
AstSenTree* firstIterTriggerp = nullptr;
// The DPI Export trigger
AstSenTree* const dpiExportTriggered
@ -565,9 +603,19 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
netlistp, {&logic}, trigToSen, "ico", false, false,
[&](const AstVarScope* vscp, std::vector<AstSenTree*>& out) {
AstVar* const varp = vscp->varp();
if (varp->isPrimaryInish() || varp->isSigUserRWPublic()) {
out.push_back(inputChanged);
// If it has an explicit change detect trigger, use that,
// otherwise fall back to using the 'first iteration' trigger
auto it = inp2changedp.find(vscp);
if (it != inp2changedp.end()) {
out.push_back(it->second);
} else if (varp->isPrimaryInish() || varp->isSigUserRWPublic() || varp->sampled()) {
if (!firstIterTriggerp) {
firstIterTriggerp
= trigKit.newExtraTriggerSenTree(trigKit.vscp(), firstIterationTrigger);
}
out.push_back(firstIterTriggerp);
}
// Add other triggers
if (varp->isWrittenByDpi()) out.push_back(dpiExportTriggered);
if (vscp->varp()->sensIfacep() || vscp->varp()->isVirtIface()) {
const auto& ifaceTriggered
@ -593,8 +641,14 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
// Work statements: Invoke the 'ico' function
util::callVoidFunc(icoFuncp));
// Add the first iteration trigger to the trigger computation function
trigKit.addExtraTriggerAssignment(icoLoop.firstIterp, firstIterationTrigger, false);
// Add the first iteration trigger to the trigger computation function - if used
if (firstIterTriggerp) {
trigKit.addExtraTriggerAssignment(icoLoop.firstIterp, firstIterationTrigger, false);
}
// Release temporary input change detect SenTrees
for (AstSenTree* const senTreep : icoChangeSenTreeps) senTreep->deleteTree();
icoChangeSenTreeps.clear();
return icoLoop.stmtsp;
}

View File

@ -351,8 +351,7 @@ std::string reportLoopVars(FileLine* /*warnFl*/, Graph* graphp, SchedAcyclicVarV
if (splittable) {
ss << V3Error::warnMore()
<< "... Suggest add /*verilator split_var*/ or /*verilator "
"isolate_assignments*/ to appropriate variables above.\n";
<< "... Suggest add /*verilator split_var*/ to appropriate variables above.\n";
}
V3Stats::addStat("Scheduling, split_var, candidates", splittable);
return ss.str();

View File

@ -150,8 +150,8 @@ public:
: SchedReplicateVertex{graphp}
, m_vscp{vscp} {
// Top level inputs are
if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->isWrittenByDpi()
|| varp()->sensIfacep() || varp()->isVirtIface()) {
if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->sampled()
|| varp()->isWrittenByDpi() || varp()->sensIfacep() || varp()->isVirtIface()) {
addDrivingRegions(INPUT);
}
// Currently we always execute suspendable processes at the beginning of

View File

@ -1,195 +0,0 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Break always into separate statements to reduce temps
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
// V3SplitAs's Transformations:
//
// Search each ALWAYS for a VARREF lvalue with a /*isolate_assignments*/ attribute
// If found, color statements with both, assignment to that varref, or other assignments.
// Replicate the Always, and remove mis-colored duplicate code.
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3SplitAs.h"
#include "V3Stats.h"
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// Find all split variables in a block
class SplitAsFindVisitor final : public VNVisitorConst {
// STATE - across all visitors
AstVarScope* m_splitVscp = nullptr; // Variable we want to split
// METHODS
void visit(AstVarRef* nodep) override {
if (nodep->access().isWriteOrRW() && !m_splitVscp && nodep->varp()->attrIsolateAssign()) {
m_splitVscp = nodep->varScopep();
}
}
void visit(AstExprStmt* nodep) override {
// A function call inside the splitting assignment
// We need to presume the whole call is preserved (if the upper statement is)
// This will break if the m_splitVscp is a "ref" argument to the function,
// but little we can do.
}
void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
public:
// CONSTRUCTORS
explicit SplitAsFindVisitor(AstAlways* nodep) { iterateConst(nodep); }
~SplitAsFindVisitor() override = default;
// METHODS
AstVarScope* splitVscp() const { return m_splitVscp; }
};
//######################################################################
// Remove nodes not containing proper references
class SplitAsCleanVisitor final : public VNVisitor {
// STATE - across all visitors
const AstVarScope* const m_splitVscp; // Variable we want to split
const bool m_modeMatch; // Remove matching Vscp, else non-matching
// STATE - for current visit position (use VL_RESTORER)
bool m_keepStmt = false; // Current Statement must be preserved
bool m_matches = false; // Statement below has matching lvalue reference
// METHODS
void visit(AstVarRef* nodep) override {
if (nodep->access().isWriteOrRW()) {
if (nodep->varScopep() == m_splitVscp) {
UINFO(6, " CL VAR " << nodep);
m_matches = true;
}
}
}
void visit(AstNodeStmt* nodep) override {
UINFO(6, " CL STMT " << nodep);
const bool oldKeep = m_keepStmt;
{
VL_RESTORER(m_matches);
m_matches = false;
m_keepStmt = false;
iterateChildren(nodep);
if (m_keepStmt || (m_modeMatch ? m_matches : !m_matches)) {
UINFO(6, " Keep STMT " << nodep);
m_keepStmt = true;
} else {
UINFO(6, " Delete STMT " << nodep);
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
}
// If something below matches, the upper statement remains too.
m_keepStmt = oldKeep || m_keepStmt;
UINFO(9, " upKeep=" << m_keepStmt << " STMT " << nodep);
}
void visit(AstExprStmt* nodep) override {
// A function call inside the splitting assignment
// We need to presume the whole call is preserved (if the upper statement is)
// This will break if the m_splitVscp is a "ref" argument to the function,
// but little we can do.
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
// CONSTRUCTORS
SplitAsCleanVisitor(AstAlways* nodep, AstVarScope* vscp, bool modeMatch)
: m_splitVscp{vscp}
, m_modeMatch{modeMatch} {
iterate(nodep);
}
~SplitAsCleanVisitor() override = default;
};
//######################################################################
// SplitAs class functions
class SplitAsVisitor final : public VNVisitor {
// NODE STATE
// AstAlways::user() -> bool. True if already processed
const VNUser1InUse m_inuser1;
// STATE - across all visitors
VDouble0 m_statSplits; // Statistic tracking
// METHODS
void splitAlways(AstAlways* nodep, AstVarScope* splitVscp) {
UINFOTREE(9, nodep, "", "in");
// Duplicate it and link in
// Below cloneTree should perhaps be cloneTreePure, but given
// isolate_assignments is required to hit this code, we presume the user
// knows what they are asking for
AstAlways* const newp = nodep->cloneTree(false);
newp->user1(true); // So we don't clone it again
nodep->addNextHere(newp);
{ // Delete stuff we don't want in old
const SplitAsCleanVisitor visitor{nodep, splitVscp, false};
UINFOTREE(9, nodep, "", "out0");
}
{ // Delete stuff we don't want in new
const SplitAsCleanVisitor visitor{newp, splitVscp, true};
UINFOTREE(9, newp, "", "out1");
}
}
void visit(AstAlways* nodep) override {
// Are there any lvalue references below this?
// There could be more than one. So, we process the first one found first.
const AstVarScope* lastSplitVscp = nullptr;
while (!nodep->user1()) {
// Find any splittable variables
const SplitAsFindVisitor visitor{nodep};
AstVarScope* const splitVscp = visitor.splitVscp();
// Now isolate the always
if (splitVscp) {
UINFO(3, "Split " << nodep);
UINFO(3, " For " << splitVscp);
// If we did this last time! Something's stuck!
UASSERT_OBJ(splitVscp != lastSplitVscp, nodep,
"Infinite loop in isolate_assignments removal for: "
<< splitVscp->prettyNameQ());
lastSplitVscp = splitVscp;
splitAlways(nodep, splitVscp);
++m_statSplits;
} else {
nodep->user1(true);
}
}
}
void visit(AstNodeExpr*) override {} // Accelerate
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
// CONSTRUCTORS
explicit SplitAsVisitor(AstNetlist* nodep) { iterate(nodep); }
~SplitAsVisitor() override {
V3Stats::addStat("Optimizations, isolate_assignments blocks", m_statSplits);
}
};
//######################################################################
// SplitAs class functions
void V3SplitAs::splitAsAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ SplitAsVisitor{nodep}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("splitas", 0, dumpTreeEitherLevel() >= 3);
}

View File

@ -1,32 +0,0 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Break always into separate statements to reduce temps
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
#ifndef VERILATOR_V3SPLITAS_H_
#define VERILATOR_V3SPLITAS_H_
#include "config_build.h"
#include "verilatedos.h"
class AstNetlist;
//============================================================================
class V3SplitAs final {
public:
static void splitAsAll(AstNetlist* nodep) VL_MT_DISABLED;
};
#endif // Guard

View File

@ -1,701 +0,0 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Implementation of Christofides algorithm to
// approximate the solution to the traveling salesman problem.
//
// ISSUES: This isn't exactly Christofides algorithm; see the TODO
// in perfectMatching(). True minimum-weight perfect matching
// would produce a better result. How much better is TBD.
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3TSP.h"
#include "V3File.h"
#include "V3Graph.h"
#include <cmath>
#include <list>
#include <memory>
#include <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// Support classes
namespace V3TSP {
static uint32_t s_edgeIdNext = 0;
static void selfTestStates();
static void selfTestString();
} // namespace V3TSP
// Vertex that tracks a per-vertex key
template <typename T_Key>
class TspVertexTmpl final : public V3GraphVertex {
VL_RTTI_IMPL(TspVertexTmpl, V3GraphVertex)
private:
const T_Key m_key;
public:
TspVertexTmpl(V3Graph* graphp, const T_Key& k)
: V3GraphVertex{graphp}
, m_key{k} {}
~TspVertexTmpl() override = default;
const T_Key& key() const { return m_key; }
private:
VL_UNCOPYABLE(TspVertexTmpl);
};
// TspGraphTmpl represents a complete graph, templatized to work with
// different T_Key types.
template <typename T_Key>
class TspGraphTmpl final : public V3Graph {
public:
// TYPES
using Vertex = TspVertexTmpl<T_Key>;
enum VertexState : uint32_t { CLEAR = 0, MST_VISITED = 1, UNMATCHED_ODD = 2 };
// MEMBERS
std::unordered_map<T_Key, Vertex*> m_vertices; // T_Key to Vertex lookup map
// CONSTRUCTORS
TspGraphTmpl()
: V3Graph{} {}
~TspGraphTmpl() override = default;
// METHODS
void addVertex(const T_Key& key) {
const bool newEntry = m_vertices.emplace(key, new Vertex{this, key}).second;
UASSERT(newEntry, "Vertex already exists with same key");
}
// For purposes of TSP, we are using non-directional graphs.
// Map that onto the normally-directional V3Graph by creating
// a matched pairs of opposite-directional edges to represent
// each non-directional edge:
void addEdge(const T_Key& from, const T_Key& to, int cost) {
#if VL_DEBUG // Hot, so only in debug
UASSERT(from != to, "Adding edge would form a loop");
UASSERT(cost >= 0, "Negative weight edge");
#endif
Vertex* const fp = findVertex(from);
Vertex* const tp = findVertex(to);
// No need to dedup edges.
// The only time we may create duplicate edges is when
// combining the MST with the perfect-matched pairs,
// and in that case, we want to permit duplicate edges.
const uint32_t edgeId = ++V3TSP::s_edgeIdNext;
// We want to be able to compare edges quickly for a total
// ordering, so pre-compute a sorting key and store it in
// the edge user field. We also want easy access to the 'id'
// which uniquely identifies a single bidir edge. Luckily we
// can do both efficiently.
// cppcheck-suppress badBitmaskCheck
const uint64_t userValue = (static_cast<uint64_t>(cost) << 32) | edgeId;
(new V3GraphEdge{this, fp, tp, cost})->user(userValue);
(new V3GraphEdge{this, tp, fp, cost})->user(userValue);
}
static uint32_t getEdgeId(const V3GraphEdge* edgep) {
return static_cast<uint32_t>(edgep->user());
}
// cppcheck-suppress duplInheritedMember
bool empty() const { return m_vertices.empty(); }
const std::list<Vertex*> keysToVertexList(const std::vector<T_Key>& odds) {
std::list<Vertex*> vertices;
for (unsigned i = 0; i < odds.size(); ++i) vertices.push_back(findVertex(odds.at(i)));
return vertices;
}
private:
// We will keep sorted lists of edges as vectors
using EdgeList = std::vector<V3GraphEdge*>;
static bool edgeCmp(const V3GraphEdge* ap, const V3GraphEdge* bp) {
// We pre-computed these when adding the edge to sort first by cost, then by identity
return ap->user() > bp->user();
}
struct EdgeListCmp final {
bool operator()(const EdgeList* ap, const EdgeList* bp) const {
// Compare heads
return edgeCmp(bp->back(), ap->back());
}
};
static Vertex* castVertexp(V3GraphVertex* vxp) { return static_cast<Vertex*>(vxp); }
static const Vertex* castVertexp(const V3GraphVertex* vxp) {
return static_cast<const Vertex*>(vxp);
}
public:
// From *this, populate *mstp with the minimum spanning tree.
// *mstp must be initially empty.
void makeMinSpanningTree(TspGraphTmpl* mstp) {
UASSERT(mstp->empty(), "Output graph must start empty");
// Use Prim's algorithm to efficiently construct the MST.
uint32_t vertCount = 0;
for (V3GraphVertex& vtx : vertices()) {
mstp->addVertex(castVertexp(&vtx)->key());
vertCount++;
}
// Allocate storage for per vertex edge lists up front.
std::vector<EdgeList> allocatedEdgeLists{vertCount};
// Index of vertex in visitation order (used for indexing allocatedEdgeLists)
uint32_t vertIdx = 0;
// We keep pending edges as a sorted set of sorted vectors. This allows us to find the
// lowest cost edge quickly, while also reducing the cost of inserting batches of new
// edges, which is what we need in this algorithm.
std::set<EdgeList*, EdgeListCmp> pendingEdgeListps;
const auto visit = [&](V3GraphVertex* vtxp) {
#ifdef VL_DEBUG // Very hot, so only in debug
UASSERT(vtxp->user() == VertexState::CLEAR, "Vertex visited twice");
#endif
// Mark vertex as visited
vtxp->user(VertexState::MST_VISITED);
// Allocate new edge list
EdgeList* const newEdgesp = &allocatedEdgeLists[vertIdx++];
// Gather out edges of this vertex
for (V3GraphEdge& edge : vtxp->outEdges()) {
// Don't add edges leading to vertices we already visited. This is a highly
// connected graph, so this greatly reduces the cost of maintaining the pending
// set.
if (edge.top()->user() == VertexState::MST_VISITED) continue;
newEdgesp->push_back(&edge);
}
// If no relevant out edges, then we are done
if (newEdgesp->empty()) return;
// Sort new edge list
std::sort(newEdgesp->begin(), newEdgesp->end(), edgeCmp);
// Add edge list to pending set
pendingEdgeListps.insert(newEdgesp);
};
// To start, choose an arbitrary vertex and visit it.
visit(vertices().frontp());
// Repeatedly find the least costly edge in the pending set.
// If it connects to an unvisited node, visit that node and update
// the pending edge set. If it connects to an already visited node,
// discard it and repeat again.
while (!pendingEdgeListps.empty()) {
// Grab lowest cost edge list
auto it = pendingEdgeListps.begin();
// Grab lowest cost edge
EdgeList* const bestEdgeListp = *it;
const V3GraphEdge* const bestEdgep = bestEdgeListp->back();
// Remove the lowest cost edge list. We will remove its lowest cost element, and either
// we are done with (if it had a single element) it in which case it will be discarded,
// or the cost of the new head element might be different, so we will need to re-insert
// it in the right place. In either case, it needs to be removed.
pendingEdgeListps.erase(it);
// If the lowest cost edge list is not a singleton list, then pop the lowest cost
// edge and re-insert the remaining edge list into the pending set.
if (bestEdgeListp->size() > 1) {
bestEdgeListp->pop_back();
pendingEdgeListps.insert(bestEdgeListp);
}
// Grab the target vertex
Vertex* const neighborp = castVertexp(bestEdgep->top());
// If the neighbour is not yet visited
if (neighborp->user() == VertexState::CLEAR) {
// Visit it
visit(neighborp);
// Create the edge in our output MST graph
Vertex* const from_vertexp = castVertexp(bestEdgep->fromp());
mstp->addEdge(from_vertexp->key(), neighborp->key(), bestEdgep->weight());
#if VL_DEBUG // Very hot loop, so only in debug
UASSERT(from_vertexp->user() == MST_VISITED,
"bestEdgep->fromp() should be already seen");
#endif
}
}
UASSERT(vertIdx == vertCount, "Should have visited all vertices");
}
// Populate *outp with a minimal perfect matching of *this.
// *outp must be initially empty.
void perfectMatching(const std::vector<T_Key>& oddKeys, TspGraphTmpl* outp) {
UASSERT(outp->empty(), "Output graph must start empty");
const std::list<Vertex*>& odds = keysToVertexList(oddKeys);
UASSERT(odds.size() % 2 == 0, "number of odd-order nodes should be even");
for (Vertex* const vtxp : odds) {
outp->addVertex(vtxp->key());
vtxp->user(VertexState::UNMATCHED_ODD);
}
// TODO: The true Christofides algorithm calls for minimum-weight
// perfect matching. Instead, we have a simple greedy algorithm
// which might get close to the minimum, maybe, with luck?
//
// TODO: Revisit this. It's possible to compute the true minimum in
// N*N*log(N) time using variants of the Blossom algorithm.
// Implementing Blossom looks hard, maybe we can use an existing
// open source implementation -- for example the "LEMON" library
// which has a TSP solver.
// -----
// Gather and sort all edges. We use a vector then sort, because this is faster than a
// sorted set. Reuse the comparator from Prim's routine (note it a 'greater', not a
// 'lesser' comparator). The logic is the same here.
//
// Note that there are two V3GraphEdge's representing a single bidir edge. While we could
// just add both to the pending list and get the same result, we will only add one (based
// on fast pointer comparison - this still yields deterministic results), in order to
// reduce the size of the working set.
std::vector<V3GraphEdge*> pendingEdges;
for (Vertex* const fromp : odds) {
for (V3GraphEdge& edge : fromp->outEdges()) {
Vertex* const top = castVertexp(edge.top());
// There are two edges (in both directions) between these two vertices. Keep one.
if (fromp > top) continue;
// We only care about edges between the odd-order vertices
if (top->user() != VertexState::UNMATCHED_ODD) continue;
// Add to candidate list
pendingEdges.push_back(&edge);
}
}
// Sort reverse iterators. This yields ascending order with a 'greater' comparator.
std::sort(pendingEdges.rbegin(), pendingEdges.rend(), edgeCmp);
// Iterate over all edges, in order from low to high cost.
// For any edge whose ends are both odd-order vertices which
// haven't been matched yet, match them.
for (V3GraphEdge* const edgep : pendingEdges) {
Vertex* const fromp = castVertexp(edgep->fromp());
Vertex* const top = castVertexp(edgep->top());
if (fromp->user() == VertexState::UNMATCHED_ODD
&& top->user() == VertexState::UNMATCHED_ODD) {
outp->addEdge(fromp->key(), top->key(), edgep->weight());
fromp->user(VertexState::CLEAR);
top->user(VertexState::CLEAR);
}
}
}
void combineGraph(const TspGraphTmpl& g) {
std::unordered_set<uint32_t> edges_done;
for (const V3GraphVertex& vtx : g.vertices()) {
const Vertex* const fromp = castVertexp(&vtx);
for (const V3GraphEdge& edge : fromp->outEdges()) {
const Vertex* const top = castVertexp(edge.top());
if (edges_done.insert(getEdgeId(&edge)).second) {
addEdge(fromp->key(), top->key(), edge.weight());
}
}
}
}
void findEulerTourRecurse(std::unordered_set<unsigned>* markedEdgesp, Vertex* startp,
std::vector<T_Key>* sortedOutp) {
Vertex* cur_vertexp = startp;
// Go on a random tour. Fun!
std::vector<Vertex*> tour;
do {
UINFO(6, "Adding " << cur_vertexp->key() << " to tour.");
tour.push_back(cur_vertexp);
// Look for an arbitrary edge we've not yet marked
for (V3GraphEdge& edge : cur_vertexp->outEdges()) {
const uint32_t edgeId = getEdgeId(&edge);
if (markedEdgesp->end() == markedEdgesp->find(edgeId)) {
// This edge is not yet marked, so follow it.
markedEdgesp->insert(edgeId);
Vertex* const neighborp = castVertexp(edge.top());
UINFO(6, "following edge " << edgeId << " from " << cur_vertexp->key()
<< " to " << neighborp->key());
cur_vertexp = neighborp;
goto found;
}
}
v3fatalSrc("No unmarked edges found in tour");
found:;
} while (cur_vertexp != startp);
UINFO(6, "stopped, got back to start of tour @ " << cur_vertexp->key());
// Look for nodes on the tour that still have
// un-marked edges. If we find one, recurse.
for (Vertex* vxp : tour) {
bool recursed;
do {
recursed = false;
// Look for an arbitrary edge at vxp we've not yet marked
for (V3GraphEdge& edge : vxp->outEdges()) {
const uint32_t edgeId = getEdgeId(&edge);
if (markedEdgesp->end() == markedEdgesp->find(edgeId)) {
UINFO(6, "Recursing.");
findEulerTourRecurse(markedEdgesp, vxp, sortedOutp);
recursed = true;
goto recursed;
}
}
recursed:;
} while (recursed);
sortedOutp->push_back(vxp->key());
}
if (debug() >= 6) {
UINFO(0, "Tour was:");
for (const Vertex* vxp : tour) std::cout << "- " << vxp->key() << '\n';
std::cout << "-\n";
}
}
void dumpGraph(std::ostream& os, const string& nameComment) const {
// UINFO(0) as controlled by caller
os << "At " << nameComment << ", dumping graph. Keys:\n";
for (const V3GraphVertex& vtx : vertices()) {
const Vertex* const tspvp = castVertexp(&vtx);
os << " " << tspvp->key() << '\n';
for (const V3GraphEdge& edge : tspvp->outEdges()) {
const Vertex* const neighborp = castVertexp(edge.top());
os << " has edge " << getEdgeId(&edge) << " to " << neighborp->key() << '\n';
}
}
}
void dumpGraphFilePrefixed(const string& nameComment) const {
if (dumpLevel()) {
const string filename = v3Global.debugFilename(nameComment) + ".txt";
const std::unique_ptr<std::ofstream> logp{V3File::new_ofstream(filename)};
if (logp->fail()) v3fatal("Can't write file: " << filename);
dumpGraph(*logp, nameComment);
}
}
void findEulerTour(std::vector<T_Key>* sortedOutp) {
UASSERT(sortedOutp->empty(), "Output graph must start empty");
if (::dumpGraphLevel() >= 6) dumpDotFilePrefixed("findEulerTour");
std::unordered_set<unsigned /*edgeID*/> markedEdges;
// Pick a start node
Vertex* const start_vertexp = castVertexp(vertices().frontp());
findEulerTourRecurse(&markedEdges, start_vertexp, sortedOutp);
}
std::vector<T_Key> getOddDegreeKeys() const {
std::vector<T_Key> result;
for (const V3GraphVertex& vtx : vertices()) {
const Vertex* const tspvp = castVertexp(&vtx);
const uint32_t degree = vtx.outEdges().size();
if (degree & 1) result.push_back(tspvp->key());
}
return result;
}
private:
Vertex* findVertex(const T_Key& key) const {
const auto it = m_vertices.find(key);
UASSERT(it != m_vertices.end(), "Vertex not found");
return it->second;
}
VL_UNCOPYABLE(TspGraphTmpl);
};
//######################################################################
// Main algorithm
void V3TSP::tspSort(const V3TSP::StateVec& states, V3TSP::StateVec* resultp) VL_MT_SAFE {
UASSERT(resultp->empty(), "Output graph must start empty");
// Make this TSP implementation work for graphs of size 0 or 1
// which, unfortunately, is a special case as the following
// code assumes >= 2 nodes.
if (states.empty()) return;
if (states.size() == 1) {
resultp->push_back(*(states.begin()));
return;
}
// Build the initial graph from the starting state set.
using Graph = TspGraphTmpl<const TspStateBase*>;
Graph graph;
for (const auto& state : states) graph.addVertex(state);
for (V3TSP::StateVec::const_iterator it = states.begin(); it != states.end(); ++it) {
for (V3TSP::StateVec::const_iterator jt = it; jt != states.end(); ++jt) {
if (it == jt) continue;
graph.addEdge(*it, *jt, (*it)->cost(*jt));
}
}
// Create the minimum spanning tree
Graph minGraph;
graph.makeMinSpanningTree(&minGraph);
if (dumpGraphLevel() >= 6) minGraph.dumpGraphFilePrefixed("minGraph");
const std::vector<const TspStateBase*> oddDegree = minGraph.getOddDegreeKeys();
Graph matching;
graph.perfectMatching(oddDegree, &matching);
if (dumpGraphLevel() >= 6) matching.dumpGraphFilePrefixed("matching");
// Adds edges to minGraph, the resulting graph will have even number of
// edge counts at every vertex:
minGraph.combineGraph(matching);
V3TSP::StateVec prelim_result;
minGraph.findEulerTour(&prelim_result);
UASSERT(prelim_result.size() >= states.size(), "Algorithm size error");
// Discard duplicate nodes that the Euler tour might contain.
{
std::unordered_set<const TspStateBase*> seen;
for (V3TSP::StateVec::iterator it = prelim_result.begin(); it != prelim_result.end();
++it) {
const TspStateBase* const elemp = *it;
const auto itFoundPair = seen.insert(elemp);
if (itFoundPair.second) resultp->push_back(elemp);
}
}
UASSERT(resultp->size() == states.size(), "Algorithm size error");
// Find the most expensive arc and rotate the list so that the most
// expensive arc connects the last and first elements. (Since we're not
// modeling something that actually cycles back, we don't need to pay
// that cost at all.)
{
unsigned max_cost = 0;
unsigned max_cost_idx = 0;
for (unsigned i = 0; i < resultp->size(); ++i) {
const TspStateBase* const ap = (*resultp)[i];
const TspStateBase* const bp
= (i + 1 == resultp->size()) ? (*resultp)[0] : (*resultp)[i + 1];
const unsigned cost = ap->cost(bp);
if (cost > max_cost) {
max_cost = cost;
max_cost_idx = i;
}
}
if (max_cost_idx == resultp->size() - 1) {
// List is already rotated for minimum cost. stop.
return;
}
V3TSP::StateVec new_result;
unsigned i = max_cost_idx + 1;
UASSERT(i < resultp->size(), "Algorithm size error");
while (i != max_cost_idx) {
new_result.push_back((*resultp)[i]);
i++;
if (i >= resultp->size()) i = 0;
}
new_result.push_back((*resultp)[i]);
UASSERT(resultp->size() == new_result.size(), "Algorithm size error");
*resultp = new_result;
}
}
//######################################################################
// Self Tests
class TspTestState final : public V3TSP::TspStateBase {
public:
TspTestState(unsigned xpos, unsigned ypos)
: m_xpos{xpos}
, m_ypos{ypos}
, m_serial{++s_serialNext} {}
~TspTestState() override = default;
int cost(const TspStateBase* otherp) const override VL_MT_SAFE {
return cost(dynamic_cast<const TspTestState*>(otherp));
}
static unsigned diff(unsigned a, unsigned b) VL_PURE {
if (a > b) return a - b;
return b - a;
}
int cost(const TspTestState* otherp) const VL_PURE {
// For test purposes, each TspTestState is merely a point
// on the Cartesian plane; cost is the linear distance
// between two points.
unsigned xabs;
unsigned yabs;
xabs = diff(otherp->m_xpos, m_xpos);
yabs = diff(otherp->m_ypos, m_ypos);
return std::lround(std::sqrt(xabs * xabs + yabs * yabs));
}
unsigned xpos() const { return m_xpos; }
unsigned ypos() const { return m_ypos; }
bool operator<(const TspStateBase& other) const override {
return operator<(dynamic_cast<const TspTestState&>(other));
}
bool operator<(const TspTestState& other) const { return m_serial < other.m_serial; }
private:
const unsigned m_xpos;
const unsigned m_ypos;
const unsigned m_serial;
static unsigned s_serialNext;
};
unsigned TspTestState::s_serialNext = 0;
void V3TSP::selfTestStates() {
// Linear test -- coords all along the x-axis
{
V3TSP::StateVec states;
const TspTestState s10{10, 0};
const TspTestState s60{60, 0};
const TspTestState s20{20, 0};
const TspTestState s100{100, 0};
const TspTestState s5{5, 0};
states.push_back(&s10);
states.push_back(&s60);
states.push_back(&s20);
states.push_back(&s100);
states.push_back(&s5);
V3TSP::StateVec result;
tspSort(states, &result);
V3TSP::StateVec expect;
expect.push_back(&s100);
expect.push_back(&s60);
expect.push_back(&s20);
expect.push_back(&s10);
expect.push_back(&s5);
if (VL_UNCOVERABLE(expect != result)) {
for (V3TSP::StateVec::iterator it = result.begin(); it != result.end(); ++it) {
const TspTestState* const statep = dynamic_cast<const TspTestState*>(*it);
cout << statep->xpos() << " ";
}
cout << endl;
v3fatalSrc("TSP linear self-test fail. Result (above) did not match expectation.");
}
}
// Second test. Coords are distributed in 2D space.
// Test that tspSort() will rotate the list for minimum cost.
{
V3TSP::StateVec states;
const TspTestState a{0, 0};
const TspTestState b{100, 0};
const TspTestState c{200, 0};
const TspTestState d{200, 100};
const TspTestState e{150, 150};
const TspTestState f{0, 150};
const TspTestState g{0, 100};
states.push_back(&a);
states.push_back(&b);
states.push_back(&c);
states.push_back(&d);
states.push_back(&e);
states.push_back(&f);
states.push_back(&g);
V3TSP::StateVec result;
tspSort(states, &result);
V3TSP::StateVec expect;
expect.push_back(&f);
expect.push_back(&g);
expect.push_back(&a);
expect.push_back(&b);
expect.push_back(&c);
expect.push_back(&d);
expect.push_back(&e);
if (VL_UNCOVERABLE(expect != result)) {
for (V3TSP::StateVec::iterator it = result.begin(); it != result.end(); ++it) {
const TspTestState* const statep = dynamic_cast<const TspTestState*>(*it);
cout << statep->xpos() << "," << statep->ypos() << " ";
}
cout << endl;
v3fatalSrc(
"TSP 2d cycle=false self-test fail. Result (above) did not match expectation.");
}
}
}
void V3TSP::selfTestString() {
using Graph = TspGraphTmpl<std::string>;
Graph graph;
graph.addVertex("0");
graph.addVertex("1");
graph.addVertex("2");
graph.addVertex("3");
graph.addEdge("0", "1", 3943);
graph.addEdge("0", "2", 3456);
graph.addEdge("0", "3", 4920);
graph.addEdge("1", "2", 2730);
graph.addEdge("1", "3", 8199);
graph.addEdge("2", "3", 4130);
Graph minGraph;
graph.makeMinSpanningTree(&minGraph);
if (dumpGraphLevel() >= 6) minGraph.dumpGraphFilePrefixed("minGraph");
const std::vector<string> oddDegree = minGraph.getOddDegreeKeys();
Graph matching;
graph.perfectMatching(oddDegree, &matching);
if (dumpGraphLevel() >= 6) matching.dumpGraphFilePrefixed("matching");
minGraph.combineGraph(matching);
std::vector<string> result;
minGraph.findEulerTour(&result);
std::vector<string> expect;
expect.emplace_back("0");
expect.emplace_back("2");
expect.emplace_back("1");
expect.emplace_back("2");
expect.emplace_back("3");
if (VL_UNCOVERABLE(expect != result)) {
for (const string& i : result) cout << i << " ";
cout << endl;
v3fatalSrc("TSP string self-test fail. Result (above) did not match expectation.");
}
}
void V3TSP::selfTest() {
selfTestString();
selfTestStates();
}

View File

@ -1,57 +0,0 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Implementation of Christofides algorithm to
// approximate the solution to the traveling salesman problem.
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
#ifndef VERILATOR_V3TSP_H_
#define VERILATOR_V3TSP_H_
#include "config_build.h"
#include "verilatedos.h"
#include "V3Error.h"
#include <vector>
namespace V3TSP {
// Perform a "Traveling Salesman Problem" optimizing sort
// on any type you like -- so long as inherits from TspStateBase.
class TspStateBase VL_NOT_FINAL {
public:
// This is the cost function that the TSP sort will minimize.
// All costs in V3TSP are int, chosen to match the type of
// V3GraphEdge::weight() which will reflect each edge's cost.
virtual int cost(const TspStateBase* otherp) const VL_MT_SAFE = 0;
// This operator< must place a meaningless, arbitrary, but
// stable order on all TspStateBase's. It's used only to
// key maps so that iteration is stable, without relying
// on pointer values that could lead to nondeterminism.
virtual bool operator<(const TspStateBase& otherp) const = 0;
virtual ~TspStateBase() = default;
};
using StateVec = std::vector<const TspStateBase*>;
// Given an unsorted set of TspState's, sort them to minimize
// the transition cost for walking the sorted list.
void tspSort(const StateVec& states, StateVec* resultp) VL_MT_SAFE;
void selfTest() VL_MT_DISABLED;
} // namespace V3TSP
#endif // Guard

View File

@ -248,9 +248,9 @@ private:
UASSERT_OBJ(nodep->taskp(), nodep, "Unlinked task");
TaskFTaskVertex* const taskVtxp = getFTaskVertex(nodep->taskp());
new TaskEdge{&m_callGraph, m_curVxp, taskVtxp};
if (isVirtualIfaceMethodCall(nodep) && isIfaceFTaskScope(getScope(nodep->taskp()))) {
taskVtxp->needsNonInlineCFunc(true);
}
// Virtual-interface method calls dispatch through a runtime handle and
// must not be inlined.
if (isVirtualIfaceMethodCall(nodep)) taskVtxp->needsNonInlineCFunc(true);
// Do we have to disable inlining the function?
const V3TaskConnects tconnects = V3Task::taskConnects(nodep, nodep->taskp()->stmtsp());
if (!taskVtxp->noInline()) { // Else short-circuit below
@ -1638,6 +1638,7 @@ class TaskVisitor final : public VNVisitor {
// Create cloned statements
AstNode* beginp;
AstCNew* cnewp = nullptr;
// getScope() is safe here: TaskStateVisitor stamped all FTask scopes before this pass.
const bool virtualIfaceCall
= TaskStateVisitor::isVirtualIfaceMethodCall(nodep)
&& TaskStateVisitor::isIfaceFTaskScope(m_statep->getScope(nodep->taskp()));

View File

@ -51,15 +51,14 @@ class UnknownVisitor final : public VNVisitor {
static const std::string s_xrandPrefix;
// STATE - across all visitors
VDouble0 m_statUnkVars; // Statistic tracking
VDouble0 m_statUnkVars; // Statistic of xrand variable created
VDouble0 m_statElses; // Statistic of else branches created for array selects
V3UniqueNames m_lvboundNames; // For generating unique temporary variable names
std::unique_ptr<V3UniqueNames> m_xrandNames; // For generating unique temporary variable names
// STATE - for current visit position (use VL_RESTORER)
AstNodeModule* m_modp = nullptr; // Current module
AstNodeFTask* m_ftaskp = nullptr; // Current function/task
AstAssignDly* m_assigndlyp = nullptr; // Current assignment
AstNode* m_timingControlp = nullptr; // Current assignment's intra timing control
bool m_constXCvt = false; // Convert X's
bool m_allowXUnique = true; // Allow unique assignments
@ -69,38 +68,21 @@ class UnknownVisitor final : public VNVisitor {
if (m_ftaskp) {
varp->funcLocal(true);
varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
m_ftaskp->stmtsp()->addHereThisAsNext(varp);
if (m_ftaskp->stmtsp())
m_ftaskp->stmtsp()->addHereThisAsNext(varp);
else
m_ftaskp->addStmtsp(varp);
} else {
m_modp->stmtsp()->addHereThisAsNext(varp);
if (m_modp->stmtsp())
m_modp->stmtsp()->addHereThisAsNext(varp);
else
m_modp->addStmtsp(varp);
}
}
void replaceBoundLvalue(AstNodeExpr* nodep, AstNodeExpr* condp) {
// Spec says a out-of-range LHS SEL results in a NOP.
// This is a PITA. We could:
// 1. IF(...) around an ASSIGN,
// but that would break a "foo[TOO_BIG]=$fopen(...)".
// 2. Hack to extend the size of the output structure
// by one bit, and write to that temporary, but never read it.
// That makes there be two widths() and is likely a bug farm.
// 3. Make a special SEL to choose between the real lvalue
// and a temporary NOP register.
// 4. Assign to a temp, then IF that assignment.
// This is suspected to be nicest to later optimizations.
// 4 seems best but breaks later optimizations. 3 was tried,
// but makes a mess in the emitter as lvalue switching is needed. So 4.
// SEL(...) -> temp
// if (COND(LTE(bit<=maxlsb))) ASSIGN(SEL(...)),temp)
const bool needDly = (m_assigndlyp != nullptr);
if (m_assigndlyp) {
// Delayed assignments become normal assignments,
// then the temp created becomes the delayed assignment
AstNode* const newp = new AstAssign{m_assigndlyp->fileline(),
m_assigndlyp->lhsp()->unlinkFrBackWithNext(),
m_assigndlyp->rhsp()->unlinkFrBackWithNext()};
m_assigndlyp->replaceWith(newp);
VL_DO_CLEAR(pushDeletep(m_assigndlyp), m_assigndlyp = nullptr);
}
// We wrap the expression into IF
AstNodeExpr* prep = nodep;
// Scan back to put the condlvalue above all selects (IE top of the lvalue)
@ -121,32 +103,35 @@ class UnknownVisitor final : public VNVisitor {
// Already exists; rather than IF(a,... IF(b... optimize to IF(a&&b,
// Saves us teaching V3Const how to optimize, and it won't be needed again.
if (const AstIf* const ifp = VN_AS(prep->user2p(), If)) {
UASSERT_OBJ(!needDly, prep, "Should have already converted to non-delay");
VNRelinker replaceHandle;
AstNodeExpr* const earliercondp = ifp->condp()->unlinkFrBack(&replaceHandle);
AstNodeExpr* const newp = new AstLogAnd{condp->fileline(), condp, earliercondp};
UINFO(4, "Edit BOUNDLVALUE " << newp);
replaceHandle.relink(newp);
} else {
AstVar* const varp
= new AstVar{fl, VVarType::MODULETEMP, m_lvboundNames.get(prep), prep->dtypep()};
addVar(varp);
AstNode* stmtp = prep->backp(); // Grab above point before we replace 'prep'
while (!VN_IS(stmtp, NodeStmt)) stmtp = stmtp->backp();
prep->replaceWith(new AstVarRef{fl, varp, VAccess::WRITE});
if (m_timingControlp) m_timingControlp->unlinkFrBack();
AstIf* const newp = new AstIf{
fl, condp,
(needDly
? static_cast<AstNode*>(new AstAssignDly{
fl, prep, new AstVarRef{fl, varp, VAccess::READ}, m_timingControlp})
: static_cast<AstNode*>(new AstAssign{
fl, prep, new AstVarRef{fl, varp, VAccess::READ}, m_timingControlp}))};
VNRelinker replaceHandle;
AstNode* const origStmtp = stmtp->unlinkFrBack(&replaceHandle);
AstNode* elseStmtp = nullptr;
const bool hasSideEffects = origStmtp->exists(
[](AstNode* const np) { return !np->isPure() || np->isTimingControl(); });
if (hasSideEffects) {
// Copy original statement and replace `prep` with reference to tmp var to make
// sure that side effect will take place
m_statElses++;
elseStmtp = origStmtp->cloneTree(false);
AstVar* const varp = new AstVar{fl, VVarType::MODULETEMP, m_lvboundNames.get(prep),
prep->dtypep()};
addVar(varp);
AstNode* const prepCopyp = prep->clonep();
prepCopyp->replaceWith(new AstVarRef{fl, varp, VAccess::WRITE});
pushDeletep(prepCopyp);
}
AstIf* const newp = new AstIf{fl, condp, origStmtp, elseStmtp};
replaceHandle.relink(newp);
newp->branchPred(VBranchPred::BP_LIKELY);
newp->isBoundsCheck(true);
UINFOTREE(9, newp, "", "_new");
stmtp->addNextHere(newp);
prep->user2p(newp); // Save so we may LogAnd it next time
}
}
@ -208,23 +193,6 @@ class UnknownVisitor final : public VNVisitor {
m_ftaskp = nodep;
iterateChildren(nodep);
}
void visit(AstAssignDly* nodep) override {
VL_RESTORER(m_assigndlyp);
VL_RESTORER(m_timingControlp);
m_assigndlyp = nodep;
m_timingControlp = nodep->timingControlp();
VL_DO_DANGLING(iterateChildren(nodep), nodep); // May delete nodep.
}
void visit(AstAssignW* nodep) override {
VL_RESTORER(m_timingControlp);
m_timingControlp = nodep->timingControlp();
VL_DO_DANGLING(iterateChildren(nodep), nodep); // May delete nodep.
}
void visit(AstNodeAssign* nodep) override {
VL_RESTORER(m_timingControlp);
m_timingControlp = nodep->timingControlp();
iterateChildren(nodep);
}
void visit(AstCaseItem* nodep) override {
VL_RESTORER(m_constXCvt);
m_constXCvt = false; // Avoid losing the X's in casex
@ -584,6 +552,7 @@ public:
}
~UnknownVisitor() override { //
V3Stats::addStat("Unknowns, variables created", m_statUnkVars);
V3Stats::addStat("Unknowns, else branches created", m_statElses);
}
};

View File

@ -255,13 +255,6 @@ class WidthVisitor final : public VNVisitor {
EXTEND_OFF // No extension
};
int widthUnpacked(const AstNodeDType* const dtypep) {
if (const AstUnpackArrayDType* const arrDtypep = VN_CAST(dtypep, UnpackArrayDType)) {
return arrDtypep->subDTypep()->width() * arrDtypep->arrayUnpackedElements();
}
return dtypep->width();
}
static void packIfUnpacked(AstNodeExpr* const nodep) {
if (AstUnpackArrayDType* const unpackDTypep = VN_CAST(nodep->dtypep(), UnpackArrayDType)) {
const int elementsNum = unpackDTypep->arrayUnpackedElements();
@ -274,6 +267,9 @@ class WidthVisitor final : public VNVisitor {
nodep->findLogicDType(unpackBits, unpackMinBits, VSigning::UNSIGNED)});
}
}
static bool lowerAsFixedAggregate(const AstNodeDType* const dtypep) {
return dtypep->isStreamableFixedAggregate() && dtypep->containsUnpackedStruct();
}
// When fromp() is a DType (e.g. unlinked RefDType), resolve through
// the ref chain; when it's an expression, dtypep() is already resolved.
static AstNodeDType* fromDTypep(AstNode* fromp) {
@ -979,7 +975,10 @@ class WidthVisitor final : public VNVisitor {
}
const AstNodeDType* const lhsDtypep = nodep->lhsp()->dtypep()->skipRefToEnump();
if (VN_IS(lhsDtypep, DynArrayDType) || VN_IS(lhsDtypep, QueueDType)
|| VN_IS(lhsDtypep, UnpackArrayDType)) {
|| (VN_IS(lhsDtypep, UnpackArrayDType) && lhsDtypep->isStreamableFixedAggregate())
|| (VN_IS(lhsDtypep, NodeUOrStructDType)
&& !VN_AS(lhsDtypep, NodeUOrStructDType)->packed()
&& lhsDtypep->isStreamableFixedAggregate())) {
nodep->dtypeSetStream();
} else if (lhsDtypep->isCompound()) {
nodep->v3warn(E_UNSUPPORTED,
@ -3074,6 +3073,7 @@ class WidthVisitor final : public VNVisitor {
UASSERT_OBJ(nodep->dtypep(), nodep, "LHS var should be dtype completed");
}
// UINFOTREE(9, nodep, "", "VRout");
if (nodep->access().isWriteOrRW()) nodep->varp()->icoMaybeWritten(true);
if (nodep->access().isWriteOrRW() && nodep->varp()->direction() == VDirection::CONSTREF) {
nodep->v3error("Assigning to const ref variable: " << nodep->prettyNameQ());
} else if (nodep->access().isWriteOrRW() && nodep->varp()->isInput()
@ -4352,6 +4352,12 @@ class WidthVisitor final : public VNVisitor {
}
void methodCallAssoc(AstMethodCall* nodep, AstAssocArrayDType* adtypep) {
AstCMethodHard* newp = nullptr;
if (nodep->withp() && adtypep->subDTypep()->isWide()) {
nodep->v3warn(
E_UNSUPPORTED,
"Unsupported: `with` clause on assoc arrays with wide value types in method '"
<< nodep->prettyName() << "'");
}
if (nodep->name() == "num" // function int num()
|| nodep->name() == "size") {
methodOkArguments(nodep, 0, 0);
@ -6289,14 +6295,14 @@ class WidthVisitor final : public VNVisitor {
userIterateAndNext(nodep->rhsp(), WidthVP{nodep->dtypep(), PRELIM}.p());
//
// UINFOTREE(1, nodep, "", "assign");
AstNodeDType* const lhsDTypep
AstNodeDType* lhsDTypep
= nodep->lhsp()->dtypep(); // Note we use rhsp for context determined
// Check width of stream and wrap if needed
if (AstNodeStream* const streamp = VN_CAST(nodep->rhsp(), NodeStream)) {
AstNodeDType* const lhsDTypeSkippedRefp = lhsDTypep->skipRefp();
const int lwidth = widthUnpacked(lhsDTypeSkippedRefp);
const int rwidth = widthUnpacked(streamp->lhsp()->dtypep()->skipRefp());
const int lwidth = lhsDTypeSkippedRefp->widthStream();
const int rwidth = streamp->lhsp()->dtypep()->skipRefp()->widthStream();
if (lwidth != 0 && lwidth < rwidth) {
nodep->v3widthWarn(lwidth, rwidth,
"Target fixed size variable ("
@ -6308,16 +6314,18 @@ class WidthVisitor final : public VNVisitor {
const int queueElementSize = streamp->lhsp()->dtypep()->subDTypep()->width();
UASSERT_OBJ(queueElementSize <= lwidth, nodep, "LHS < RHS");
}
if (VN_IS(lhsDTypeSkippedRefp, UnpackArrayDType)) {
if (VN_IS(lhsDTypeSkippedRefp, UnpackArrayDType)
|| lowerAsFixedAggregate(lhsDTypeSkippedRefp)) {
streamp->unlinkFrBack();
nodep->rhsp(new AstCvtPackedToArray{streamp->fileline(), streamp,
lhsDTypeSkippedRefp});
}
}
if (const AstNodeStream* const streamp = VN_CAST(nodep->lhsp(), NodeStream)) {
if (AstNodeStream* const streamp = VN_CAST(nodep->lhsp(), NodeStream)) {
const AstNodeDType* const rhsDTypep = nodep->rhsp()->dtypep()->skipRefp();
const int lwidth = widthUnpacked(streamp->lhsp()->dtypep()->skipRefp());
const int rwidth = widthUnpacked(rhsDTypep);
AstNodeDType* const lhsStreamDTypep = streamp->lhsp()->dtypep()->skipRefp();
const int lwidth = lhsStreamDTypep->widthStream();
const int rwidth = rhsDTypep->widthStream();
if (rwidth != 0 && rwidth < lwidth) {
nodep->v3widthWarn(lwidth, rwidth,
"Stream target requires "
@ -6325,7 +6333,27 @@ class WidthVisitor final : public VNVisitor {
<< " bits, but source expression only provides "
<< rwidth << " bits (IEEE 1800-2023 11.4.14.3)");
}
if (VN_IS(rhsDTypep, UnpackArrayDType)) {
if (lowerAsFixedAggregate(lhsStreamDTypep)) {
AstNodeExpr* const streamExprp = nodep->lhsp()->unlinkFrBack();
AstNodeExpr* const dstp = streamp->lhsp()->unlinkFrBack();
AstNodeExpr* srcp = nodep->rhsp()->unlinkFrBack();
if (VN_IS(streamp, StreamL)) {
streamp->lhsp(srcp);
streamp->dtypeSetLogicUnsized(srcp->width(), srcp->widthMin(),
VSigning::UNSIGNED);
srcp = streamExprp;
} else {
if (srcp->width() > lwidth) {
srcp = new AstSel{streamp->fileline(), srcp, srcp->width() - lwidth,
lwidth};
}
VL_DO_DANGLING(pushDeletep(streamExprp), streamExprp);
}
nodep->lhsp(dstp);
nodep->rhsp(new AstCvtPackedToArray{srcp->fileline(), srcp, lhsStreamDTypep});
nodep->dtypeFrom(dstp);
lhsDTypep = nodep->lhsp()->dtypep();
} else if (VN_IS(rhsDTypep, UnpackArrayDType)) {
AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack();
nodep->rhsp(
new AstCvtArrayToPacked{rhsp->fileline(), rhsp, streamp->dtypep()});

View File

@ -98,12 +98,10 @@
#include "V3Scoreboard.h"
#include "V3Slice.h"
#include "V3Split.h"
#include "V3SplitAs.h"
#include "V3SplitVar.h"
#include "V3Stats.h"
#include "V3String.h"
#include "V3Subst.h"
#include "V3TSP.h"
#include "V3Table.h"
#include "V3Task.h"
#include "V3ThreadPool.h"
@ -261,6 +259,7 @@ static void process() {
// Assertion insertion
// After we've added block coverage, but before other nasty transforms
V3AssertCommon::collectDefaultDisable(v3Global.rootp());
V3AssertNfa::assertNfaAll(v3Global.rootp());
// V3AssertProp removed: NFA subsumes multi-cycle property lowering.
// Unsupported constructs fall through to V3AssertPre.
@ -421,7 +420,6 @@ static void process() {
// Split single ALWAYS blocks into multiple blocks for better ordering chances
if (v3Global.opt.fSplit()) V3Split::splitAll(v3Global.rootp());
V3SplitAs::splitAsAll(v3Global.rootp());
// Create tracing sample points, before we start eliminating signals
if (v3Global.opt.trace()) V3TraceDecl::traceDeclAll(v3Global.rootp());
@ -734,7 +732,6 @@ static bool verilate(const string& argString) {
VHashSha256::selfTest();
VSpellCheck::selfTest();
V3Graph::selfTest();
V3TSP::selfTest();
V3ScoreboardBase::selfTest();
V3Order::selfTestParallel();
V3ExecGraph::selfTest();

View File

@ -145,6 +145,26 @@ def clean_output(filename: str, outname: str, is_output: bool, is_c: bool) -> No
out.append(line)
lines = out
out = []
# Code updates for the .c
if outname.endswith(".c"):
for line in lines:
modify = "YYSTACK_FREE (yyss);" in line and "if (yyss != yyssa)" in out[-1]
ifdefined = "#if defined __GNUC__ && ! defined __ICC\n"
endif = "#endif\n"
if modify:
out.insert(-1, ifdefined)
out.insert(-1, " _Pragma (\"GCC diagnostic push\")\n")
out.insert(
-1,
" _Pragma (\"GCC diagnostic ignored \\\"-Wfree-nonheap-object\\\"\")\n")
out.insert(-1, endif)
out.append(line)
if modify:
out.append(ifdefined)
out.append(" _Pragma (\"GCC diagnostic pop\")\n")
out.append(endif)
lines = out
out = []
with open(outname, "w", encoding="utf-8") as fh:
tmpy = re.escape(tmp_prefix() + ".y")

View File

@ -137,8 +137,6 @@ constParameterPointer:src/V3Tristate.cpp
constParameterReference:src/V3Tristate.cpp
constVariablePointer:src/V3Tristate.cpp
constVariableReference:src/V3Tristate.cpp
constVariablePointer:src/V3TSP.cpp
constVariableReference:src/V3TSP.cpp
constParameterPointer:src/V3Undriven.cpp
constVariablePointer:src/V3Undriven.cpp
constParameterPointer:src/V3Unroll.cpp

View File

@ -3121,7 +3121,7 @@ sigAttr<nodep>:
| yVL_PUBLIC_FLAT { $$ = new AstAttrOf{$1, VAttrType::VAR_PUBLIC_FLAT}; v3Global.dpi(true); }
| yVL_PUBLIC_FLAT_RD { $$ = new AstAttrOf{$1, VAttrType::VAR_PUBLIC_FLAT_RD}; v3Global.dpi(true); }
| yVL_PUBLIC_FLAT_RW attr_event_controlE { $$ = new AstAttrOf{$1, VAttrType::VAR_PUBLIC_FLAT_RW}; v3Global.dpi(true); DEL($2); }
| yVL_ISOLATE_ASSIGNMENTS { $$ = new AstAttrOf{$1, VAttrType::VAR_ISOLATE_ASSIGNMENTS}; }
| yVL_ISOLATE_ASSIGNMENTS { $$ = nullptr; /* Historical, now has no effect */ }
| yVL_SC_BIGUINT { $$ = new AstAttrOf{$1, VAttrType::VAR_SC_BIGUINT}; }
| yVL_SC_BV { $$ = new AstAttrOf{$1, VAttrType::VAR_SC_BV}; }
| yVL_SFORMAT { $$ = new AstAttrOf{$1, VAttrType::VAR_SFORMAT}; }
@ -4657,12 +4657,12 @@ task_prototype<nodeFTaskp>: // ==IEEE: task_prototype
function_declaration<nodeFTaskp>: // IEEE: function_declaration + function_body_declaration
yFUNCTION dynamic_override_specifiersE lifetimeE funcId funcIsolateE tfGuts yENDFUNCTION endLabelE
{ $$ = $4; $4->attrIsolateAssign($5); $$->addStmtsp($6);
{ $$ = $4; $$->addStmtsp($6);
$$->baseOverride($2);
$$->lifetime($3);
GRAMMARP->endLabel($<fl>8, $$, $8); }
| yFUNCTION dynamic_override_specifiersE lifetimeE funcIdNew funcIsolateE tfNewGuts yENDFUNCTION endLabelE
{ $$ = $4; $4->attrIsolateAssign($5); $$->addStmtsp($6);
{ $$ = $4; $$->addStmtsp($6);
$$->baseOverride($2);
$$->lifetime($3);
GRAMMARP->endLabel($<fl>8, $$, $8); }
@ -6497,14 +6497,34 @@ concurrent_assertion_statement<nodeStmtp>: // ==IEEE: concurrent_assertion_stat
| yCOVER yPROPERTY '(' property_spec ')' stmt
{ $$ = new AstCover{$1, $4, $6, VAssertType::CONCURRENT}; }
// // IEEE: cover_sequence_statement
// // Reuses AstCover + AstPropSpec (same wrapper as
// // cover_property_statement above) and the isCoverSeq
// // flag drives V3AssertNfa to fire stmt per end-of-match
// // (IEEE 1800-2023 16.14.3), not per property success.
| yCOVER ySEQUENCE '(' sexpr ')' stmt
{ $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover sequence"); DEL($4, $6); }
// // IEEE: yCOVER ySEQUENCE '(' clocking_event sexpr ')' stmt
// // sexpr already includes "clocking_event sexpr"
{ AstCover* const coverp = new AstCover{$1,
new AstPropSpec{$4->fileline(), nullptr, nullptr, $4},
$6, VAssertType::CONCURRENT};
coverp->isCoverSeq(true);
$$ = coverp; }
| yCOVER ySEQUENCE '(' clocking_event sexpr ')' stmt
{ AstCover* const coverp = new AstCover{$1,
new AstPropSpec{$4->fileline(), $4, nullptr, $5},
$7, VAssertType::CONCURRENT};
coverp->isCoverSeq(true);
$$ = coverp; }
| yCOVER ySEQUENCE '(' clocking_event yDISABLE yIFF '(' expr/*expression_or_dist*/ ')' sexpr ')' stmt
{ $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover sequence"); DEL($4, $8, $10, $12);}
{ AstCover* const coverp = new AstCover{$1,
new AstPropSpec{$4->fileline(), $4, $8, $10},
$12, VAssertType::CONCURRENT};
coverp->isCoverSeq(true);
$$ = coverp; }
| yCOVER ySEQUENCE '(' yDISABLE yIFF '(' expr/*expression_or_dist*/ ')' sexpr ')' stmt
{ $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover sequence"); DEL($7, $9, $11); }
{ AstCover* const coverp = new AstCover{$1,
new AstPropSpec{$7->fileline(), nullptr, $7, $9},
$11, VAssertType::CONCURRENT};
coverp->isCoverSeq(true);
$$ = coverp; }
// // IEEE: restrict_property_statement
| yRESTRICT yPROPERTY '(' property_spec ')' ';'
{ $$ = new AstRestrict{$1, $4}; }
@ -6664,14 +6684,8 @@ property_spec<propSpecp>: // IEEE: property_spec
{ $$ = new AstPropSpec{$1, $3, nullptr, $5}; }
| '@' senitemVar pexpr
{ $$ = new AstPropSpec{$1, $2, nullptr, $3}; }
// // Disable applied after the event occurs,
// // so no existing AST can represent this
| yDISABLE yIFF '(' expr ')' '@' '(' senitemEdge ')' pexpr
{ $$ = new AstPropSpec{$1, $8, nullptr, new AstLogOr{$1, $4, $10}};
BBUNSUP($<fl>1, "Unsupported: property '(disable iff (...) @ (...)'\n"
+ $<fl>1->warnMore()
+ "... Suggest use property '(@(...) disable iff (...))'"); }
//UNSUP remove above
| yDISABLE yIFF '(' expr ')' '@' '(' senitem ')' pexpr
{ $$ = new AstPropSpec{$1, $8, $4, $10}; }
| yDISABLE yIFF '(' expr ')' pexpr { $$ = new AstPropSpec{$4->fileline(), nullptr, $4, $6}; }
| pexpr { $$ = new AstPropSpec{$1->fileline(), nullptr, nullptr, $1}; }
;
@ -8559,8 +8573,7 @@ vltVarAttrSpecE<strp>:
;
vltVarAttrFront<attrtypeen>:
yVLT_ISOLATE_ASSIGNMENTS { $$ = VAttrType::VAR_ISOLATE_ASSIGNMENTS; }
| yVLT_FORCEABLE { $$ = VAttrType::VAR_FORCEABLE; }
yVLT_FORCEABLE { $$ = VAttrType::VAR_FORCEABLE; }
| yVLT_PUBLIC { $$ = VAttrType::VAR_PUBLIC; v3Global.dpi(true); }
| yVLT_PUBLIC_FLAT { $$ = VAttrType::VAR_PUBLIC_FLAT; v3Global.dpi(true); }
| yVLT_PUBLIC_FLAT_RD { $$ = VAttrType::VAR_PUBLIC_FLAT_RD; v3Global.dpi(true); }
@ -8574,6 +8587,7 @@ vltVarAttrFront<attrtypeen>:
vltVarAttrFrontDeprecated:
yVLT_CLOCK_ENABLE { }
| yVLT_CLOCKER { }
| yVLT_ISOLATE_ASSIGNMENTS { }
| yVLT_NO_CLOCKER { }
;

134
test_regress/AGENTS.md Normal file
View File

@ -0,0 +1,134 @@
<!-- DESCRIPTION: Verilator: test_regress/ guidelines for AI coding agents
SPDX-FileCopyrightText: 2026-2026 Wilson Snyder
SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -->
# test_regress/ -- regression tests
Covers `.v`/`.sv` sources, `.py` drivers, and `.out` golden files under
`test_regress/`. Read the repository-root [AGENTS.md](../AGENTS.md) first. This
file has two parts: **Orientation** explains how the harness runs a test;
**Before you open a PR** is the test-authoring checklist.
______________________________________________________________________
# Orientation: how the harness works
- **A test is a `.v`/`.sv` source plus a matching `.py` driver in `t/`.** The
driver calls `test.compile()`, `test.execute()`, and/or `test.lint()`. Without a
`.py` the source never runs and is dead code giving false coverage confidence.
- **Golden output lives in `.out` files**, compared via `expect_filename`.
Generate them with `HARNESS_UPDATE_GOLDEN=1 python3 t/<name>.py` -- never
hand-write them; the harness normalizes paths, version markers, and line numbers
that hand edits get wrong.
- **Run one test from the repository root:** `test_regress/t/t_<name>.py`. When
running from a checkout, set `VERILATOR_ROOT` to the checkout root first so the
harness compiles the generated C++ against the right headers.
- **`test.compile()` defaults are richer than they look:** the vlt driver
auto-injects `--exe` and a generated main, and `assert property` fires its
action blocks without `--assert` -- try the simple form before adding flags.
- **The `t_dist_*` tests enforce repository conventions** (file headers, sorted
lists, warning documentation, ASCII-only) -- run the relevant ones before
submitting.
______________________________________________________________________
# Before you open a PR
## Naming
- Name tests `t_{category}_{description}` in snake_case; the first word groups the
category (`t_lint_unused_func_bad`, not `t_unused_func_lint_bad`) so related
tests are findable and runnable together.
- Use `_bad` when the code is illegal SystemVerilog, `_unsup` when it is legal but
unsupported, `_off` for disabled-behavior tests -- never `_fail`.
- Keep filenames under roughly 30-35 characters.
## Files and drivers
- Every `.v` needs a matching `.py` driver that actually calls `test.compile()`
and `test.execute()` (or `test.lint()` for static-analysis-only tests) -- a
driver that sets up but never runs hides coverage gaps silently.
- Copy the license header from an existing file: `.v` tests carry the CC0 notice,
`.py` drivers carry the standard driver header -- distribution tests check
headers on every committed file.
- Use `--binary` instead of `--exe --main --timing` -- it implies the others.
- Error tests use `test.compile(fails=True, expect_filename=test.golden_filename)`
(or `test.lint(...)`) and must not call `test.execute()`.
- Use `.out` golden files with `expect_filename` instead of inline `expect`
regex strings -- goldens are diffable and maintainable.
- Use `test.glob_one()`/`test.glob_some()` and `test.timeout()` instead of raw
`glob.glob()` and manual `time.time()` measurements.
- Coverage tests run `verilator_coverage` and verify individual bins and points,
not just aggregate percentages.
- Assert optimization stats with exact expected counts:
`test.file_grep(test.stats, r'Optimizations, ...\s+(\d+)', N)`.
- Add a `_protect_ids` variant when a feature emits user identifiers or filenames;
use conservative thread counts (2 or fewer) in multithreaded tests.
- Extend existing test files with related cases instead of creating many
single-purpose files; keep drivers minimal -- test logic belongs in the `.v`.
## Golden .out files
- Never hand-write or hand-edit `.out` goldens -- regenerate with
`HARNESS_UPDATE_GOLDEN=1`.
- When a feature lands, remove its now-supported entries from `t_*_unsup.v` /
`t_*_bad.v` in the same change and regenerate the goldens -- stale entries no
longer error and reviewers will flag them.
## Verilog style
- 2-space indentation, no tabs.
- Declarations are flush-left with a single space between type and name; never
column-align:
```systemverilog
// WRONG: column-aligned
bit [63:0] crc = 64'h5aef0c8d_d70a4497;
int cyc = 0;
// RIGHT: flush-left
bit [63:0] crc = 64'h5aef0c8d_d70a4497;
int cyc = 0;
```
- Run `nodist/verilog_format` on new `.v` files; wrap macro definitions in
`// verilog_format: off`/`on` so the formatter does not split them.
- Use `$display("%0d", ...)` not `%d` -- avoids leading-space padding.
- Wrap Verilator-specific test code (e.g. `$c`) in `` `ifdef VERILATOR ``.
- Use inline `// verilator lint_off WARNCODE` only when that warning is itself
under test -- fix root causes otherwise.
- Use only IEEE 1800-compliant constructs other simulators also accept -- tests
validate standard behavior, not Verilator's parser leniency.
- Omit optional end labels on `endmodule`/`endclass`/`endtask`/`endfunction`.
## Self-checking
- Use the `checkh`/`checkd`/`checks` assertion macros, not manual
`if`/`$display`/`$stop` sequences. Note `checkh` prints with `%p`, which renders
integers as hex -- use `checkd` for integer comparisons.
- Use the `` `stop `` macro rather than direct `$stop`.
- Drive logic with runtime-varying inputs (counters, CRC/LFSR registers) so
constant folding cannot pre-evaluate the logic under test; check behavior across
multiple clock cycles, not just initial values.
- For a test whose pass/fail depends on varying or random values, loop enough
iterations that the values demonstrably differ and size the value space so a
failure is probable per run, then confirm the test fails on an un-fixed tree
before submitting.
## Test design
- Use non-power-of-2, non-word-aligned widths (7, 15, 31, 33, 63, 65, 95) --
exposes masking and word-boundary bugs that 32/64/128 hide.
- Test both `[high:low]` and `[low:high]` orderings plus non-zero bounds like
`[3:1]`; use different ranges for each axis of multidimensional arrays.
- When adding type support, test all basic types (chandle, string, real) and
typedef-wrapped variants.
- Include the issue's own reproducer as a committed test, and verify it fails
without the fix.
- Assert non-blocking-assignment results in the cycle immediately after they take
effect, before later overwrites, using indices that change post-NBA.

View File

@ -1,6 +1,15 @@
[0] %Error: t_assert_ctl_immediate.v:51: Assertion failed in top.t.module_with_assertctl: 'assert' failed.
-Info: t/t_assert_ctl_immediate.v:51: Verilog $stop, ignored due to +verilator+error+limit
[0] %Error: t_assert_ctl_immediate.v:57: Assertion failed in top.t.module_with_assertctl: 'assert' failed.
[0] %Error: t_assert_ctl_immediate.v:45: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed.
[0] %Error: t_assert_ctl_immediate.v:45: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed.
[0] %Error: t_assert_ctl_immediate.v:52: Assertion failed in top.t.module_with_assertctl: 'assert' failed.
-Info: t/t_assert_ctl_immediate.v:52: Verilog $stop, ignored due to +verilator+error+limit
[0] %Error: t_assert_ctl_immediate.v:58: Assertion failed in top.t.module_with_assertctl: 'assert' failed.
[0] %Error: t_assert_ctl_immediate.v:46: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed.
[0] %Error: t_assert_ctl_immediate.v:46: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed.
[0] %Error: t_assert_ctl_immediate.v:159: Assertion failed in top.t.module_with_method_ctl: 'assert' failed.
[0] %Error: t_assert_ctl_immediate.v:132: Assertion failed in top.t.module_with_method_ctl.Ctl.check_positive: 'assert' failed.
[0] %Error: t_assert_ctl_immediate.v:169: Assertion failed in top.t.module_with_method_ctl: 'assert' failed.
[0] %Error: t_assert_ctl_immediate.v:93: Assertion failed in top.t.module_with_method_ctl.iface.check_positive: 'assert' failed.
[0] %Error: t_assert_ctl_immediate.v:180: Assertion failed in top.t.module_with_method_ctl: 'assert' failed.
[0] %Error: t_assert_ctl_immediate.v:186: Assertion failed in top.t.module_with_method_ctl: 'assert' failed.
[0] %Error: t_assert_ctl_immediate.v:195: Assertion failed in top.t.module_with_method_ctl: 'assert' failed.
[0] %Error: t_assert_ctl_immediate.v:199: Assertion failed in top.t.module_with_method_ctl: 'assert' failed.
[0] %Error: t_assert_ctl_immediate.v:203: Assertion failed in top.t.module_with_method_ctl: 'assert' failed.
*-* All Finished *-*

View File

@ -10,6 +10,7 @@ module t (
module_with_assert module_with_assert (clk);
module_with_assertctl module_with_assertctl (clk);
module_with_method_ctl module_with_method_ctl ();
always @(posedge clk) begin
assert (0);
@ -63,3 +64,146 @@ module module_with_assertctl (
f_assert();
end
endmodule
// Assertion control invoked from class methods and interface functions, in a
// single sub-module with a single initial block so the golden output order is
// stable across --fno-inline (otherwise parallel sub-module initials are
// interleaved differently by the inliner).
//
// Covers:
// - class task $assertoff / $asserton
// - class static method $assertcontrol(Off=4 / On=3), IEEE 1800-2023 Table 20-5
// - $assertkill from a class method
// - assert that lives inside a class method (obeys context-global gating)
// - interface function $assertoff / $asserton
// - assert inside an interface function
// - virtual interface dispatch to an assertion-control function
// - interface class (AstClass with isInterfaceClass) via a concrete impl
// - multiple instances of each thing: OFF via one instance, ON via another
// (assertion control is global per-context, IEEE 1800-2023 20.11)
interface AssertCtlIf;
function void suppress();
$assertoff;
endfunction
function void enable();
$asserton;
endfunction
function void check_positive(int v);
assert (v > 0);
endfunction
endinterface
// verilog_format: off (verible-verilog-format mangles `pure virtual function`)
interface class IAssertCtl;
pure virtual function void suppress();
pure virtual function void enable();
endclass
// verilog_format: on
class IAssertCtlImpl implements IAssertCtl;
virtual function void suppress();
$assertoff;
endfunction
virtual function void enable();
$asserton;
endfunction
endclass
module module_with_method_ctl;
class Ctl;
virtual AssertCtlIf vif;
static function void off_all();
$assertcontrol(4);
endfunction
static function void on_all();
$asserton;
endfunction
function void kill_all();
$assertkill;
endfunction
function void inst_off();
$assertoff;
endfunction
function void inst_on();
$asserton;
endfunction
function void check_positive(int v);
assert (v > 0);
endfunction
function void vif_suppress();
vif.suppress();
endfunction
function void vif_enable();
vif.enable();
endfunction
endclass
Ctl c;
Ctl c2;
AssertCtlIf iface ();
AssertCtlIf iface2 ();
IAssertCtlImpl impl;
IAssertCtlImpl impl2;
initial begin
c = new;
c2 = new;
impl = new;
impl2 = new;
// --- class method coverage ---
Ctl::off_all();
assert (0); // gated via class static -> no fire
Ctl::on_all();
assert (0); // fires
Ctl::off_all();
c.check_positive(-1); // assert inside class method, gated -> no fire
Ctl::on_all();
c.check_positive(-2); // assert inside class method, fires
// --- interface function coverage ---
iface.suppress();
assert (0); // gated via iface fn -> no fire
iface.enable();
assert (0); // fires
iface.suppress();
iface.check_positive(-1); // assert inside iface fn, gated -> no fire
iface.enable();
iface.check_positive(-2); // assert inside iface fn, fires
// --- virtual interface dispatch coverage ---
c.vif = iface;
c.vif_suppress();
assert (0); // gated via virtual interface dispatch -> no fire
c.vif_enable();
assert (0); // fires
// --- interface class via concrete impl ---
impl.suppress();
assert (0); // gated via interface-class impl -> no fire
impl.enable();
assert (0); // fires
// --- multiple instances: OFF via one instance, ON via another ---
// Assertion control is global per-context (IEEE 1800-2023 20.11, no scope
// list), so OFF issued via one instance gates every assertion and ON issued
// via a different instance re-enables them.
c.inst_off(); // class: OFF via c
assert (0); // gated -> no fire
c2.inst_on(); // class: ON via c2
assert (0); // fires
iface.suppress(); // interface: OFF via iface
assert (0); // gated -> no fire
iface2.enable(); // interface: ON via iface2
assert (0); // fires
impl.suppress(); // interface class: OFF via impl
assert (0); // gated -> no fire
impl2.enable(); // interface class: ON via impl2
assert (0); // fires
// --- $assertkill (last: terminal per IEEE 1800-2023 Table 20-5) ---
c.kill_all();
assert (0); // killed -> no fire
end
endmodule

View File

@ -1,123 +1,103 @@
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:25:5: Unsupported: non-constant assert assertion-type expression
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:26:5: Unsupported: non-constant assert assertion-type expression
: ... note: In instance 't.unsupported_ctl_type'
25 | $assertcontrol(Lock, a);
26 | $assertcontrol(Lock, a);
| ^~~~~~~~~~~~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:27:5: Unsupported: $assertcontrol control_type '2'
27 | $assertcontrol(Unlock);
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:28:5: Unsupported: $assertcontrol control_type '2'
28 | $assertcontrol(Unlock);
| ^~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:29:5: Unsupported: $assertcontrol control_type '6'
29 | $assertcontrol(PassOn);
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:30:5: Unsupported: $assertcontrol control_type '6'
30 | $assertcontrol(PassOn);
| ^~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:30:5: Unsupported: assert control assertion_type
: ... note: In instance 't.unsupported_ctl_type'
30 | $assertpasson;
| ^~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:31:5: Unsupported: assert control assertion_type
: ... note: In instance 't.unsupported_ctl_type'
31 | $assertpasson(a);
31 | $assertpasson;
| ^~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:32:5: Unsupported: assert control assertion_type
: ... note: In instance 't.unsupported_ctl_type'
32 | $assertpasson(a, t);
32 | $assertpasson(a);
| ^~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:34:5: Unsupported: $assertcontrol control_type '7'
34 | $assertcontrol(PassOff);
| ^~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:35:5: Unsupported: assert control assertion_type
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:33:5: Unsupported: assert control assertion_type
: ... note: In instance 't.unsupported_ctl_type'
35 | $assertpassoff;
33 | $assertpasson(a, t);
| ^~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:35:5: Unsupported: $assertcontrol control_type '7'
35 | $assertcontrol(PassOff);
| ^~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:36:5: Unsupported: assert control assertion_type
: ... note: In instance 't.unsupported_ctl_type'
36 | $assertpassoff(a);
36 | $assertpassoff;
| ^~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:37:5: Unsupported: assert control assertion_type
: ... note: In instance 't.unsupported_ctl_type'
37 | $assertpassoff(a, t);
37 | $assertpassoff(a);
| ^~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:39:5: Unsupported: $assertcontrol control_type '8'
39 | $assertcontrol(FailOn);
| ^~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:40:5: Unsupported: assert control assertion_type
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:38:5: Unsupported: assert control assertion_type
: ... note: In instance 't.unsupported_ctl_type'
40 | $assertfailon;
| ^~~~~~~~~~~~~
38 | $assertpassoff(a, t);
| ^~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:40:5: Unsupported: $assertcontrol control_type '8'
40 | $assertcontrol(FailOn);
| ^~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:41:5: Unsupported: assert control assertion_type
: ... note: In instance 't.unsupported_ctl_type'
41 | $assertfailon(a);
41 | $assertfailon;
| ^~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:42:5: Unsupported: assert control assertion_type
: ... note: In instance 't.unsupported_ctl_type'
42 | $assertfailon(a, t);
42 | $assertfailon(a);
| ^~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:44:5: Unsupported: $assertcontrol control_type '9'
44 | $assertcontrol(FailOff);
| ^~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:45:5: Unsupported: assert control assertion_type
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:43:5: Unsupported: assert control assertion_type
: ... note: In instance 't.unsupported_ctl_type'
45 | $assertfailoff;
43 | $assertfailon(a, t);
| ^~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:45:5: Unsupported: $assertcontrol control_type '9'
45 | $assertcontrol(FailOff);
| ^~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:46:5: Unsupported: assert control assertion_type
: ... note: In instance 't.unsupported_ctl_type'
46 | $assertfailoff(a);
46 | $assertfailoff;
| ^~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:47:5: Unsupported: assert control assertion_type
: ... note: In instance 't.unsupported_ctl_type'
47 | $assertfailoff(a, t);
47 | $assertfailoff(a);
| ^~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:49:5: Unsupported: $assertcontrol control_type '10'
49 | $assertcontrol(NonvacuousOn);
| ^~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:50:5: Unsupported: assert control assertion_type
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:48:5: Unsupported: assert control assertion_type
: ... note: In instance 't.unsupported_ctl_type'
50 | $assertnonvacuouson;
| ^~~~~~~~~~~~~~~~~~~
48 | $assertfailoff(a, t);
| ^~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:50:5: Unsupported: $assertcontrol control_type '10'
50 | $assertcontrol(NonvacuousOn);
| ^~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:51:5: Unsupported: assert control assertion_type
: ... note: In instance 't.unsupported_ctl_type'
51 | $assertnonvacuouson(a);
51 | $assertnonvacuouson;
| ^~~~~~~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:52:5: Unsupported: assert control assertion_type
: ... note: In instance 't.unsupported_ctl_type'
52 | $assertnonvacuouson(a, t);
52 | $assertnonvacuouson(a);
| ^~~~~~~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:54:5: Unsupported: $assertcontrol control_type '11'
54 | $assertcontrol(VacuousOff);
| ^~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:55:5: Unsupported: assert control assertion_type
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:53:5: Unsupported: assert control assertion_type
: ... note: In instance 't.unsupported_ctl_type'
55 | $assertvacuousoff;
| ^~~~~~~~~~~~~~~~~
53 | $assertnonvacuouson(a, t);
| ^~~~~~~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:55:5: Unsupported: $assertcontrol control_type '11'
55 | $assertcontrol(VacuousOff);
| ^~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:56:5: Unsupported: assert control assertion_type
: ... note: In instance 't.unsupported_ctl_type'
56 | $assertvacuousoff(a);
56 | $assertvacuousoff;
| ^~~~~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:57:5: Unsupported: assert control assertion_type
: ... note: In instance 't.unsupported_ctl_type'
57 | $assertvacuousoff(a, t);
57 | $assertvacuousoff(a);
| ^~~~~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:64:5: Unsupported: non-const assert control type expression
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:58:5: Unsupported: assert control assertion_type
: ... note: In instance 't.unsupported_ctl_type'
58 | $assertvacuousoff(a, t);
| ^~~~~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:65:5: Unsupported: non-const assert control type expression
: ... note: In instance 't.unsupported_ctl_type_expr'
64 | $assertcontrol(ctl_type);
65 | $assertcontrol(ctl_type);
| ^~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:93:7: Unsupported: assertcontrols in classes or interfaces
: ... note: In instance 't.assert_class'
93 | $asserton;
| ^~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:99:7: Unsupported: assertcontrols in classes or interfaces
: ... note: In instance 't.assert_class'
99 | $assertoff;
| ^~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:172:5: Unsupported: assertcontrols in classes or interfaces
: ... note: In instance 't.assert_iface'
172 | $assertoff;
| ^~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:138:5: Unsupported: assertcontrols in classes or interfaces
: ... note: In instance 't.assert_iface_class'
138 | $assertoff;
| ^~~~~~~~~~
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:145:5: Unsupported: assertcontrols in classes or interfaces
: ... note: In instance 't.assert_iface_class'
145 | $asserton;
| ^~~~~~~~~
%Error: Exiting due to

View File

@ -4,15 +4,16 @@
// SPDX-FileCopyrightText: 2024 Antmicro
// SPDX-License-Identifier: CC0-1.0
module t(input logic clk);
unsupported_ctl_type unsupported_ctl_type(clk ? 1 : 2);
unsupported_ctl_type_expr unsupported_ctl_type_expr();
assert_class assert_class();
assert_iface assert_iface();
assert_iface_class assert_iface_class();
module t (
input logic clk
);
unsupported_ctl_type unsupported_ctl_type (clk ? 1 : 2);
unsupported_ctl_type_expr unsupported_ctl_type_expr ();
endmodule
module unsupported_ctl_type(input int a);
module unsupported_ctl_type (
input int a
);
initial begin
let Lock = 1;
let Unlock = 2;
@ -64,122 +65,3 @@ module unsupported_ctl_type_expr;
$assertcontrol(ctl_type);
end
endmodule
module assert_class;
virtual class AssertCtl;
pure virtual function void virtual_assert_ctl();
endclass
class AssertCls;
static function void static_function();
assert(0);
endfunction
static task static_task();
assert(0);
endtask
function void assert_function();
assert(0);
endfunction
task assert_task();
assert(0);
endtask
virtual function void virtual_assert();
assert(0);
endfunction
endclass
class AssertOn extends AssertCtl;
virtual function void virtual_assert_ctl();
$asserton;
endfunction
endclass
class AssertOff extends AssertCtl;
virtual function void virtual_assert_ctl();
$assertoff;
endfunction
endclass
AssertCls assertCls;
AssertOn assertOn;
AssertOff assertOff;
initial begin
$assertoff;
AssertCls::static_function();
AssertCls::static_task();
$asserton;
AssertCls::static_function();
AssertCls::static_task();
assertCls = new;
assertOn = new;
assertOff = new;
assertOff.virtual_assert_ctl();
assertCls.assert_function();
assertCls.assert_task();
assertCls.virtual_assert();
assertOn.virtual_assert_ctl();
assertCls.assert_function();
assertCls.assert_task();
assertCls.virtual_assert();
assertOff.virtual_assert_ctl();
assertCls.assert_function();
end
endmodule
interface Iface;
function void assert_func();
assert(0);
endfunction
function void assertoff_func();
$assertoff;
endfunction
initial begin
assertoff_func();
assert(0);
assert_func();
$asserton;
assert(0);
assert_func();
end
endinterface
module assert_iface;
Iface iface();
virtual Iface vIface = iface;
initial begin
vIface.assert_func();
vIface.assertoff_func();
vIface.assert_func();
iface.assert_func();
iface.assertoff_func();
iface.assert_func();
end
endmodule
interface class IfaceClass;
pure virtual function void assertoff_func();
pure virtual function void assert_func();
endclass
class IfaceClassImpl implements IfaceClass;
virtual function void assertoff_func();
$assertoff;
endfunction
virtual function void assert_func();
assert(0);
endfunction
endclass
module assert_iface_class;
IfaceClassImpl ifaceClassImpl = new;
initial begin
ifaceClassImpl.assertoff_func();
ifaceClassImpl.assert_func();
end
endmodule

View File

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

View File

@ -0,0 +1,48 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Antmicro Ltd
// SPDX-License-Identifier: CC0-1.0
module t (
input clk
);
int cyc;
logic rst = 1'b1;
logic x = 1'b1;
int issue_fail;
int pre_fail;
int post_fail;
int pre_temporal_fail;
int post_temporal_fail;
a_issue: assert property (disable iff(rst !== 1'b0) @(posedge clk) !x)
else issue_fail++;
assert property (disable iff (cyc < 5) @(posedge clk) 0)
else pre_fail++;
assert property (@(posedge clk) disable iff (cyc < 5) 0)
else post_fail++;
assert property (disable iff (cyc < 5) @(posedge clk) 1 ##1 0)
else pre_temporal_fail++;
assert property (@(posedge clk) disable iff (cyc < 5) 1 ##1 0)
else post_temporal_fail++;
always @(posedge clk) begin
cyc <= cyc + 1;
rst <= cyc < 4;
x <= cyc < 4;
if (cyc == 12) begin
if (issue_fail != 0) $stop;
if (pre_fail != post_fail) $stop;
if (pre_temporal_fail != post_temporal_fail) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule

View File

@ -1,6 +0,0 @@
%Error-UNSUPPORTED: t/t_assert_iff_clk_unsup.v:20:20: Unsupported: property '(disable iff (...) @ (...)'
: ... Suggest use property '(@(...) disable iff (...))'
20 | assert property (disable iff (cyc < 5) @(posedge clk) cyc >= 5);
| ^~~~~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Exiting due to

View File

@ -1,22 +0,0 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2022 Antmicro Ltd
// SPDX-License-Identifier: CC0-1.0
module t (
input clk
);
input clk;
int cyc = 0;
logic val = 0;
always @(posedge clk) begin
cyc <= cyc + 1;
val = ~val;
end
assert property (disable iff (cyc < 5) @(posedge clk) cyc >= 5);
endmodule

View File

@ -15,7 +15,7 @@ test.compile(verilator_flags2=['--binary', '--stats'])
test.execute(check_finished=True)
test.file_grep(test.stats, r'Assertions, assertOn checks combined\s+(\d+)', 2)
test.file_grep(test.stats, r'Assertions, assertOn checks hoisted\s+(\d+)', 11)
test.file_grep(test.stats, r'Assertions, assertOn checks combined\s+(\d+)', 3)
test.file_grep(test.stats, r'Assertions, assertOn checks hoisted\s+(\d+)', 15)
test.passes()

View File

@ -58,4 +58,12 @@ module t;
end
end
// Should combine the 2 nested assertOn checks after hoisting
always @(posedge clk) begin
if (assertEnable) begin
// This is an 'assert' with another 'assert' in the fail branch
assert(cntB - 100 == cntA); else assert(cntB == cntA + 100);
end
end
endmodule

View File

@ -22,15 +22,23 @@ module t;
int qe[int]; // Empty
int qv[$]; // Value returns
int qi[$]; // Index returns
bit[229:0] qw[int]; // Wide values
bit[229:0] qwe[int]; // Wide values - empty
bit[229:0] qwv[$]; // Wide values - Value returns
int qwi[$]; // Wide values - Index returns
point points_q[int];
point points_qe[int]; // Empty points
point points_qv[$];
int i;
bit b;
bit[229:0] w;
q = '{10: 1, 11: 2, 12: 2, 13: 4, 14: 3};
`checkp(q, "'{'ha:'h1, 'hb:'h2, 'hc:'h2, 'hd:'h4, 'he:'h3}");
qw = '{10: 1, 11: 2, 12: 2, 13: 4, 14: 3};
`checkp(qw, "'{'ha:'h1, 'hb:'h2, 'hc:'h2, 'hd:'h4, 'he:'h3}");
// NOT tested: with ... selectors
//q.sort; // Not legal on assoc - see t_assoc_meth_bad
@ -43,16 +51,34 @@ module t;
`checkp(qv, "'{'h1, 'h2, 'h4, 'h3}");
qv = qe.unique;
`checkp(qv, "'{}");
qwv = qw.unique;
`checkp(qwv, "'{'h1, 'h2, 'h4, 'h3}");
qwv = qwe.unique;
`checkp(qwv, "'{}");
qi = q.unique_index;
qi.sort;
`checkp(qi, "'{'ha, 'hb, 'hd, 'he}");
qi = qe.unique_index;
`checkp(qi, "'{}");
qwi = qw.unique_index;
qwi.sort;
`checkp(qwi, "'{'ha, 'hb, 'hd, 'he}");
qwi = qwe.unique_index;
`checkp(qwi, "'{}");
points_q[0] = point'{1, 2};
points_q[1] = point'{2, 4};
points_q[5] = point'{1, 4};
qi = points_qe.unique_index();
`checkp(qi, "'{}");
qi = points_q.unique_index();
`checkh(qi.size, 3);
points_qv = points_q.unique(p) with (p.x);
`checkh(points_qv.size, 2);
qi = points_q.unique_index (p) with (p.x + p.y);
@ -113,6 +139,10 @@ module t;
qv = q.min;
`checkp(qv, "'{'h1}");
qwv = qw.min;
`checkp(qwv, "'{'h1}");
points_qv = points_q.min(p) with (p.x + p.y);
if (points_qv[0].x != 1 || points_qv[0].y != 2) $stop;
@ -130,8 +160,13 @@ module t;
qv = qe.max(x) with (x + 1);
`checkp(qv, "'{}");
// Reduction methods
// Wide
qwv = qwe.min;
`checkp(qwv, "'{}");
qwv = qwe.max;
`checkp(qwv, "'{}");
// Reduction methods
i = q.sum;
`checkh(i, 32'hc);
i = q.sum with (item + 1);
@ -141,6 +176,12 @@ module t;
i = q.product with (item + 1);
`checkh(i, 32'h168);
// Wide
w = qw.sum;
`checkh(w, 230'hc);
w = qw.product;
`checkh(w, 230'h30);
i = qe.sum;
`checkh(i, 32'h0);
i = qe.sum with (item + 1);
@ -150,6 +191,12 @@ module t;
i = qe.product with (item + 1);
`checkh(i, 32'h0);
// Wide
w = qwe.sum;
`checkh(w, 230'h0);
w = qwe.product;
`checkh(w, 230'h0);
q = '{10: 32'b1100, 11: 32'b1010};
i = q.and;
`checkh(i, 32'b1000);
@ -164,6 +211,14 @@ module t;
i = q.xor with (item + 1);
`checkh(i, 32'b0110);
qw = '{10: 230'b1100, 11: 230'b1010};
w = qw.and;
`checkh(w, 230'b1000);
w = qw.or;
`checkh(w, 230'b1110);
w = qw.xor;
`checkh(w, 230'b0110);
i = qe.and;
`checkh(i, 32'b0);
i = qe.and with (item + 1);
@ -177,6 +232,14 @@ module t;
i = qe.xor with (item + 1);
`checkh(i, 32'b0);
// Wide
w = qwe.and;
`checkh(w, 230'b0);
w = qwe.or;
`checkh(w, 230'b0);
w = qwe.xor;
`checkh(w, 230'b0);
i = q.and();
`checkh(i, 32'b1000);
i = q.and() with (item + 1);
@ -190,6 +253,14 @@ module t;
i = q.xor() with (item + 1);
`checkh(i, 32'b0110);
// Wide
w = qw.and();
`checkh(w, 230'b1000);
w = qw.or();
`checkh(w, 230'b1110);
w = qw.xor();
`checkh(w, 230'b0110);
i = qe.and();
`checkh(i, 32'b0);
i = qe.or();
@ -197,6 +268,14 @@ module t;
i = qe.xor();
`checkh(i, 32'b0);
// Wide
w = qwe.and();
`checkh(w, 230'b0);
w = qwe.or();
`checkh(w, 230'b0);
w = qwe.xor();
`checkh(w, 230'b0);
q = '{10: 1, 11: 2};
qe = '{10: 1, 11: 2};
`checkh(q == qe, 1'b1);

View File

@ -0,0 +1,78 @@
%Error-UNSUPPORTED: t/t_assoc_unsup.v:17:15: Unsupported: `with` clause on assoc arrays with wide value types in method 'min'
: ... note: In instance 't'
17 | qwv = qwe.min(x) with (x + 1);
| ^~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error-UNSUPPORTED: t/t_assoc_unsup.v:18:15: Unsupported: `with` clause on assoc arrays with wide value types in method 'max'
: ... note: In instance 't'
18 | qwv = qwe.max(x) with (x + 1);
| ^~~
%Error-UNSUPPORTED: t/t_assoc_unsup.v:20:12: Unsupported: `with` clause on assoc arrays with wide value types in method 'sum'
: ... note: In instance 't'
20 | w = qw.sum with (item + 1);
| ^~~
%Error-UNSUPPORTED: t/t_assoc_unsup.v:21:12: Unsupported: `with` clause on assoc arrays with wide value types in method 'product'
: ... note: In instance 't'
21 | w = qw.product with (item + 1);
| ^~~~~~~
%Error-UNSUPPORTED: t/t_assoc_unsup.v:23:13: Unsupported: `with` clause on assoc arrays with wide value types in method 'sum'
: ... note: In instance 't'
23 | w = qwe.sum with (item + 1);
| ^~~
%Error-UNSUPPORTED: t/t_assoc_unsup.v:24:13: Unsupported: `with` clause on assoc arrays with wide value types in method 'product'
: ... note: In instance 't'
24 | w = qwe.product with (item + 1);
| ^~~~~~~
%Error-UNSUPPORTED: t/t_assoc_unsup.v:27:12: Unsupported: `with` clause on assoc arrays with wide value types in method 'and'
: ... note: In instance 't'
27 | w = qw.and with (item + 1);
| ^~~
%Error-UNSUPPORTED: t/t_assoc_unsup.v:28:12: Unsupported: `with` clause on assoc arrays with wide value types in method 'or'
: ... note: In instance 't'
28 | w = qw.or with (item + 1);
| ^~
%Error-UNSUPPORTED: t/t_assoc_unsup.v:29:12: Unsupported: `with` clause on assoc arrays with wide value types in method 'xor'
: ... note: In instance 't'
29 | w = qw.xor with (item + 1);
| ^~~
%Error-UNSUPPORTED: t/t_assoc_unsup.v:31:12: Unsupported: `with` clause on assoc arrays with wide value types in method 'and'
: ... note: In instance 't'
31 | w = qw.and() with (item + 1);
| ^~~
%Error-UNSUPPORTED: t/t_assoc_unsup.v:32:12: Unsupported: `with` clause on assoc arrays with wide value types in method 'or'
: ... note: In instance 't'
32 | w = qw.or() with (item + 1);
| ^~
%Error-UNSUPPORTED: t/t_assoc_unsup.v:33:12: Unsupported: `with` clause on assoc arrays with wide value types in method 'xor'
: ... note: In instance 't'
33 | w = qw.xor() with (item + 1);
| ^~~
%Error-UNSUPPORTED: t/t_assoc_unsup.v:35:14: Unsupported: `with` clause on assoc arrays with wide value types in method 'find'
: ... note: In instance 't'
35 | qwv = qw.find with (item == 2);
| ^~~~
%Error-UNSUPPORTED: t/t_assoc_unsup.v:36:14: Unsupported: `with` clause on assoc arrays with wide value types in method 'find_first'
: ... note: In instance 't'
36 | qwv = qw.find_first with (item == 2);
| ^~~~~~~~~~
%Error-UNSUPPORTED: t/t_assoc_unsup.v:37:14: Unsupported: `with` clause on assoc arrays with wide value types in method 'find_last'
: ... note: In instance 't'
37 | qwv = qw.find_last with (item == 2);
| ^~~~~~~~~
%Error-UNSUPPORTED: t/t_assoc_unsup.v:39:13: Unsupported: `with` clause on assoc arrays with wide value types in method 'find_index'
: ... note: In instance 't'
39 | qi = qw.find_index with (item == 2);
| ^~~~~~~~~~
%Error-UNSUPPORTED: t/t_assoc_unsup.v:40:13: Unsupported: `with` clause on assoc arrays with wide value types in method 'find_first_index'
: ... note: In instance 't'
40 | qi = qw.find_first_index with (item == 2);
| ^~~~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assoc_unsup.v:41:13: Unsupported: `with` clause on assoc arrays with wide value types in method 'find_last_index'
: ... note: In instance 't'
41 | qi = qw.find_last_index with (item == 2);
| ^~~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_assoc_unsup.v:45:14: Unsupported: `with` clause on assoc arrays with wide value types in method 'map'
: ... note: In instance 't'
45 | qwv = qw.map(el) with (el / 100);
| ^~~
%Error: Exiting due to

View File

@ -9,8 +9,8 @@
import vltest_bootstrap
test.scenarios('vlt')
test.scenarios("linter")
test.lint(expect_filename=test.golden_filename, verilator_flags2=['--assert'], fails=True)
test.lint(fails=True, expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,50 @@
// 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;
initial begin
bit[229:0] qw[int]; // Wide values
bit[229:0] qwe[int]; // Wide values - empty
bit[229:0] qwv[$]; // Wide values - Value returns
int qi[$]; // Index returns
bit[229:0] w;
qw = '{10: 1, 11: 2, 12: 2, 13: 4, 14: 3};
qwv = qwe.min(x) with (x + 1);
qwv = qwe.max(x) with (x + 1);
w = qw.sum with (item + 1);
w = qw.product with (item + 1);
w = qwe.sum with (item + 1);
w = qwe.product with (item + 1);
qw = '{10: 230'b1100, 11: 230'b1010};
w = qw.and with (item + 1);
w = qw.or with (item + 1);
w = qw.xor with (item + 1);
w = qw.and() with (item + 1);
w = qw.or() with (item + 1);
w = qw.xor() with (item + 1);
qwv = qw.find with (item == 2);
qwv = qw.find_first with (item == 2);
qwv = qw.find_last with (item == 2);
qi = qw.find_index with (item == 2);
qi = qw.find_first_index with (item == 2);
qi = qw.find_last_index with (item == 2);
// Map method (IEEE 1800-2023 7.12.5)
qw = '{1: 100, 2: 200, 3: 300};
qwv = qw.map(el) with (el / 100);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -15,6 +15,6 @@ test.compile(verilator_flags2=['--stats'])
test.execute()
test.file_grep(test.stats, r'Impure case expressions\s+(\d+)', 2)
test.file_grep(test.stats, r'LiftExpr, lifted calls\s+(\d+)', 3)
test.passes()

View File

@ -11,7 +11,8 @@ import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=["--stats"])
# This tests combining CFuncs, but Dfg would inline the submodule, disabling
test.compile(verilator_flags2=["--stats", "-fno-dfg"])
test.execute()

View File

@ -15,6 +15,6 @@ test.compile(verilator_flags2=['--stats'])
test.execute()
test.file_grep(test.stats, r'Impure case expressions\s+(\d+)', 2)
test.file_grep(test.stats, r'LiftExpr, lifted calls\s+(\d+)', 3)
test.passes()

View File

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

View File

@ -0,0 +1,47 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
// verilog_format: off
`define stop $stop
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
module top;
bit clk = 1'b0;
always #1 clk = ~clk;
logic [2:0] cyc = 3'd0;
int count = 0;
always @(posedge clk) begin
// verilator lint_off CASEWITHX
case (cyc) inside
3'b000: begin
$display("case inside 000");
++count;
end
3'b001: begin
$display("case inside 001");
++count;
end
// Should match z
3'b01?: begin
$display("case inside 01?");
++count;
end
// Should match x
3'b1xx: begin
$display("case inside 1xx");
++count;
end
endcase
// verilator lint_on CASEWITHX
cyc <= cyc + 3'd1;
if (&cyc) begin
`checkh(count, 8);
$finish;
end
end
endmodule

Some files were not shown because too many files have changed in this diff Show More