Merge branch 'verilator:master' into fix/force-unpacked-bitselect
This commit is contained in:
commit
a3d517c4bc
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
33
Changes
|
|
@ -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
|
||||
|
|
|
|||
14
Makefile.in
14
Makefile.in
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -861,6 +861,7 @@ hx
|
|||
hyperthreading
|
||||
hyperthreads
|
||||
icecream
|
||||
ico
|
||||
idmap
|
||||
ifdef
|
||||
ifdefed
|
||||
|
|
@ -924,6 +925,7 @@ localparams
|
|||
localtime
|
||||
logicals
|
||||
longint
|
||||
lookups
|
||||
lossy
|
||||
lsb
|
||||
lubc
|
||||
|
|
|
|||
|
|
@ -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` |
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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(); }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
123
src/V3Assert.cpp
123
src/V3Assert.cpp
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
806
src/V3Case.cpp
806
src/V3Case.cpp
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()};
|
||||
|
|
|
|||
138
src/V3Const.cpp
138
src/V3Const.cpp
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) \
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@
|
|||
|
||||
#include "V3EmitCFunc.h"
|
||||
|
||||
#include "V3TSP.h"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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); }
|
||||
}
|
||||
};
|
||||
|
|
|
|||
109
src/V3Gate.cpp
109
src/V3Gate.cpp
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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*/);
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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: "
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
||||
701
src/V3TSP.cpp
701
src/V3TSP.cpp
|
|
@ -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();
|
||||
}
|
||||
57
src/V3TSP.h
57
src/V3TSP.h
|
|
@ -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
|
||||
|
|
@ -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()));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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()});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
20
src/bisonpre
20
src/bisonpre
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 { }
|
||||
;
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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 *-*
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
Loading…
Reference in New Issue