Merge branch 'verilator:master' into fix/force-unpacked-bitselect
This commit is contained in:
commit
a3d517c4bc
|
|
@ -28,6 +28,38 @@ concurrency:
|
||||||
|
|
||||||
jobs:
|
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:
|
build-2404-gcc:
|
||||||
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
|
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
|
||||||
uses: ./.github/workflows/reusable-build.yml
|
uses: ./.github/workflows/reusable-build.yml
|
||||||
|
|
@ -58,7 +90,7 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- {os: ubuntu-24.04, cc: clang, asan: 1}
|
- {os: ubuntu-24.04, cc: clang, asan: 0}
|
||||||
|
|
||||||
build-2204-gcc:
|
build-2204-gcc:
|
||||||
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
|
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
|
||||||
|
|
@ -76,22 +108,6 @@ jobs:
|
||||||
include:
|
include:
|
||||||
- {os: ubuntu-22.04, cc: gcc, asan: 0}
|
- {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:
|
build-osx-gcc:
|
||||||
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
|
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
|
||||||
uses: ./.github/workflows/reusable-build.yml
|
uses: ./.github/workflows/reusable-build.yml
|
||||||
|
|
@ -160,6 +176,54 @@ jobs:
|
||||||
path: ${{ github.workspace }}/repo/verilator.zip
|
path: ${{ github.workspace }}/repo/verilator.zip
|
||||||
name: verilator-win.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:
|
test-2404-gcc:
|
||||||
name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }}
|
name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }}
|
||||||
needs: build-2404-gcc
|
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-1}
|
||||||
- {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-2}
|
- {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:
|
lint-py:
|
||||||
name: Lint Python
|
name: Lint Python
|
||||||
uses: ./.github/workflows/reusable-lint-py.yml
|
uses: ./.github/workflows/reusable-lint-py.yml
|
||||||
|
|
@ -264,17 +304,19 @@ jobs:
|
||||||
name: Test suite passed
|
name: Test suite passed
|
||||||
if: always()
|
if: always()
|
||||||
needs:
|
needs:
|
||||||
|
- build-2604-gcc
|
||||||
|
- build-2604-clang
|
||||||
- build-2404-gcc
|
- build-2404-gcc
|
||||||
- build-2404-clang
|
- build-2404-clang
|
||||||
- build-2204-gcc
|
- build-2204-gcc
|
||||||
- build-2204-clang
|
|
||||||
- build-osx-gcc
|
- build-osx-gcc
|
||||||
- build-osx-clang
|
- build-osx-clang
|
||||||
- build-windows
|
- build-windows
|
||||||
|
- build-2404-gcc
|
||||||
|
- build-2404-clang
|
||||||
- test-2404-gcc
|
- test-2404-gcc
|
||||||
- test-2404-clang
|
- test-2404-clang
|
||||||
- test-2204-gcc
|
- test-2204-gcc
|
||||||
- test-2204-clang
|
|
||||||
- lint-py
|
- lint-py
|
||||||
|
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ jobs:
|
||||||
find obj_coverage -type f | paste -sd, | sed "s/^/files=/" >> "$GITHUB_OUTPUT"
|
find obj_coverage -type f | paste -sd, | sed "s/^/files=/" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Upload to codecov.io
|
- name: Upload to codecov.io
|
||||||
uses: codecov/codecov-action@v6
|
uses: codecov/codecov-action@v7
|
||||||
with:
|
with:
|
||||||
disable_file_fixes: true
|
disable_file_fixes: true
|
||||||
disable_search: true
|
disable_search: true
|
||||||
|
|
@ -197,7 +197,7 @@ jobs:
|
||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@v3.2.0
|
uses: actions/create-github-app-token@v3.2.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ vars.VERILATOR_CI_ID }}
|
client-id: ${{ vars.VERILATOR_CI_ID }}
|
||||||
private-key: ${{ secrets.VERILATOR_CI_KEY }}
|
private-key: ${{ secrets.VERILATOR_CI_KEY }}
|
||||||
owner: verilator
|
owner: verilator
|
||||||
repositories: verilator
|
repositories: verilator
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ jobs:
|
||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@v3.2.0
|
uses: actions/create-github-app-token@v3.2.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ vars.VERILATOR_CI_ID }}
|
client-id: ${{ vars.VERILATOR_CI_ID }}
|
||||||
private-key: ${{ secrets.VERILATOR_CI_KEY }}
|
private-key: ${{ secrets.VERILATOR_CI_KEY }}
|
||||||
permission-actions: write
|
permission-actions: write
|
||||||
permission-pull-requests: write
|
permission-pull-requests: write
|
||||||
|
|
|
||||||
|
|
@ -292,7 +292,7 @@ jobs:
|
||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@v3.2.0
|
uses: actions/create-github-app-token@v3.2.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ vars.VERILATOR_CI_ID }}
|
client-id: ${{ vars.VERILATOR_CI_ID }}
|
||||||
private-key: ${{ secrets.VERILATOR_CI_KEY }}
|
private-key: ${{ secrets.VERILATOR_CI_KEY }}
|
||||||
owner: verilator
|
owner: verilator
|
||||||
repositories: verilator-rtlmeter-results
|
repositories: verilator-rtlmeter-results
|
||||||
|
|
@ -394,7 +394,7 @@ jobs:
|
||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@v3.2.0
|
uses: actions/create-github-app-token@v3.2.0
|
||||||
with:
|
with:
|
||||||
app-id: ${{ vars.VERILATOR_CI_ID }}
|
client-id: ${{ vars.VERILATOR_CI_ID }}
|
||||||
private-key: ${{ secrets.VERILATOR_CI_KEY }}
|
private-key: ${{ secrets.VERILATOR_CI_KEY }}
|
||||||
owner: verilator
|
owner: verilator
|
||||||
repositories: 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:**
|
**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]
|
* 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.
|
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 `--coverage-per-instance` (#7636). [Yogish Sekhar]
|
||||||
* Add NOTREDOP error on reduction and negation operators (#7417) (#7623) (#7624).
|
* Add NOTREDOP error on reduction and negation operators (#7417) (#7623) (#7624).
|
||||||
* Add hierarchy-aware reporting to `verilator_coverage` (#7657). [Yogish Sekhar]
|
* 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]
|
* 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 `+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]
|
* 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 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 weak `until` / `until_with` property operators (#7290) (#7548) (#7685). [Yilou Wang]
|
||||||
* Support `s_eventually` (#7291) (#7508). [Bartłomiej Chmiel, Antmicro Ltd.]
|
* 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 if/if-else in properties (#7692). [Artur Bieniek, Antmicro Ltd.]
|
||||||
* Support process::self().srand() (#7695). [Igor Zaworski, Antmicro Ltd.]
|
* Support process::self().srand() (#7695). [Igor Zaworski, Antmicro Ltd.]
|
||||||
* Support MacOS lldb (#7697). [Tracy Narine]
|
* 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 emitting to_string() for compiler speedup (#7468). [Jakub Michalski, Antmicro Ltd.]
|
||||||
* Optimize additional DFG peephole cases (#7553). [Varun Koyyalagunta, Testorrent USA, Inc.]
|
* 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.]
|
* 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 runtime assertOn() checks (#7707). [Geza Lore, Testorrent USA, Inc.]
|
||||||
* Optimize $countones and $onehot in DFG. [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 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 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 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 segmentation fault when using --trace with --lib-create (#7299) (#7518). [anonkey]
|
||||||
* Fix destructive event state before dynamic waits (#7340). [Nick Brereton]
|
* Fix destructive event state before dynamic waits (#7340). [Nick Brereton]
|
||||||
* Fix ALWCOMBORDER on variable ordering (#7350) (#7608). [Cookie]
|
* 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 loss of events due to bit shift (#7670). [Artur Bieniek, Antmicro Ltd.]
|
||||||
* Fix parameter read through locally-declared interface instance (#7679). [Nick Brereton]
|
* Fix parameter read through locally-declared interface instance (#7679). [Nick Brereton]
|
||||||
* Fix skipping nulls in $sscanf (#7689).
|
* 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 (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 `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.]
|
* 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 Makefile action to not write to ${srcdir} (#7715). [Larry Doolittle]
|
||||||
* Fix splitting functions containing fork logic (#7717). [Mateusz Gancarz, Antmicro Ltd.]
|
* Fix splitting functions containing fork logic (#7717). [Mateusz Gancarz, Antmicro Ltd.]
|
||||||
* Fix optimizations of assignments with timing controls (#7718). [Ryszard Rozak, 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
|
Verilator 5.048 2026-04-26
|
||||||
|
|
|
||||||
14
Makefile.in
14
Makefile.in
|
|
@ -495,6 +495,11 @@ MAKE_FILES = \
|
||||||
src/Makefile*.in \
|
src/Makefile*.in \
|
||||||
test_regress/Makefile* \
|
test_regress/Makefile* \
|
||||||
|
|
||||||
|
# Markdown
|
||||||
|
MD_FILES = \
|
||||||
|
*.md \
|
||||||
|
*/*.md \
|
||||||
|
|
||||||
# Perl programs
|
# Perl programs
|
||||||
PERL_PROGRAMS = \
|
PERL_PROGRAMS = \
|
||||||
bin/redirect \
|
bin/redirect \
|
||||||
|
|
@ -553,7 +558,7 @@ YAML_FILES = \
|
||||||
# Format
|
# Format
|
||||||
|
|
||||||
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 = beautysh
|
||||||
BEAUTYSH_FLAGS = --indent-size 2
|
BEAUTYSH_FLAGS = --indent-size 2
|
||||||
|
|
@ -590,6 +595,13 @@ format-make mbake:
|
||||||
$(MBAKE) --version
|
$(MBAKE) --version
|
||||||
$(MBAKE) $(MBAKE_FLAGS) $(MAKE_FILES)
|
$(MBAKE) $(MBAKE_FLAGS) $(MAKE_FILES)
|
||||||
|
|
||||||
|
MDFORMAT = mdformat
|
||||||
|
MDFORMAT_FLAGS =
|
||||||
|
|
||||||
|
format-md:
|
||||||
|
$(MDFORMAT) --version
|
||||||
|
$(MDFORMAT) $(MDFORMAT_FLAGS) $(MD_FILES)
|
||||||
|
|
||||||
YAPF = yapf
|
YAPF = yapf
|
||||||
YAPF_FLAGS = -i --parallel
|
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
|
404allen404
|
||||||
Adam Bagley
|
Adam Bagley
|
||||||
|
Adam Kostrzewski
|
||||||
Adrian Sampson
|
Adrian Sampson
|
||||||
Adrien Le Masle
|
Adrien Le Masle
|
||||||
أحمد المحمودي (Ahmed El-Mahmoudy)
|
أحمد المحمودي (Ahmed El-Mahmoudy)
|
||||||
|
|
@ -158,6 +159,7 @@ Kefa Chen
|
||||||
Keith Colbert
|
Keith Colbert
|
||||||
Kevin Kiningham
|
Kevin Kiningham
|
||||||
Kevin Nygaard
|
Kevin Nygaard
|
||||||
|
Kornel Uriasz
|
||||||
Kritik Bhimani
|
Kritik Bhimani
|
||||||
Krzysztof Bieganski
|
Krzysztof Bieganski
|
||||||
Krzysztof Boronski
|
Krzysztof Boronski
|
||||||
|
|
@ -325,3 +327,5 @@ Yogish Sekhar
|
||||||
24bit-xjkp
|
24bit-xjkp
|
||||||
Zubin Jain
|
Zubin Jain
|
||||||
Muzaffer Kal
|
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>"
|
.. 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
|
Used to indicate that the assignments to this signal in any blocks
|
||||||
should be isolated into new blocks. Same as
|
should be isolated into new blocks. Same as
|
||||||
:option:`/*verilator&32;isolate_assignments*/` metacomment.
|
:option:`/*verilator&32;isolate_assignments*/` metacomment.
|
||||||
|
|
|
||||||
|
|
@ -737,6 +737,21 @@ Summary:
|
||||||
this is not recommended as may cause additional warnings and ordering
|
this is not recommended as may cause additional warnings and ordering
|
||||||
issues.
|
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
|
||||||
|
|
||||||
.. option:: -fno-inline-funcs
|
.. option:: -fno-inline-funcs
|
||||||
|
|
|
||||||
|
|
@ -341,6 +341,10 @@ or "`ifdef`"'s may break other tools.
|
||||||
|
|
||||||
.. option:: /*verilator&32;isolate_assignments*/
|
.. 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
|
Used after a signal declaration to indicate the assignments to this
|
||||||
signal in any blocks should be isolated into new blocks. When large
|
signal in any blocks should be isolated into new blocks. When large
|
||||||
combinatorial block results in a :option:`UNOPTFLAT` warning, attaching
|
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
|
the conflict. If you run with :vlopt:`--report-unoptflat`, Verilator will
|
||||||
suggest possible candidates for :option:`/*verilator&32;split_var*/`.
|
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
|
Before version 5.000, the UNOPTFLAT warning may also have been due to
|
||||||
clock enables, identified from the reported path going through a clock
|
clock enables, identified from the reported path going through a clock
|
||||||
gating instance. To fix these, the clock_enable meta comment was used.
|
gating instance. To fix these, the clock_enable meta comment was used.
|
||||||
|
|
|
||||||
|
|
@ -861,6 +861,7 @@ hx
|
||||||
hyperthreading
|
hyperthreading
|
||||||
hyperthreads
|
hyperthreads
|
||||||
icecream
|
icecream
|
||||||
|
ico
|
||||||
idmap
|
idmap
|
||||||
ifdef
|
ifdef
|
||||||
ifdefed
|
ifdefed
|
||||||
|
|
@ -924,6 +925,7 @@ localparams
|
||||||
localtime
|
localtime
|
||||||
logicals
|
logicals
|
||||||
longint
|
longint
|
||||||
|
lookups
|
||||||
lossy
|
lossy
|
||||||
lsb
|
lsb
|
||||||
lubc
|
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>
|
template <typename... snprintf_args_ts>
|
||||||
static size_t _vl_snprintf_string(std::string& str, const char* format,
|
static size_t _vl_snprintf_string(std::string& str, const char* format,
|
||||||
snprintf_args_ts... args) VL_MT_SAFE {
|
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,
|
extern void VL_WARN_MT(const char* filename, int linenum, const char* hier,
|
||||||
const char* msg) VL_MT_SAFE;
|
const char* msg) VL_MT_SAFE;
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
/// Print a string, multithread safe. Eventually VL_PRINTF will get called.
|
/// 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;
|
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
|
/// 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;
|
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
|
/// 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()); }
|
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
|
// EMIT_RULE: VL_RANDOM: oclean=dirty
|
||||||
inline IData VL_RANDOM_I() VL_MT_SAFE { return vl_rand64(); }
|
inline IData VL_RANDOM_I() VL_MT_SAFE { return vl_rand64(); }
|
||||||
inline QData VL_RANDOM_Q() 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); }
|
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]; }
|
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]; }
|
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
|
// METHODS
|
||||||
EData& at(size_t index) VL_MT_SAFE { return m_storage[index]; }
|
EData& at(size_t index) VL_MT_SAFE { return m_storage[index]; }
|
||||||
|
|
@ -1280,7 +1302,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
T_Value r_sum() const {
|
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;
|
for (const auto& i : m_map) out += i.second;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
@ -1291,8 +1313,9 @@ public:
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
T_Value r_product() const {
|
T_Value r_product() const {
|
||||||
if (m_map.empty()) return T_Value(0); // The big three do it this way
|
// The big three return 0 when assoc array is empty
|
||||||
T_Value out = T_Value(1);
|
if (m_map.empty()) return T_Value{};
|
||||||
|
T_Value out = T_Value{1};
|
||||||
for (const auto& i : m_map) out *= i.second;
|
for (const auto& i : m_map) out *= i.second;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
@ -1304,8 +1327,9 @@ public:
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
T_Value r_and() const {
|
T_Value r_and() const {
|
||||||
if (m_map.empty()) return T_Value(0); // The big three do it this way
|
// The big three return 0 when assoc array is empty
|
||||||
T_Value out = ~T_Value(0);
|
if (m_map.empty()) return T_Value{};
|
||||||
|
T_Value out = m_map.cbegin()->second;
|
||||||
for (const auto& i : m_map) out &= i.second;
|
for (const auto& i : m_map) out &= i.second;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
@ -1317,7 +1341,7 @@ public:
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
T_Value r_or() const {
|
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;
|
for (const auto& i : m_map) out |= i.second;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
@ -1328,7 +1352,7 @@ public:
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
T_Value r_xor() const {
|
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;
|
for (const auto& i : m_map) out ^= i.second;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ compiledb==0.10.7
|
||||||
distro==1.9.0
|
distro==1.9.0
|
||||||
gersemi==0.23.1
|
gersemi==0.23.1
|
||||||
mbake==1.4.3
|
mbake==1.4.3
|
||||||
|
mdformat==1.0.0
|
||||||
mypy==1.19.0
|
mypy==1.19.0
|
||||||
pylint==3.0.2
|
pylint==3.0.2
|
||||||
ruff==0.14.8
|
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
|
V3Simulate.h
|
||||||
V3Slice.h
|
V3Slice.h
|
||||||
V3Split.h
|
V3Split.h
|
||||||
V3SplitAs.h
|
|
||||||
V3SplitVar.h
|
V3SplitVar.h
|
||||||
V3StackCount.h
|
V3StackCount.h
|
||||||
V3Stats.h
|
V3Stats.h
|
||||||
|
|
@ -183,7 +182,6 @@ set(HEADERS
|
||||||
V3String.h
|
V3String.h
|
||||||
V3Subst.h
|
V3Subst.h
|
||||||
V3SymTable.h
|
V3SymTable.h
|
||||||
V3TSP.h
|
|
||||||
V3Table.h
|
V3Table.h
|
||||||
V3Task.h
|
V3Task.h
|
||||||
V3ThreadPool.h
|
V3ThreadPool.h
|
||||||
|
|
@ -351,14 +349,12 @@ set(COMMON_SOURCES
|
||||||
V3Scoreboard.cpp
|
V3Scoreboard.cpp
|
||||||
V3Slice.cpp
|
V3Slice.cpp
|
||||||
V3Split.cpp
|
V3Split.cpp
|
||||||
V3SplitAs.cpp
|
|
||||||
V3SplitVar.cpp
|
V3SplitVar.cpp
|
||||||
V3StackCount.cpp
|
V3StackCount.cpp
|
||||||
V3Stats.cpp
|
V3Stats.cpp
|
||||||
V3StatsReport.cpp
|
V3StatsReport.cpp
|
||||||
V3String.cpp
|
V3String.cpp
|
||||||
V3Subst.cpp
|
V3Subst.cpp
|
||||||
V3TSP.cpp
|
|
||||||
V3Table.cpp
|
V3Table.cpp
|
||||||
V3Task.cpp
|
V3Task.cpp
|
||||||
V3ThreadPool.cpp
|
V3ThreadPool.cpp
|
||||||
|
|
|
||||||
|
|
@ -333,11 +333,9 @@ RAW_OBJS_PCH_ASTNOMT = \
|
||||||
V3Scoreboard.o \
|
V3Scoreboard.o \
|
||||||
V3Slice.o \
|
V3Slice.o \
|
||||||
V3Split.o \
|
V3Split.o \
|
||||||
V3SplitAs.o \
|
|
||||||
V3SplitVar.o \
|
V3SplitVar.o \
|
||||||
V3StackCount.o \
|
V3StackCount.o \
|
||||||
V3Subst.o \
|
V3Subst.o \
|
||||||
V3TSP.o \
|
|
||||||
V3Table.o \
|
V3Table.o \
|
||||||
V3Task.o \
|
V3Task.o \
|
||||||
V3Timing.o \
|
V3Timing.o \
|
||||||
|
|
|
||||||
123
src/V3Assert.cpp
123
src/V3Assert.cpp
|
|
@ -23,6 +23,79 @@
|
||||||
|
|
||||||
VL_DEFINE_DEBUG_FUNCTIONS;
|
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
|
// AssertDeFutureVisitor
|
||||||
// If any AstFuture, then move all non-future varrefs to be one cycle behind,
|
// 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);
|
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
|
// 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,
|
// 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.
|
// 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
|
//========== Case assertions
|
||||||
|
|
@ -914,12 +1001,6 @@ class AssertVisitor final : public VNVisitor {
|
||||||
visitAssertionIterate(nodep, nodep->failsp());
|
visitAssertionIterate(nodep, nodep->failsp());
|
||||||
}
|
}
|
||||||
void visit(AstAssertCtl* nodep) override {
|
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);
|
iterateChildren(nodep);
|
||||||
|
|
||||||
if (!resolveAssertType(nodep)) {
|
if (!resolveAssertType(nodep)) {
|
||||||
|
|
@ -1007,10 +1088,12 @@ class AssertVisitor final : public VNVisitor {
|
||||||
VL_RESTORER(m_modPastNum);
|
VL_RESTORER(m_modPastNum);
|
||||||
VL_RESTORER(m_modStrobeNum);
|
VL_RESTORER(m_modStrobeNum);
|
||||||
VL_RESTORER(m_modExpr2Sen2DelayedAlwaysp);
|
VL_RESTORER(m_modExpr2Sen2DelayedAlwaysp);
|
||||||
|
VL_RESTORER(m_finalp);
|
||||||
m_modp = nodep;
|
m_modp = nodep;
|
||||||
m_modPastNum = 0;
|
m_modPastNum = 0;
|
||||||
m_modStrobeNum = 0;
|
m_modStrobeNum = 0;
|
||||||
m_modExpr2Sen2DelayedAlwaysp.clear();
|
m_modExpr2Sen2DelayedAlwaysp.clear();
|
||||||
|
m_finalp = nullptr;
|
||||||
iterateChildren(nodep);
|
iterateChildren(nodep);
|
||||||
}
|
}
|
||||||
void visit(AstNodeProcedure* nodep) override {
|
void visit(AstNodeProcedure* nodep) override {
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,11 @@
|
||||||
|
|
||||||
//============================================================================
|
//============================================================================
|
||||||
|
|
||||||
|
class V3AssertCommon final {
|
||||||
|
public:
|
||||||
|
static void collectDefaultDisable(AstNetlist* nodep) VL_MT_DISABLED;
|
||||||
|
};
|
||||||
|
|
||||||
class V3Assert final {
|
class V3Assert final {
|
||||||
public:
|
public:
|
||||||
static void assertAll(AstNetlist* nodep) VL_MT_DISABLED;
|
static void assertAll(AstNetlist* nodep) VL_MT_DISABLED;
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
|
|
||||||
#include "V3AssertNfa.h"
|
#include "V3AssertNfa.h"
|
||||||
|
|
||||||
|
#include "V3Assert.h"
|
||||||
#include "V3Const.h"
|
#include "V3Const.h"
|
||||||
#include "V3Graph.h"
|
#include "V3Graph.h"
|
||||||
#include "V3Task.h"
|
#include "V3Task.h"
|
||||||
|
|
@ -109,6 +110,9 @@ public:
|
||||||
// Reject when source is active and condp is false; set only on
|
// Reject when source is active and condp is false; set only on
|
||||||
// outermost required-step Link
|
// outermost required-step Link
|
||||||
bool m_rejectOnFail = false;
|
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
|
// CONSTRUCTORS
|
||||||
SvaTransEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, AstNodeExpr* condp,
|
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)
|
// Mid-window sources for range delays (pure boolean RHS): match-only (isUnbounded)
|
||||||
std::vector<SvaStateVertex*> midSources;
|
std::vector<SvaStateVertex*> midSources;
|
||||||
bool errorEmitted = false; // Builder already emitted specific error; skip generic
|
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; }
|
bool valid() const { return termVertexp != nullptr; }
|
||||||
static BuildResult fail(bool errored = false) { return {nullptr, nullptr, {}, errored}; }
|
static BuildResult fail(bool errored = false) { return {nullptr, nullptr, {}, errored}; }
|
||||||
static BuildResult failWithError() { return {nullptr, nullptr, {}, true}; }
|
static BuildResult failWithError() { return {nullptr, nullptr, {}, true}; }
|
||||||
|
|
@ -198,6 +206,16 @@ class SvaNfaBuilder final {
|
||||||
// (IEEE 1800-2023 16.12.14 outer-wraps-inner).
|
// (IEEE 1800-2023 16.12.14 outer-wraps-inner).
|
||||||
std::vector<AstNodeExpr*> m_outerAbortStack;
|
std::vector<AstNodeExpr*> m_outerAbortStack;
|
||||||
bool m_inUnboundedScope = false; // Sticky: nodes created after inherit liveness
|
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) {
|
AstNodeExpr* throughoutCond(AstNodeExpr* baseCondp, FileLine* flp) {
|
||||||
if (m_throughoutStack.empty()) return baseCondp;
|
if (m_throughoutStack.empty()) return baseCondp;
|
||||||
|
|
@ -365,7 +383,7 @@ class SvaNfaBuilder final {
|
||||||
// failure, sets outErrorEmitted per semantic-error policy and returns false.
|
// failure, sets outErrorEmitted per semantic-error policy and returns false.
|
||||||
bool applyRangeDelay(AstDelay* delayp, AstNodeExpr* rhsExprp, SvaStateVertex*& currentp,
|
bool applyRangeDelay(AstDelay* delayp, AstNodeExpr* rhsExprp, SvaStateVertex*& currentp,
|
||||||
std::vector<SvaStateVertex*>& midSources, FileLine* flp,
|
std::vector<SvaStateVertex*>& midSources, FileLine* flp,
|
||||||
bool& outErrorEmitted) {
|
bool& outErrorEmitted, RangeDelayRejectInfo* rangeRejectInfop = nullptr) {
|
||||||
const int minDelay = getConstInt(delayp->lhsp());
|
const int minDelay = getConstInt(delayp->lhsp());
|
||||||
if (minDelay < 0) {
|
if (minDelay < 0) {
|
||||||
delayp->v3error("Range delay minimum is not a non-negative elaboration-time constant"
|
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
|
// count is O(1) in range regardless of user input; no adversarial N
|
||||||
// blowup is possible.
|
// blowup is possible.
|
||||||
constexpr int kChainLimit = 256;
|
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) {
|
if (range > kChainLimit) {
|
||||||
// Large range: counter FSM. Overlapping triggers during an active
|
// Large range: counter FSM. Overlapping triggers during an active
|
||||||
// count are dropped (non-overlapping semantics only).
|
// count are dropped (non-overlapping semantics only).
|
||||||
|
|
@ -414,8 +442,14 @@ class SvaNfaBuilder final {
|
||||||
guardedEdge(currentp, counterVtxp, flp);
|
guardedEdge(currentp, counterVtxp, flp);
|
||||||
currentp = counterVtxp;
|
currentp = counterVtxp;
|
||||||
} else if (VN_IS(rhsExprp, SExpr)) {
|
} 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();
|
SvaStateVertex* const mergeVtxp = scopedCreateVertex();
|
||||||
|
mergeVtxp->m_isUnbounded = true;
|
||||||
guardedLink(currentp, mergeVtxp, flp);
|
guardedLink(currentp, mergeVtxp, flp);
|
||||||
for (int i = 0; i < range; ++i) {
|
for (int i = 0; i < range; ++i) {
|
||||||
SvaStateVertex* const nextVtxp = scopedCreateVertex();
|
SvaStateVertex* const nextVtxp = scopedCreateVertex();
|
||||||
|
|
@ -424,16 +458,25 @@ class SvaNfaBuilder final {
|
||||||
currentp = nextVtxp;
|
currentp = nextVtxp;
|
||||||
}
|
}
|
||||||
currentp = mergeVtxp;
|
currentp = mergeVtxp;
|
||||||
|
m_inUnboundedScope = true;
|
||||||
} else {
|
} else {
|
||||||
// Pure boolean RHS: register chain. Each mid-position links to
|
// Pure boolean RHS: register chain. Each mid-position links to
|
||||||
// match (match-only); last position is the reject source.
|
// 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);
|
midSources.push_back(currentp);
|
||||||
for (int i = 0; i < range; ++i) {
|
for (int i = 0; i < range; ++i) {
|
||||||
SvaStateVertex* const nextVtxp = scopedCreateVertex();
|
SvaStateVertex* const nextVtxp = scopedCreateVertex();
|
||||||
AstNodeExpr* const notExprp
|
if (m_isCoverSeq) {
|
||||||
= new AstLogNot{flp, sampledRefOrClone(hoistVarp, rhsExprp, flp)};
|
guardedEdge(currentp, nextVtxp, flp);
|
||||||
guardedEdge(currentp, nextVtxp, notExprp, 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);
|
if (i < range - 1) midSources.push_back(nextVtxp);
|
||||||
currentp = nextVtxp;
|
currentp = nextVtxp;
|
||||||
}
|
}
|
||||||
|
|
@ -441,12 +484,44 @@ class SvaNfaBuilder final {
|
||||||
return true;
|
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,
|
BuildResult buildSExpr(AstSExpr* sexprp, SvaStateVertex* entryVtxp,
|
||||||
bool isTopLevelStep = false) {
|
bool isTopLevelStep = false) {
|
||||||
AstDelay* const delayp = VN_CAST(sexprp->delayp(), Delay);
|
AstDelay* const delayp = VN_CAST(sexprp->delayp(), Delay);
|
||||||
if (!delayp || !delayp->isCycleDelay()) return BuildResult::fail();
|
if (!delayp || !delayp->isCycleDelay()) return BuildResult::fail();
|
||||||
|
|
||||||
FileLine* const flp = sexprp->fileline();
|
FileLine* const flp = sexprp->fileline();
|
||||||
|
AstNodeExpr* const exprp = sexprp->exprp();
|
||||||
|
|
||||||
// Handle LHS (preExpr)
|
// Handle LHS (preExpr)
|
||||||
SvaStateVertex* currentp = entryVtxp;
|
SvaStateVertex* currentp = entryVtxp;
|
||||||
|
|
@ -469,10 +544,12 @@ class SvaNfaBuilder final {
|
||||||
|
|
||||||
// Handle delay
|
// Handle delay
|
||||||
std::vector<SvaStateVertex*> rangeMidSources;
|
std::vector<SvaStateVertex*> rangeMidSources;
|
||||||
|
RangeDelayRejectInfo rangeRejectInfo;
|
||||||
|
const bool addRangeReject = isTopLevelStep && !m_inUnboundedScope;
|
||||||
if (delayp->isRangeDelay()) {
|
if (delayp->isRangeDelay()) {
|
||||||
bool errorEmitted = false;
|
bool errorEmitted = false;
|
||||||
if (!applyRangeDelay(delayp, sexprp->exprp(), currentp, rangeMidSources, flp,
|
if (!applyRangeDelay(delayp, sexprp->exprp(), currentp, rangeMidSources, flp,
|
||||||
errorEmitted)) {
|
errorEmitted, addRangeReject ? &rangeRejectInfo : nullptr)) {
|
||||||
return BuildResult::fail(errorEmitted);
|
return BuildResult::fail(errorEmitted);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -487,8 +564,11 @@ class SvaNfaBuilder final {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multi-cycle RHS: recurse (only plain boolean is returned as finalCondp).
|
// Multi-cycle RHS: recurse (only plain boolean is returned as finalCondp).
|
||||||
AstNodeExpr* const exprp = sexprp->exprp();
|
if (exprp->isMultiCycleSva()) {
|
||||||
if (exprp->isMultiCycleSva()) return buildExpr(exprp, currentp, isTopLevelStep);
|
const BuildResult result = buildExpr(exprp, currentp, isTopLevelStep);
|
||||||
|
if (result.valid()) addFiniteRangeReject(rangeRejectInfo, result, flp);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
return {currentp, exprp, std::move(rangeMidSources)};
|
return {currentp, exprp, std::move(rangeMidSources)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -516,6 +596,10 @@ class SvaNfaBuilder final {
|
||||||
if (exceedsAssertUnrollLimit(repp, totalSites)) return BuildResult::failWithError();
|
if (exceedsAssertUnrollLimit(repp, totalSites)) return BuildResult::failWithError();
|
||||||
AstVar* const hoistVarp = tryHoistSampled(exprp, flp, totalSites);
|
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;
|
SvaStateVertex* currentp = entryVtxp;
|
||||||
for (int i = 0; i < minN; ++i) {
|
for (int i = 0; i < minN; ++i) {
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
|
|
@ -531,6 +615,10 @@ class SvaNfaBuilder final {
|
||||||
if (isTopLevelStep && (i == 0 || i == minN - 1)) { linkp->m_rejectOnFail = true; }
|
if (isTopLevelStep && (i == 0 || i == minN - 1)) { linkp->m_rejectOnFail = true; }
|
||||||
currentp = condVtxp;
|
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 (repp->unbounded()) {
|
||||||
if (minN == 0) {
|
if (minN == 0) {
|
||||||
|
|
@ -564,11 +652,19 @@ class SvaNfaBuilder final {
|
||||||
guardedLink(nextVtxp, checkVtxp, sampledRefOrClone(hoistVarp, exprp, flp), flp);
|
guardedLink(nextVtxp, checkVtxp, sampledRefOrClone(hoistVarp, exprp, flp), flp);
|
||||||
guardedLink(checkVtxp, mergeVtxp, flp);
|
guardedLink(checkVtxp, mergeVtxp, flp);
|
||||||
currentp = checkVtxp;
|
currentp = checkVtxp;
|
||||||
|
if (m_isCoverSeq) consMidSources.push_back(checkVtxp);
|
||||||
}
|
}
|
||||||
currentp = mergeVtxp;
|
currentp = mergeVtxp;
|
||||||
}
|
}
|
||||||
// finalCond = nullptr (already checked via Links)
|
// 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).
|
// 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)");
|
UASSERT_OBJ(maxN >= minN, repp, "GotoRep range max < min (V3Width invariant)");
|
||||||
if (exceedsAssertUnrollLimit(repp, maxN)) return BuildResult::failWithError();
|
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
|
// Wait + match per iter -> 2 sites per iteration; range form needs
|
||||||
// sites for every iteration in [0..maxN). NOT($sampled(x)) matches
|
// sites for every iteration in [0..maxN). NOT($sampled(x)) matches
|
||||||
// $sampled(NOT(x)) at the value level (IEEE 1800-2023 16.9.9);
|
// $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
|
if (!lhs.valid() || !rhs.valid()) { // LCOV_EXCL_START -- sub-build fail bail
|
||||||
return BuildResult::fail(lhs.errorEmitted || rhs.errorEmitted);
|
return BuildResult::fail(lhs.errorEmitted || rhs.errorEmitted);
|
||||||
} // LCOV_EXCL_STOP
|
} // 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();
|
SvaStateVertex* const mergeVtxp = scopedCreateVertex();
|
||||||
if (lhs.finalCondp) {
|
if (lhs.finalCondp) {
|
||||||
guardedLink(lhs.termVertexp, mergeVtxp, sampled(lhs.finalCondp->cloneTreePure(false)),
|
guardedLink(lhs.termVertexp, mergeVtxp, sampled(lhs.finalCondp->cloneTreePure(false)),
|
||||||
|
|
@ -970,10 +1086,12 @@ class SvaNfaBuilder final {
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SvaNfaBuilder(SvaGraph& graph, AstNodeModule* modp, V3UniqueNames& propTempNames)
|
SvaNfaBuilder(SvaGraph& graph, AstNodeModule* modp, V3UniqueNames& propTempNames,
|
||||||
|
bool isCoverSeq = false)
|
||||||
: m_graph{graph}
|
: m_graph{graph}
|
||||||
, m_modp{modp}
|
, m_modp{modp}
|
||||||
, m_propTempNames{propTempNames} {}
|
, m_propTempNames{propTempNames}
|
||||||
|
, m_isCoverSeq{isCoverSeq} {}
|
||||||
|
|
||||||
// Reset scope between antecedent and consequent: liveness must not leak.
|
// Reset scope between antecedent and consequent: liveness must not leak.
|
||||||
void resetScope() {
|
void resetScope() {
|
||||||
|
|
@ -1346,8 +1464,11 @@ class SvaNfaLowering final {
|
||||||
// throughout-drop reject; clean up intermediate state signals.
|
// throughout-drop reject; clean up intermediate state signals.
|
||||||
// Phase 3: terminalActive and rejectBase from Links to matchVertex.
|
// Phase 3: terminalActive and rejectBase from Links to matchVertex.
|
||||||
// Builder only adds Links (non-clocked) to matchVertex via addLink in
|
// Builder only adds Links (non-clocked) to matchVertex via addLink in
|
||||||
// wireMatchAndMidSources.
|
// wireMatchAndMidSources. When outPerMidSrcsp is non-null, also collect
|
||||||
void computeTerminalMatchAndReject(LowerCtx& c, AstNodeExpr* snapshotOkp, SignalSet& sigs) {
|
// 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) {
|
for (const SvaTransEdge* const tep : c.edges) {
|
||||||
if (tep->toVtxp() != c.graph.m_matchVertexp) continue;
|
if (tep->toVtxp() != c.graph.m_matchVertexp) continue;
|
||||||
const int fi = tep->fromVtxp()->color();
|
const int fi = tep->fromVtxp()->color();
|
||||||
|
|
@ -1359,6 +1480,18 @@ class SvaNfaLowering final {
|
||||||
if (snapshotOkp) {
|
if (snapshotOkp) {
|
||||||
srcSigp = new AstLogAnd{c.flp, srcSigp, snapshotOkp->cloneTreePure(false)};
|
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) {
|
if (tep->fromVtxp()->m_isCounter) {
|
||||||
sigs.terminalActivep
|
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;
|
SignalSet sigs;
|
||||||
|
|
||||||
// Snapshot comparison expression for disable-iff counter.
|
// Snapshot comparison expression for disable-iff counter.
|
||||||
|
|
@ -1421,18 +1555,28 @@ class SvaNfaLowering final {
|
||||||
new AstVarRef{c.flp, c.disableCntVarp, VAccess::READ}};
|
new AstVarRef{c.flp, c.disableCntVarp, VAccess::READ}};
|
||||||
}
|
}
|
||||||
|
|
||||||
computeTerminalMatchAndReject(c, snapshotOkp, sigs);
|
computeTerminalMatchAndReject(c, snapshotOkp, sigs, outPerMidSrcsp);
|
||||||
|
|
||||||
// Phase 3a: required-step rejection.
|
// Phase 3a: required-step rejection.
|
||||||
// Builder only sets m_rejectOnFail on non-clocked Links with m_condp,
|
// Builder only sets m_rejectOnFail on non-clocked Links with m_condp
|
||||||
// and the source always has a resolved stateSig.
|
// or m_condVtxp, and the source always has a resolved stateSig.
|
||||||
for (const SvaTransEdge* const tep : c.edges) {
|
for (const SvaTransEdge* const tep : c.edges) {
|
||||||
if (!tep->m_rejectOnFail) continue;
|
if (!tep->m_rejectOnFail) continue;
|
||||||
const int fi = tep->fromVtxp()->color();
|
const int fi = tep->fromVtxp()->color();
|
||||||
UASSERT_OBJ(c.vtx[fi]->datap()->stateSigp && tep->m_condp, tep->fromVtxp(),
|
UASSERT_OBJ(c.vtx[fi]->datap()->stateSigp && (tep->m_condp || tep->m_condVtxp),
|
||||||
"rejectOnFail Link must have condp and source stateSig");
|
tep->fromVtxp(),
|
||||||
|
"rejectOnFail Link must have condp/condVtxp and source stateSig");
|
||||||
AstNodeExpr* const srcSigp = c.vtx[fi]->datap()->stateSigp->cloneTreePure(false);
|
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};
|
AstNodeExpr* const failp = new AstLogAnd{c.flp, srcSigp, notCondp};
|
||||||
if (outRequiredStepSrcsp) {
|
if (outRequiredStepSrcsp) {
|
||||||
outRequiredStepSrcsp->push_back(failp->cloneTreePure(false));
|
outRequiredStepSrcsp->push_back(failp->cloneTreePure(false));
|
||||||
|
|
@ -1648,7 +1792,8 @@ public:
|
||||||
AstNodeExpr* disableExprp = nullptr, bool negated = false,
|
AstNodeExpr* disableExprp = nullptr, bool negated = false,
|
||||||
AstNodeExpr** outMatchpp = nullptr, AstVar* disableCntVarp = nullptr,
|
AstNodeExpr** outMatchpp = nullptr, AstVar* disableCntVarp = nullptr,
|
||||||
AstVar* snapshotVarp = 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("");
|
const std::string baseName = m_names.get("");
|
||||||
|
|
||||||
// Number vertices with sequential colors for array indexing.
|
// Number vertices with sequential colors for array indexing.
|
||||||
|
|
@ -1734,7 +1879,7 @@ public:
|
||||||
emitAndCombinerDoneLatchNba(c);
|
emitAndCombinerDoneLatchNba(c);
|
||||||
|
|
||||||
// Phase 3/3a/3b: Compute terminal match/reject signals (cleans up stateSig).
|
// 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(
|
AstNodeExpr* const resultp = assembleResult(
|
||||||
flp, isCover, negated, matchCondp, sigs.terminalActivep, sigs.rejectBasep,
|
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.
|
// Wire match vertex and mid-window sources for a successful NFA build.
|
||||||
static void wireMatchAndMidSources(SvaGraph& graph, const BuildResult& result, FileLine* flp) {
|
static void wireMatchAndMidSources(SvaGraph& graph, const BuildResult& result, FileLine* flp) {
|
||||||
graph.createMatchVertex();
|
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) {
|
for (SvaStateVertex* srcVtxp : result.midSources) {
|
||||||
AstNodeExpr* condp = nullptr;
|
AstNodeExpr* condp = nullptr;
|
||||||
for (AstNodeExpr* const tc : srcVtxp->m_throughoutConds) {
|
for (AstNodeExpr* const tc : srcVtxp->m_throughoutConds) {
|
||||||
|
|
@ -2234,9 +2382,11 @@ class AssertNfaVisitor final : public VNVisitor {
|
||||||
|
|
||||||
FileLine* const flp = assertp->fileline();
|
FileLine* const flp = assertp->fileline();
|
||||||
const bool isCover = VN_IS(assertp, Cover);
|
const bool isCover = VN_IS(assertp, Cover);
|
||||||
|
AstCover* const coverp = VN_CAST(assertp, Cover);
|
||||||
|
const bool isCoverSeq = coverp && coverp->isCoverSeq();
|
||||||
|
|
||||||
SvaGraph graph;
|
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);
|
const BuildResult result = buildAssertionGraph(builder, graph, seqBodyp, parts, flp);
|
||||||
if (result.valid()) wireMatchAndMidSources(graph, result, flp);
|
if (result.valid()) wireMatchAndMidSources(graph, result, flp);
|
||||||
|
|
@ -2268,12 +2418,18 @@ class AssertNfaVisitor final : public VNVisitor {
|
||||||
= !isCover && !parts.hasImplication && assertWithFailp && assertWithFailp->failsp();
|
= !isCover && !parts.hasImplication && assertWithFailp && assertWithFailp->failsp();
|
||||||
std::vector<AstNodeExpr*> requiredStepSrcs;
|
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 alwaysTriggerp = new AstConst{flp, AstConst::BitTrue{}};
|
||||||
AstNodeExpr* const outputExprp
|
AstNodeExpr* const outputExprp = m_loweringp->lower(
|
||||||
= m_loweringp->lower(flp, graph, alwaysTriggerp, senTreep, result.finalCondp, isCover,
|
flp, graph, alwaysTriggerp, senTreep, result.finalCondp, isCover,
|
||||||
disableExprp ? disableExprp->cloneTreePure(false) : nullptr,
|
disableExprp ? disableExprp->cloneTreePure(false) : nullptr, negated,
|
||||||
negated, needMatch ? &matchExprp : nullptr, disableCntVarp,
|
needMatch ? &matchExprp : nullptr, disableCntVarp, snapshotVarp,
|
||||||
snapshotVarp, needPerSrcFail ? &requiredStepSrcs : nullptr);
|
needPerSrcFail ? &requiredStepSrcs : nullptr, isCoverSeq ? &perMidSrcs : nullptr);
|
||||||
|
|
||||||
AstSenTree* const perSrcSenTreep
|
AstSenTree* const perSrcSenTreep
|
||||||
= (requiredStepSrcs.size() >= 2) ? senTreep->cloneTree(false) : nullptr;
|
= (requiredStepSrcs.size() >= 2) ? senTreep->cloneTree(false) : nullptr;
|
||||||
|
|
@ -2286,9 +2442,38 @@ class AssertNfaVisitor final : public VNVisitor {
|
||||||
attachMatchHandlers(flp, assertAssertp, assertWithFailp, needMatch ? matchExprp : nullptr,
|
attachMatchHandlers(flp, assertAssertp, assertWithFailp, needMatch ? matchExprp : nullptr,
|
||||||
perSrcSenTreep, requiredStepSrcs);
|
perSrcSenTreep, requiredStepSrcs);
|
||||||
|
|
||||||
AstNode* const innerPropp = propSpecp->propp();
|
if (isCoverSeq && perMidSrcs.size() > 1) {
|
||||||
innerPropp->replaceWith(outputExprp);
|
// Clone AstCover (N-1) times, each gated by its own per-mid signal.
|
||||||
VL_DO_DANGLING(pushDeletep(innerPropp), innerPropp);
|
// 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);
|
UINFO(4, "NFA converted assertion at " << flp << endl);
|
||||||
}
|
}
|
||||||
|
|
@ -2301,7 +2486,7 @@ class AssertNfaVisitor final : public VNVisitor {
|
||||||
VL_RESTORER(m_defaultDisablep);
|
VL_RESTORER(m_defaultDisablep);
|
||||||
m_modp = nodep;
|
m_modp = nodep;
|
||||||
m_defaultClockingp = nullptr;
|
m_defaultClockingp = nullptr;
|
||||||
m_defaultDisablep = nullptr;
|
m_defaultDisablep = nodep->defaultDisablep();
|
||||||
SvaNfaLowering lowering{nodep};
|
SvaNfaLowering lowering{nodep};
|
||||||
m_loweringp = &lowering;
|
m_loweringp = &lowering;
|
||||||
iterateChildren(nodep);
|
iterateChildren(nodep);
|
||||||
|
|
@ -2310,9 +2495,12 @@ class AssertNfaVisitor final : public VNVisitor {
|
||||||
if (nodep->isDefault() && !m_defaultClockingp) m_defaultClockingp = nodep;
|
if (nodep->isDefault() && !m_defaultClockingp) m_defaultClockingp = nodep;
|
||||||
iterateChildren(nodep);
|
iterateChildren(nodep);
|
||||||
}
|
}
|
||||||
void visit(AstDefaultDisable* nodep) override {
|
void visit(AstGenBlock* nodep) override {
|
||||||
if (!m_defaultDisablep) m_defaultDisablep = nodep;
|
VL_RESTORER(m_defaultDisablep);
|
||||||
|
m_defaultDisablep = nodep->defaultDisablep();
|
||||||
|
iterateChildren(nodep);
|
||||||
}
|
}
|
||||||
|
void visit(AstDefaultDisable* nodep) override {}
|
||||||
void visit(AstAssert* nodep) override { processAssertion(nodep); }
|
void visit(AstAssert* nodep) override { processAssertion(nodep); }
|
||||||
void visit(AstCover* nodep) override { processAssertion(nodep); }
|
void visit(AstCover* nodep) override { processAssertion(nodep); }
|
||||||
void visit(AstRestrict* nodep) override {
|
void visit(AstRestrict* nodep) override {
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
|
|
||||||
#include "V3AssertPre.h"
|
#include "V3AssertPre.h"
|
||||||
|
|
||||||
|
#include "V3Assert.h"
|
||||||
#include "V3Const.h"
|
#include "V3Const.h"
|
||||||
#include "V3Task.h"
|
#include "V3Task.h"
|
||||||
#include "V3UniqueNames.h"
|
#include "V3UniqueNames.h"
|
||||||
|
|
@ -1438,12 +1439,6 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
void visit(AstDefaultDisable* nodep) override {
|
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);
|
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
||||||
}
|
}
|
||||||
void visit(AstInferredDisable* nodep) override {
|
void visit(AstInferredDisable* nodep) override {
|
||||||
|
|
@ -1565,10 +1560,15 @@ private:
|
||||||
VL_RESTORER(m_modp);
|
VL_RESTORER(m_modp);
|
||||||
m_defaultClockingp = nullptr;
|
m_defaultClockingp = nullptr;
|
||||||
m_defaultClkEvtVarp = nullptr;
|
m_defaultClkEvtVarp = nullptr;
|
||||||
m_defaultDisablep = nullptr;
|
m_defaultDisablep = nodep->defaultDisablep();
|
||||||
m_modp = nodep;
|
m_modp = nodep;
|
||||||
iterateChildren(nodep);
|
iterateChildren(nodep);
|
||||||
}
|
}
|
||||||
|
void visit(AstGenBlock* nodep) override {
|
||||||
|
VL_RESTORER(m_defaultDisablep);
|
||||||
|
m_defaultDisablep = nodep->defaultDisablep();
|
||||||
|
iterateChildren(nodep);
|
||||||
|
}
|
||||||
void visit(AstProperty* nodep) override {
|
void visit(AstProperty* nodep) override {
|
||||||
// The body will be visited when will be substituted in place of property reference
|
// The body will be visited when will be substituted in place of property reference
|
||||||
// (AstFuncRef)
|
// (AstFuncRef)
|
||||||
|
|
|
||||||
|
|
@ -342,7 +342,6 @@ public:
|
||||||
VAR_PUBLIC_FLAT, // V3LinkParse moves to AstVar::sigPublic
|
VAR_PUBLIC_FLAT, // V3LinkParse moves to AstVar::sigPublic
|
||||||
VAR_PUBLIC_FLAT_RD, // V3LinkParse moves to AstVar::sigPublic
|
VAR_PUBLIC_FLAT_RD, // V3LinkParse moves to AstVar::sigPublic
|
||||||
VAR_PUBLIC_FLAT_RW, // 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_BIGUINT, // V3LinkParse moves to AstVar::attrScBigUint
|
||||||
VAR_SC_BV, // V3LinkParse moves to AstVar::attrScBv
|
VAR_SC_BV, // V3LinkParse moves to AstVar::attrScBv
|
||||||
VAR_SFORMAT, // V3LinkParse moves to AstVar::attrSFormat
|
VAR_SFORMAT, // V3LinkParse moves to AstVar::attrSFormat
|
||||||
|
|
@ -364,7 +363,7 @@ public:
|
||||||
"TYPEID", "TYPENAME",
|
"TYPEID", "TYPENAME",
|
||||||
"VAR_BASE", "VAR_FORCEABLE", "VAR_FSM_ARC_INCLUDE_COND", "VAR_FSM_RESET_ARC",
|
"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_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"
|
"VAR_SC_BIGUINT", "VAR_SC_BV", "VAR_SFORMAT", "VAR_SPLIT_VAR"
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
@ -1191,6 +1190,19 @@ public:
|
||||||
= {"user", "array", "auto", "ignore", "illegal", "default", "wildcard", "transition"};
|
= {"user", "array", "auto", "ignore", "illegal", "default", "wildcard", "transition"};
|
||||||
return names[m_e];
|
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) {
|
constexpr bool operator==(const VCoverBinsType& lhs, VCoverBinsType::en rhs) {
|
||||||
return lhs.m_e == rhs;
|
return lhs.m_e == rhs;
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,10 @@ public:
|
||||||
void generic(bool flag) { m_generic = flag; }
|
void generic(bool flag) { m_generic = flag; }
|
||||||
std::pair<uint32_t, uint32_t> dimensions(bool includeBasic) const;
|
std::pair<uint32_t, uint32_t> dimensions(bool includeBasic) const;
|
||||||
uint32_t arrayUnpackedElements() const; // 1, or total multiplication of all dimensions
|
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; }
|
static int uniqueNumInc() { return ++s_uniqueNum; }
|
||||||
const char* charIQWN() const {
|
const char* charIQWN() const {
|
||||||
return (isString() ? "N" : isWide() ? "W" : isDouble() ? "D" : isQuad() ? "Q" : "I");
|
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)
|
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
|
uint64_t m_dpiOpenParent = 0; // DPI import open array, if !=0, how many callees
|
||||||
bool m_taskPublic : 1; // Public task
|
bool m_taskPublic : 1; // Public task
|
||||||
bool m_attrIsolateAssign : 1; // User isolate_assignments attribute
|
|
||||||
bool m_classMethod : 1; // Class method
|
bool m_classMethod : 1; // Class method
|
||||||
bool m_didProto : 1; // Did prototype processing
|
bool m_didProto : 1; // Did prototype processing
|
||||||
bool m_prototype : 1; // Just a prototype
|
bool m_prototype : 1; // Just a prototype
|
||||||
|
|
@ -130,7 +129,6 @@ protected:
|
||||||
: AstNode{t, fl}
|
: AstNode{t, fl}
|
||||||
, m_name{name}
|
, m_name{name}
|
||||||
, m_taskPublic{false}
|
, m_taskPublic{false}
|
||||||
, m_attrIsolateAssign{false}
|
|
||||||
, m_classMethod{false}
|
, m_classMethod{false}
|
||||||
, m_didProto{false}
|
, m_didProto{false}
|
||||||
, m_prototype{false}
|
, m_prototype{false}
|
||||||
|
|
@ -179,8 +177,6 @@ public:
|
||||||
uint64_t dpiOpenParent() const { return m_dpiOpenParent; }
|
uint64_t dpiOpenParent() const { return m_dpiOpenParent; }
|
||||||
bool taskPublic() const { return m_taskPublic; }
|
bool taskPublic() const { return m_taskPublic; }
|
||||||
void taskPublic(bool flag) { m_taskPublic = flag; }
|
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; }
|
bool classMethod() const { return m_classMethod; }
|
||||||
void classMethod(bool flag) { m_classMethod = flag; }
|
void classMethod(bool flag) { m_classMethod = flag; }
|
||||||
bool didProto() const { return m_didProto; }
|
bool didProto() const { return m_didProto; }
|
||||||
|
|
@ -301,6 +297,7 @@ class AstNodeModule VL_NOT_FINAL : public AstNode {
|
||||||
VLifetime m_lifetime; // Lifetime
|
VLifetime m_lifetime; // Lifetime
|
||||||
VTimescale m_timeunit; // Global time unit
|
VTimescale m_timeunit; // Global time unit
|
||||||
VOptionBool m_unconnectedDrive; // State of `unconnected_drive
|
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_modPublic : 1; // Module has public references
|
||||||
bool m_modTrace : 1; // Tracing this module
|
bool m_modTrace : 1; // Tracing this module
|
||||||
|
|
@ -351,6 +348,8 @@ public:
|
||||||
string origName() const override { return m_origName; }
|
string origName() const override { return m_origName; }
|
||||||
string someInstanceName() const VL_MT_SAFE { return m_someInstanceName; }
|
string someInstanceName() const VL_MT_SAFE { return m_someInstanceName; }
|
||||||
void someInstanceName(const string& name) { m_someInstanceName = name; }
|
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; }
|
bool inLibrary() const { return m_inLibrary; }
|
||||||
void inLibrary(bool flag) { m_inLibrary = flag; }
|
void inLibrary(bool flag) { m_inLibrary = flag; }
|
||||||
void depth(int value) { m_depth = value; }
|
void depth(int value) { m_depth = value; }
|
||||||
|
|
@ -987,6 +986,7 @@ public:
|
||||||
bool maybePointedTo() const override VL_MT_SAFE { return true; }
|
bool maybePointedTo() const override VL_MT_SAFE { return true; }
|
||||||
void cloneRelink() override { V3ERROR_NA; } // Not cloneable
|
void cloneRelink() override { V3ERROR_NA; } // Not cloneable
|
||||||
AstModule* modp() const { return m_modp; }
|
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
|
// 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*
|
// 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_funcReturn : 1; // Return variable for a function
|
||||||
bool m_attrScBv : 1; // User force bit vector attribute
|
bool m_attrScBv : 1; // User force bit vector attribute
|
||||||
bool m_attrScBigUint : 1; // User force sc_biguint 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_attrSFormat : 1; // User sformat attribute
|
||||||
bool m_attrSplitVar : 1; // declared with split_var metacomment
|
bool m_attrSplitVar : 1; // declared with split_var metacomment
|
||||||
bool m_attrFsmState : 1; // declared with fsm_state 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_attrFsmArcInclCond : 1; // declared with fsm_arc_include_cond metacomment
|
||||||
bool m_fileDescr : 1; // File descriptor
|
bool m_fileDescr : 1; // File descriptor
|
||||||
bool m_gotNansiType : 1; // Linker saw Non-ANSI type declaration
|
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_isConst : 1; // Table contains constant data
|
||||||
bool m_isContinuously : 1; // Ever assigned continuously (for force/release)
|
bool m_isContinuously : 1; // Ever assigned continuously (for force/release)
|
||||||
bool m_hasStrengthAssignment : 1; // Is on LHS of assignment with strength specifier
|
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_funcReturn = false;
|
||||||
m_attrScBv = false;
|
m_attrScBv = false;
|
||||||
m_attrScBigUint = false;
|
m_attrScBigUint = false;
|
||||||
m_attrIsolateAssign = false;
|
|
||||||
m_attrSFormat = false;
|
m_attrSFormat = false;
|
||||||
m_attrSplitVar = false;
|
m_attrSplitVar = false;
|
||||||
m_attrFsmState = false;
|
m_attrFsmState = false;
|
||||||
|
|
@ -2202,6 +2201,7 @@ class AstVar final : public AstNode {
|
||||||
m_attrFsmArcInclCond = false;
|
m_attrFsmArcInclCond = false;
|
||||||
m_fileDescr = false;
|
m_fileDescr = false;
|
||||||
m_gotNansiType = false;
|
m_gotNansiType = false;
|
||||||
|
m_icoMaybeWritten = false;
|
||||||
m_isConst = false;
|
m_isConst = false;
|
||||||
m_isContinuously = false;
|
m_isContinuously = false;
|
||||||
m_hasStrengthAssignment = false;
|
m_hasStrengthAssignment = false;
|
||||||
|
|
@ -2342,7 +2342,6 @@ public:
|
||||||
void attrFileDescr(bool flag) { m_fileDescr = flag; }
|
void attrFileDescr(bool flag) { m_fileDescr = flag; }
|
||||||
void attrScBv(bool flag) { m_attrScBv = flag; }
|
void attrScBv(bool flag) { m_attrScBv = flag; }
|
||||||
void attrScBigUint(bool flag) { m_attrScBigUint = flag; }
|
void attrScBigUint(bool flag) { m_attrScBigUint = flag; }
|
||||||
void attrIsolateAssign(bool flag) { m_attrIsolateAssign = flag; }
|
|
||||||
void attrSFormat(bool flag) { m_attrSFormat = flag; }
|
void attrSFormat(bool flag) { m_attrSFormat = flag; }
|
||||||
void attrSplitVar(bool flag) { m_attrSplitVar = flag; }
|
void attrSplitVar(bool flag) { m_attrSplitVar = flag; }
|
||||||
void attrFsmState(bool flag) { m_attrFsmState = flag; }
|
void attrFsmState(bool flag) { m_attrFsmState = flag; }
|
||||||
|
|
@ -2382,6 +2381,8 @@ public:
|
||||||
void hasStrengthAssignment(bool flag) { m_hasStrengthAssignment = flag; }
|
void hasStrengthAssignment(bool flag) { m_hasStrengthAssignment = flag; }
|
||||||
bool hasUserInit() const { return m_hasUserInit; }
|
bool hasUserInit() const { return m_hasUserInit; }
|
||||||
void hasUserInit(bool flag) { m_hasUserInit = flag; }
|
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; }
|
bool isDpiOpenArray() const VL_MT_SAFE { return m_isDpiOpenArray; }
|
||||||
void isDpiOpenArray(bool flag) { m_isDpiOpenArray = flag; }
|
void isDpiOpenArray(bool flag) { m_isDpiOpenArray = flag; }
|
||||||
bool isHideLocal() const { return m_isHideLocal; }
|
bool isHideLocal() const { return m_isHideLocal; }
|
||||||
|
|
@ -2461,12 +2462,6 @@ public:
|
||||||
bool isWor() const { return varType().isWor(); }
|
bool isWor() const { return varType().isWor(); }
|
||||||
bool isWiredNet() const { return varType().isWiredNet(); }
|
bool isWiredNet() const { return varType().isWiredNet(); }
|
||||||
bool isTemp() const { return varType().isTemp(); }
|
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 isClassMember() const { return varType() == VVarType::MEMBER; }
|
||||||
bool isVirtIface() const {
|
bool isVirtIface() const {
|
||||||
if (AstIfaceRefDType* const dtp = VN_CAST(dtypep(), IfaceRefDType)) {
|
if (AstIfaceRefDType* const dtp = VN_CAST(dtypep(), IfaceRefDType)) {
|
||||||
|
|
@ -2518,7 +2513,6 @@ public:
|
||||||
bool attrFsmRegisterWrapper() const { return m_attrFsmRegisterWrapper; }
|
bool attrFsmRegisterWrapper() const { return m_attrFsmRegisterWrapper; }
|
||||||
bool attrFsmResetArc() const { return m_attrFsmResetArc; }
|
bool attrFsmResetArc() const { return m_attrFsmResetArc; }
|
||||||
bool attrFsmArcInclCond() const { return m_attrFsmArcInclCond; }
|
bool attrFsmArcInclCond() const { return m_attrFsmArcInclCond; }
|
||||||
bool attrIsolateAssign() const { return m_attrIsolateAssign; }
|
|
||||||
AstIface* sensIfacep() const { return m_sensIfacep; }
|
AstIface* sensIfacep() const { return m_sensIfacep; }
|
||||||
VRandAttr rand() const { return m_rand; }
|
VRandAttr rand() const { return m_rand; }
|
||||||
string verilogKwd() const override;
|
string verilogKwd() const override;
|
||||||
|
|
@ -2530,7 +2524,6 @@ public:
|
||||||
// This is getting connected to fromp; keep attributes
|
// This is getting connected to fromp; keep attributes
|
||||||
// Note the method below too
|
// Note the method below too
|
||||||
if (fromp->attrFileDescr()) attrFileDescr(true);
|
if (fromp->attrFileDescr()) attrFileDescr(true);
|
||||||
if (fromp->attrIsolateAssign()) attrIsolateAssign(true);
|
|
||||||
if (fromp->isContinuously()) isContinuously(true);
|
if (fromp->isContinuously()) isContinuously(true);
|
||||||
}
|
}
|
||||||
void propagateWrapAttrFrom(const AstVar* fromp) {
|
void propagateWrapAttrFrom(const AstVar* fromp) {
|
||||||
|
|
@ -2809,6 +2802,7 @@ class AstGenBlock final : public AstNodeGen {
|
||||||
std::string m_name; // Name of block
|
std::string m_name; // Name of block
|
||||||
const bool m_unnamed; // Originally unnamed (name change does not affect this)
|
const bool m_unnamed; // Originally unnamed (name change does not affect this)
|
||||||
const bool m_implied; // Not inserted by user
|
const bool m_implied; // Not inserted by user
|
||||||
|
AstDefaultDisable* m_defaultDisablep = nullptr; // Default disable iff in this scope
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AstGenBlock(FileLine* fl, const string& name, AstNode* itemsp, bool implied)
|
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; }
|
void name(const std::string& name) override { m_name = name; }
|
||||||
bool unnamed() const { return m_unnamed; }
|
bool unnamed() const { return m_unnamed; }
|
||||||
bool implied() const { return m_implied; }
|
bool implied() const { return m_implied; }
|
||||||
|
AstDefaultDisable* defaultDisablep() const { return m_defaultDisablep; }
|
||||||
|
void defaultDisablep(AstDefaultDisable* nodep) { m_defaultDisablep = nodep; }
|
||||||
};
|
};
|
||||||
class AstGenCase final : public AstNodeGen {
|
class AstGenCase final : public AstNodeGen {
|
||||||
// Generate 'case'
|
// Generate 'case'
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ protected:
|
||||||
public:
|
public:
|
||||||
ASTGEN_MEMBERS_AstNodeAssign;
|
ASTGEN_MEMBERS_AstNodeAssign;
|
||||||
// Clone single node, just get same type back.
|
// Clone single node, just get same type back.
|
||||||
|
void dump(std::ostream& str) const override;
|
||||||
virtual AstNodeAssign* cloneType(AstNodeExpr* lhsp, AstNodeExpr* rhsp) = 0;
|
virtual AstNodeAssign* cloneType(AstNodeExpr* lhsp, AstNodeExpr* rhsp) = 0;
|
||||||
bool hasDType() const override VL_MT_SAFE { return true; }
|
bool hasDType() const override VL_MT_SAFE { return true; }
|
||||||
virtual bool cleanRhs() const { return true; }
|
virtual bool cleanRhs() const { return true; }
|
||||||
|
|
@ -1606,12 +1607,18 @@ public:
|
||||||
};
|
};
|
||||||
class AstCover final : public AstNodeCoverOrAssert {
|
class AstCover final : public AstNodeCoverOrAssert {
|
||||||
// @astgen op3 := coverincsp: List[AstNode] // Coverage node
|
// @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:
|
public:
|
||||||
ASTGEN_MEMBERS_AstCover;
|
ASTGEN_MEMBERS_AstCover;
|
||||||
AstCover(FileLine* fl, AstNode* propp, AstNode* stmtsp, VAssertType type,
|
AstCover(FileLine* fl, AstNode* propp, AstNode* stmtsp, VAssertType type,
|
||||||
const string& name = "")
|
const string& name = "")
|
||||||
: ASTGEN_SUPER_Cover(fl, propp, stmtsp, type, VAssertDirectiveType::COVER, name) {}
|
: ASTGEN_SUPER_Cover(fl, propp, stmtsp, type, VAssertDirectiveType::COVER, name) {}
|
||||||
string verilogKwd() const override { return "cover"; }
|
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 {
|
class AstRestrict final : public AstNodeCoverOrAssert {
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
|
|
@ -630,6 +630,7 @@ void AstVar::combineType(const AstVar* otherp) {
|
||||||
varType(otherp->varType());
|
varType(otherp->varType());
|
||||||
direction(otherp->direction());
|
direction(otherp->direction());
|
||||||
}
|
}
|
||||||
|
if (otherp->icoMaybeWritten()) icoMaybeWritten(true);
|
||||||
}
|
}
|
||||||
void AstVar::combineType(VVarType type) {
|
void AstVar::combineType(VVarType type) {
|
||||||
// These flags get combined with the existing settings of the flags.
|
// These flags get combined with the existing settings of the flags.
|
||||||
|
|
@ -1259,6 +1260,47 @@ uint32_t AstNodeDType::arrayUnpackedElements() const {
|
||||||
return entries;
|
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 {
|
std::pair<uint32_t, uint32_t> AstNodeDType::dimensions(bool includeBasic) const {
|
||||||
// How many array dimensions (packed,unpacked) does this Var have?
|
// How many array dimensions (packed,unpacked) does this Var have?
|
||||||
uint32_t packed = 0;
|
uint32_t packed = 0;
|
||||||
|
|
@ -1322,6 +1364,12 @@ AstNode* AstArraySel::baseFromp(AstNode* nodep, bool overMembers) {
|
||||||
} else if (VN_IS(nodep, Sel)) {
|
} else if (VN_IS(nodep, Sel)) {
|
||||||
nodep = VN_AS(nodep, Sel)->fromp();
|
nodep = VN_AS(nodep, Sel)->fromp();
|
||||||
continue;
|
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)) {
|
} else if (overMembers && VN_IS(nodep, MemberSel)) {
|
||||||
nodep = VN_AS(nodep, MemberSel)->fromp();
|
nodep = VN_AS(nodep, MemberSel)->fromp();
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -2133,6 +2181,14 @@ void AstNodeCoverOrAssert::dumpJson(std::ostream& str) const {
|
||||||
dumpJsonBoolFuncIf(str, immediate);
|
dumpJsonBoolFuncIf(str, immediate);
|
||||||
dumpJsonBoolFuncIf(str, senFromAlways);
|
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 {
|
void AstClocking::dump(std::ostream& str) const {
|
||||||
this->AstNode::dump(str);
|
this->AstNode::dump(str);
|
||||||
if (isDefault()) str << " [DEFAULT]";
|
if (isDefault()) str << " [DEFAULT]";
|
||||||
|
|
@ -2724,6 +2780,10 @@ void AstNodeArrayDType::dumpJson(std::ostream& str) const {
|
||||||
dumpJsonStr(str, "declRange", cvtToStr(declRange()));
|
dumpJsonStr(str, "declRange", cvtToStr(declRange()));
|
||||||
dumpJsonGen(str);
|
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 {
|
string AstPackArrayDType::prettyDTypeName(bool full) const {
|
||||||
std::ostringstream os;
|
std::ostringstream os;
|
||||||
if (const auto subp = subDTypep()) os << subp->prettyDTypeName(full);
|
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 (noReset()) str << " [!RST]";
|
||||||
if (processQueue()) str << " [PROCQ]";
|
if (processQueue()) str << " [PROCQ]";
|
||||||
if (sampled()) str << " [SAMPLED]";
|
if (sampled()) str << " [SAMPLED]";
|
||||||
if (attrIsolateAssign()) str << " [aISO]";
|
|
||||||
if (attrFsmState()) str << " [aFSMSTATE]";
|
if (attrFsmState()) str << " [aFSMSTATE]";
|
||||||
if (attrFsmResetArc()) str << " [aFSMRESETARC]";
|
if (attrFsmResetArc()) str << " [aFSMRESETARC]";
|
||||||
if (attrFsmArcInclCond()) str << " [aFSMARCCOND]";
|
if (attrFsmArcInclCond()) str << " [aFSMARCCOND]";
|
||||||
|
|
@ -3169,6 +3228,7 @@ void AstVar::dump(std::ostream& str) const {
|
||||||
str << " [FUNC]";
|
str << " [FUNC]";
|
||||||
}
|
}
|
||||||
if (hasUserInit()) str << " [UINIT]";
|
if (hasUserInit()) str << " [UINIT]";
|
||||||
|
if (icoMaybeWritten()) str << " [ICOMAYBEWRITTEN]";
|
||||||
if (isDpiOpenArray()) str << " [DPIOPENA]";
|
if (isDpiOpenArray()) str << " [DPIOPENA]";
|
||||||
if (ignorePostWrite()) str << " [IGNPWR]";
|
if (ignorePostWrite()) str << " [IGNPWR]";
|
||||||
if (ignoreSchedWrite()) str << " [IGNWR]";
|
if (ignoreSchedWrite()) str << " [IGNWR]";
|
||||||
|
|
@ -3193,11 +3253,11 @@ void AstVar::dumpJson(std::ostream& str) const {
|
||||||
dumpJsonBoolFuncIf(str, noReset);
|
dumpJsonBoolFuncIf(str, noReset);
|
||||||
dumpJsonBoolFuncIf(str, processQueue);
|
dumpJsonBoolFuncIf(str, processQueue);
|
||||||
dumpJsonBoolFuncIf(str, sampled);
|
dumpJsonBoolFuncIf(str, sampled);
|
||||||
dumpJsonBoolFuncIf(str, attrIsolateAssign);
|
|
||||||
dumpJsonBoolFuncIf(str, attrFsmState);
|
dumpJsonBoolFuncIf(str, attrFsmState);
|
||||||
dumpJsonBoolFuncIf(str, attrFsmResetArc);
|
dumpJsonBoolFuncIf(str, attrFsmResetArc);
|
||||||
dumpJsonBoolFuncIf(str, attrFsmArcInclCond);
|
dumpJsonBoolFuncIf(str, attrFsmArcInclCond);
|
||||||
dumpJsonBoolFuncIf(str, attrFileDescr);
|
dumpJsonBoolFuncIf(str, attrFileDescr);
|
||||||
|
dumpJsonBoolFuncIf(str, icoMaybeWritten);
|
||||||
dumpJsonBoolFuncIf(str, isDpiOpenArray);
|
dumpJsonBoolFuncIf(str, isDpiOpenArray);
|
||||||
dumpJsonBoolFuncIf(str, isFuncReturn);
|
dumpJsonBoolFuncIf(str, isFuncReturn);
|
||||||
dumpJsonBoolFuncIf(str, isFuncLocal);
|
dumpJsonBoolFuncIf(str, isFuncLocal);
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,6 @@ class BeginVisitor final : public VNVisitor {
|
||||||
// NODE STATE
|
// NODE STATE
|
||||||
// AstCase::user1 -> bool, if already purified
|
// AstCase::user1 -> bool, if already purified
|
||||||
|
|
||||||
V3UniqueNames m_caseTempNames; // For generating unique temporary variable names used by cases
|
|
||||||
// STATE - across all visitors
|
// STATE - across all visitors
|
||||||
BeginState* const m_statep; // Current global state
|
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
|
string m_unnamedScope; // Name of begin blocks, including unnamed blocks
|
||||||
int m_ifDepth = 0; // Current if depth
|
int m_ifDepth = 0; // Current if depth
|
||||||
bool m_keepBegins = false; // True if begins should not be inlined
|
bool m_keepBegins = false; // True if begins should not be inlined
|
||||||
VDouble0 m_statPurifiedCaseExpr; // Count of purified case expressions
|
|
||||||
|
|
||||||
// METHODS
|
// METHODS
|
||||||
|
|
||||||
|
|
@ -132,7 +130,6 @@ class BeginVisitor final : public VNVisitor {
|
||||||
}
|
}
|
||||||
void visit(AstNodeModule* nodep) override {
|
void visit(AstNodeModule* nodep) override {
|
||||||
VL_RESTORER(m_modp);
|
VL_RESTORER(m_modp);
|
||||||
VL_RESTORER(m_caseTempNames);
|
|
||||||
m_modp = nodep;
|
m_modp = nodep;
|
||||||
// Rename it (e.g. class under a generate)
|
// Rename it (e.g. class under a generate)
|
||||||
if (m_unnamedScope != "") {
|
if (m_unnamedScope != "") {
|
||||||
|
|
@ -171,7 +168,6 @@ class BeginVisitor final : public VNVisitor {
|
||||||
VL_RESTORER(m_liftedp);
|
VL_RESTORER(m_liftedp);
|
||||||
VL_RESTORER(m_namedScope);
|
VL_RESTORER(m_namedScope);
|
||||||
VL_RESTORER(m_unnamedScope);
|
VL_RESTORER(m_unnamedScope);
|
||||||
VL_RESTORER(m_caseTempNames);
|
|
||||||
m_displayScope = dot(m_displayScope, nodep->name());
|
m_displayScope = dot(m_displayScope, nodep->name());
|
||||||
m_namedScope = "";
|
m_namedScope = "";
|
||||||
m_unnamedScope = "";
|
m_unnamedScope = "";
|
||||||
|
|
@ -415,29 +411,7 @@ class BeginVisitor final : public VNVisitor {
|
||||||
// any BEGINs, but V3Coverage adds them all under the module itself.
|
// any BEGINs, but V3Coverage adds them all under the module itself.
|
||||||
iterateChildren(nodep);
|
iterateChildren(nodep);
|
||||||
}
|
}
|
||||||
void visit(AstCase* nodep) override {
|
void visit(AstCase* nodep) override { iterateChildren(nodep); }
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
// VISITORS - LINT CHECK
|
// VISITORS - LINT CHECK
|
||||||
void visit(AstIf* nodep) override { // not AstNodeIf; other types not covered
|
void visit(AstIf* nodep) override { // not AstNodeIf; other types not covered
|
||||||
VL_RESTORER(m_keepBegins);
|
VL_RESTORER(m_keepBegins);
|
||||||
|
|
@ -464,10 +438,8 @@ class BeginVisitor final : public VNVisitor {
|
||||||
public:
|
public:
|
||||||
// CONSTRUCTORS
|
// CONSTRUCTORS
|
||||||
BeginVisitor(AstNetlist* nodep, BeginState* statep)
|
BeginVisitor(AstNetlist* nodep, BeginState* statep)
|
||||||
: m_caseTempNames{"__VCase"}
|
: m_statep{statep} {
|
||||||
, m_statep{statep} {
|
|
||||||
iterate(nodep);
|
iterate(nodep);
|
||||||
V3Stats::addStatSum("Impure case expressions", m_statPurifiedCaseExpr);
|
|
||||||
}
|
}
|
||||||
~BeginVisitor() override = default;
|
~BeginVisitor() override = default;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
806
src/V3Case.cpp
806
src/V3Case.cpp
|
|
@ -42,10 +42,6 @@
|
||||||
|
|
||||||
VL_DEFINE_DEBUG_FUNCTIONS;
|
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 {
|
class CaseLintVisitor final : public VNVisitorConst {
|
||||||
|
|
@ -119,252 +115,302 @@ class CaseLintVisitor final : public VNVisitorConst {
|
||||||
}
|
}
|
||||||
void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
|
void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
|
||||||
|
|
||||||
public:
|
|
||||||
// CONSTRUCTORS
|
// CONSTRUCTORS
|
||||||
explicit CaseLintVisitor(AstCase* nodep) { iterateConst(nodep); }
|
explicit CaseLintVisitor(AstCase* nodep) { iterateConst(nodep); }
|
||||||
explicit CaseLintVisitor(AstGenCase* nodep) { iterateConst(nodep); }
|
explicit CaseLintVisitor(AstGenCase* nodep) { iterateConst(nodep); }
|
||||||
~CaseLintVisitor() override = default;
|
~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
|
// Case state, as a visitor of each AstNode
|
||||||
|
|
||||||
class CaseVisitor final : public VNVisitor {
|
class CaseVisitor final : public VNVisitor {
|
||||||
// NODE STATE
|
// Maximum width for detailed analysis
|
||||||
// Cleared each Case
|
constexpr static int CASE_DETAILS_MAX_WIDTH = 16;
|
||||||
// AstIf::user3() -> bool. Set true to indicate clone not needed
|
// Levels of priority to be ORed together in top IF tree
|
||||||
const VNUser3InUse m_inuser3;
|
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
|
// STATE
|
||||||
VDouble0 m_statCaseFast; // Statistic tracking
|
// Statistics tracking, as a struct so can be passed to 'const' methods
|
||||||
VDouble0 m_statCaseSlow; // Statistic tracking
|
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
|
const AstNode* m_alwaysp = nullptr; // Always in which case is located
|
||||||
|
|
||||||
// Per-CASE
|
// STATE - per AstCase. Update by 'analyzeCase', treat 'const' otherwise
|
||||||
int m_caseWidth = 0; // Width of valueItems
|
bool m_caseOpaque = false; // Case statement is opaque (non-packed, or non-const conditions)
|
||||||
int m_caseItems = 0; // Number of caseItem unique values
|
size_t m_caseNConditions = 0; // Number of conditions in the case statement
|
||||||
bool m_caseIncomplete = false; // Proven incomplete
|
bool m_caseDetailsValid = false; // Indicates m_caseDetails is valid
|
||||||
bool m_caseNoOverlapsAllCovered = false; // Proven to be synopsys parallel_case compliant
|
struct final {
|
||||||
// For each possible value, the case branch we need
|
bool exhaustive = false; // Proven exhaustive
|
||||||
std::array<AstNode*, 1 << CASE_OVERLAP_WIDTH> m_valueItem;
|
bool noOverlaps = false; // Proven no overlaps between cases
|
||||||
bool m_needToClearCache = false; // Whether cache needs to be cleared
|
// Map from value (index) to the CaseRecord that covers this value
|
||||||
|
std::array<CaseRecord, 1U << CASE_DETAILS_MAX_WIDTH> records;
|
||||||
|
} m_caseDetails;
|
||||||
|
|
||||||
// METHODS
|
// METHODS
|
||||||
//! Determine whether we should check case items are complete
|
|
||||||
//! @return Enum's dtype if should check, nullptr if shouldn't
|
// Xs in case or casez are impossible due to two state simulations.
|
||||||
const AstEnumDType* getEnumCompletionCheckDType(const AstCase* const nodep) {
|
// 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;
|
if (!nodep->uniquePragma() && !nodep->unique0Pragma()) return nullptr;
|
||||||
const AstEnumDType* const enumDtp
|
const AstEnumDType* const enumDtp
|
||||||
= VN_CAST(nodep->exprp()->dtypep()->skipRefToEnump(), EnumDType);
|
= VN_CAST(nodep->exprp()->dtypep()->skipRefToEnump(), EnumDType);
|
||||||
if (!enumDtp) return nullptr; // Case isn't enum
|
if (!enumDtp) return nullptr; // Case isn't enum
|
||||||
const AstBasicDType* const basicp = enumDtp->subDTypep()->basicp();
|
const AstBasicDType* const basicp = enumDtp->subDTypep()->basicp();
|
||||||
if (!basicp) return nullptr; // Not simple type (perhaps IEEE illegal)
|
if (!basicp) return nullptr; // Not simple type (perhaps IEEE illegal)
|
||||||
if (basicp->width() > 32) return nullptr;
|
|
||||||
return enumDtp;
|
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) {
|
// Check and warn if case items are not complete over the given enum type.
|
||||||
const uint32_t numCases = 1UL << m_caseWidth;
|
// Returns true iff the case items cover all enum values/patterns.
|
||||||
for (AstEnumItem* itemp = dtype->itemsp(); itemp;
|
bool checkExhaustiveEnum(const AstCase* const nodep, const AstEnumDType* const enump) {
|
||||||
itemp = VN_AS(itemp->nextp(), EnumItem)) {
|
const uint32_t numCases = 1UL << nodep->exprp()->width();
|
||||||
AstConst* const econstp = VN_AS(itemp->valuep(), Const);
|
for (AstEnumItem* eip = enump->itemsp(); eip; eip = VN_AS(eip->nextp(), EnumItem)) {
|
||||||
V3Number nummask{itemp, econstp->width()};
|
AstConst* const econstp = VN_AS(eip->valuep(), Const);
|
||||||
|
V3Number nummask{eip, econstp->width()};
|
||||||
nummask.opBitsNonX(econstp->num());
|
nummask.opBitsNonX(econstp->num());
|
||||||
const uint32_t mask = nummask.toUInt();
|
V3Number numval{eip, econstp->width()};
|
||||||
V3Number numval{itemp, econstp->width()};
|
|
||||||
numval.opBitsOne(econstp->num());
|
numval.opBitsOne(econstp->num());
|
||||||
|
|
||||||
|
const uint32_t mask = nummask.toUInt();
|
||||||
const uint32_t val = numval.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) {
|
for (uint32_t i = 0; i < numCases; ++i) {
|
||||||
if ((i & mask) == val) {
|
if ((i & mask) != val) continue; // This case is not for this enum value
|
||||||
if (!m_valueItem[i]) {
|
if (m_caseDetails.records[i].itemp) continue; // Covered case
|
||||||
if (!nodep->unique0Pragma())
|
// Warn unless unique0 case which allows no-match
|
||||||
nodep->v3warn(CASEINCOMPLETE, "Enum item "
|
if (!nodep->unique0Pragma()) {
|
||||||
<< itemp->prettyNameQ()
|
nodep->v3warn(CASEINCOMPLETE,
|
||||||
<< " not covered by case\n");
|
"Enum item " << eip->prettyNameQ() << " not covered by case");
|
||||||
m_caseIncomplete = true;
|
|
||||||
return false; // enum has uncovered value by case items
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// 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
|
return true; // enum is fully covered
|
||||||
}
|
}
|
||||||
bool isCaseTreeFast(AstCase* nodep) {
|
|
||||||
int width = 0;
|
// Check and warn if case items are not complete over all possible values.
|
||||||
bool opaque = false;
|
// Returns true iff the case items cover all values of the case expression.
|
||||||
m_caseItems = 0;
|
bool checkExhaustivePacked(AstCase* nodep) {
|
||||||
m_caseNoOverlapsAllCovered = true;
|
const uint32_t numCases = 1UL << nodep->exprp()->width();
|
||||||
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
|
for (uint32_t i = 0; i < numCases; ++i) {
|
||||||
itemp = VN_AS(itemp->nextp(), CaseItem)) {
|
if (m_caseDetails.records[i].itemp) continue; // Covered case
|
||||||
for (AstNode* icondp = itemp->condsp(); icondp; icondp = icondp->nextp()) {
|
if (!nodep->unique0Pragma()) {
|
||||||
if (icondp->width() > width) width = icondp->width();
|
nodep->v3warn(CASEINCOMPLETE,
|
||||||
if (icondp->isDouble()) opaque = true;
|
"Case values incompletely covered (example pattern 0x" << std::hex
|
||||||
if (!VN_IS(icondp, Const)) width = CASE_BARF; // Can't parse; not a constant
|
<< i << ")");
|
||||||
m_caseItems++;
|
|
||||||
}
|
}
|
||||||
|
// 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) {
|
// It's an exhaustive case statement
|
||||||
m_caseNoOverlapsAllCovered = false;
|
return true;
|
||||||
return false; // Too wide for analysis
|
}
|
||||||
|
|
||||||
|
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
|
// Now pick up the values for each assignment
|
||||||
// We can cheat and use uint32_t's because we only support narrow case's
|
// We can cheat and use uint32_t's because we only support narrow case's
|
||||||
bool reportedOverlap = false;
|
bool reportedOverlap = false;
|
||||||
bool reportedSubcase = false;
|
bool hasDefault = false;
|
||||||
bool hasDefaultCase = false;
|
m_caseDetails.noOverlaps = true;
|
||||||
std::map<AstNode*, AstCaseItem*> caseItemMap; // case condition -> case item
|
|
||||||
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
|
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
|
||||||
itemp = VN_AS(itemp->nextp(), CaseItem)) {
|
itemp = VN_AS(itemp->nextp(), CaseItem)) {
|
||||||
for (AstNode* icondp = itemp->condsp(); icondp; icondp = icondp->nextp()) {
|
|
||||||
// UINFOTREE(9, icondp, "", "caseitem");
|
// Default case
|
||||||
AstConst* const iconstp = VN_AS(icondp, Const);
|
if (itemp->isDefault()) {
|
||||||
UASSERT_OBJ(iconstp, nodep, "above 'can't parse' should have caught this");
|
// Default was moved to be the last item by V3LinkDot. Fill remaining cases
|
||||||
if (neverItem(nodep, iconstp)) {
|
for (uint32_t i = 0; i < numValues; ++i) {
|
||||||
// X in casez can't ever be executed
|
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 {
|
} else {
|
||||||
const bool isCondWildcard = iconstp->num().isAnyXZ();
|
// If this case statement doesn't have the priority keyword,
|
||||||
V3Number nummask{itemp, iconstp->width()};
|
// we want to warn on any overlap.
|
||||||
nummask.opBitsNonX(iconstp->num());
|
std::ostringstream examplePattern;
|
||||||
const uint32_t mask = nummask.toUInt();
|
if (iconstp->num().isAnyXZ()) {
|
||||||
V3Number numval{itemp, iconstp->width()};
|
examplePattern << " (example pattern 0x" << std::hex << firstOverlapValue
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
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
|
// If there was no default, check exhaustiveness
|
||||||
// Avoid e.g. priority expanders from going crazy in expansion
|
m_caseDetails.exhaustive = hasDefault || checkExhaustive(nodep);
|
||||||
|| (m_caseWidth >= 8 && (m_caseItems <= (m_caseWidth + 1)))) {
|
// Records now valid
|
||||||
return false; // Not worth simplifying
|
m_caseDetailsValid = true;
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AstNode* replaceCaseFastRecurse(AstNodeExpr* cexprp, int msb, uint32_t upperValue) {
|
// Analyze case statement. Updates 'm_case*' members. Reports warnings.
|
||||||
if (msb < 0) {
|
void analyzeCase(AstCase* nodep) {
|
||||||
// There's no space for a IF. We know upperValue is thus down to a specific
|
// Reset all analysis results
|
||||||
// exact value, so just return the tree value
|
m_caseOpaque = false;
|
||||||
// Note can't clone here, as we're going to check for equivalence above
|
m_caseNConditions = 0;
|
||||||
AstNode* const foundp = m_valueItem[upperValue];
|
m_caseDetailsValid = false;
|
||||||
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)));
|
|
||||||
|
|
||||||
if (tree0p == tree1p) {
|
AstNode* const caseExprp = nodep->exprp();
|
||||||
// Same logic on both sides, so we can just return one of 'em
|
|
||||||
return tree0p;
|
// 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;
|
bool same = true;
|
||||||
for (uint32_t a = upperValue, b = (upperValue | (1UL << msb));
|
for (uint32_t a = upperValue0, b = upperValue1; a < upperValue1; ++a, ++b) {
|
||||||
a < (upperValue | (1UL << msb)); a++, b++) {
|
if (m_caseDetails.records[a].stmtsp != m_caseDetails.records[b].stmtsp) {
|
||||||
if (m_valueItem[a] != m_valueItem[b]) {
|
|
||||||
same = false;
|
same = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -373,161 +419,120 @@ class CaseVisitor final : public VNVisitor {
|
||||||
VL_DO_DANGLING(tree1p->deleteTree(), tree1p);
|
VL_DO_DANGLING(tree1p->deleteTree(), tree1p);
|
||||||
return tree0p;
|
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,....
|
||||||
// CASEx(cexpr,....
|
// -> tree of IF(msb, IF(msb-1, 11, 10)
|
||||||
// -> tree of IF(msb, IF(msb-1, 11, 10)
|
// IF(msb-1, 01, 00))
|
||||||
// IF(msb-1, 01, 00))
|
// TODO: should return AstNodeStmt after #6280
|
||||||
AstNodeExpr* cexprp;
|
AstNode* convertCaseFast(AstCase* nodep) const {
|
||||||
AstExprStmt* cexprStmtp = nullptr;
|
const int caseWidth = nodep->exprp()->width();
|
||||||
if (nodep->exprp()->isPure()) {
|
AstNode* const ifrootp = convertCaseFastRecurse(nodep->exprp(), caseWidth - 1, 0UL);
|
||||||
cexprp = nodep->exprp()->unlinkFrBack();
|
return ifrootp && ifrootp->backp() ? ifrootp->cloneTree(true) : ifrootp;
|
||||||
} 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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void replaceCaseComplicated(AstCase* nodep) {
|
// Convet case statement using generic if/else tree
|
||||||
// CASEx(cexpr,ITEM(icond1,istmts1),ITEM(icond2,istmts2),ITEM(default,istmts3))
|
// CASEx(cexpr,ITEM(icond1,istmts1),ITEM(icond2,istmts2),ITEM(default,istmts3))
|
||||||
// -> IF((cexpr==icond1),istmts1,
|
// -> IF((cexpr==icond1),istmts1,
|
||||||
// IF((EQ (AND MASK cexpr) (AND MASK icond1)
|
// IF((EQ (AND MASK cexpr) (AND MASK icond1)
|
||||||
// ,istmts2, istmts3
|
// ,istmts2, istmts3
|
||||||
AstNodeExpr* cexprp;
|
// TODO: should return AstNodeStmt after #6280
|
||||||
AstExprStmt* cexprStmtp = nullptr;
|
AstNode* convertCaseGeneric(AstCase* nodep) const {
|
||||||
if (nodep->exprp()->isPure()) {
|
// We'll do this in two stages.
|
||||||
cexprp = nodep->exprp()->unlinkFrBack();
|
// First stage, convert the conditions to the appropriate IF AND terms.
|
||||||
} else {
|
bool hasDefault = false;
|
||||||
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;
|
|
||||||
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
|
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
|
||||||
itemp = VN_AS(itemp->nextp(), CaseItem)) {
|
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
|
FileLine* const flp = itemp->fileline();
|
||||||
AstConst* const iconstp = VN_CAST(icondp, Const);
|
|
||||||
if (iconstp && neverItem(nodep, iconstp)) {
|
// Default clause. Just make true, we'll optimize it away later
|
||||||
// X in casez can't ever be executed
|
if (itemp->isDefault()) {
|
||||||
VL_DO_DANGLING(icondp->deleteTree(), icondp);
|
itemp->addCondsp(new AstConst{flp, AstConst::BitTrue{}});
|
||||||
VL_DANGLING(iconstp);
|
hasDefault = true;
|
||||||
// For simplicity, make expression that is not equal, and let later
|
continue;
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
// If there was no default, add a empty one, this greatly simplifies below code
|
// and constant propagation will just eliminate it for us later.
|
||||||
// and constant propagation will just eliminate it for us later.
|
if (!hasDefault) {
|
||||||
nodep->addItemsp(new AstCaseItem{
|
nodep->addItemsp(new AstCaseItem{
|
||||||
nodep->fileline(), new AstConst{nodep->fileline(), AstConst::BitTrue{}}, nullptr});
|
nodep->fileline(), new AstConst{nodep->fileline(), AstConst::BitTrue{}}, nullptr});
|
||||||
}
|
}
|
||||||
UINFOTREE(9, nodep, "", "_comp_COND");
|
|
||||||
// Now build the IF statement tree
|
// 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
|
// This reduces the depth for the bottom elements, at the cost of
|
||||||
// some of the top elements. If we ever have profiling data, we
|
// some of the top elements. If we ever have profiling data, we
|
||||||
// should pull out the most common item from here and instead make
|
// should pull out the most common item from here and instead make
|
||||||
|
|
@ -538,8 +543,11 @@ class CaseVisitor final : public VNVisitor {
|
||||||
AstIf* itemnextp = nullptr;
|
AstIf* itemnextp = nullptr;
|
||||||
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
|
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
|
||||||
itemp = VN_AS(itemp->nextp(), CaseItem)) {
|
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();
|
if (istmtsp) istmtsp->unlinkFrBackWithNext();
|
||||||
|
|
||||||
// Expressioned clause
|
// Expressioned clause
|
||||||
AstNodeExpr* const ifexprp = itemp->condsp()->unlinkFrBack();
|
AstNodeExpr* const ifexprp = itemp->condsp()->unlinkFrBack();
|
||||||
{ // Prepare for next group
|
{ // Prepare for next group
|
||||||
|
|
@ -575,65 +583,76 @@ class CaseVisitor final : public VNVisitor {
|
||||||
itemnextp = newp;
|
itemnextp = newp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UINFOTREE(9, nodep, "", "_comp_TREE");
|
return grouprootp;
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void replaceCaseParallel(AstCase* nodep, bool noOverlapsAllCovered) {
|
// Convert the given case statement to a representation not using AstCase
|
||||||
// Take the notParallelp tree under the case statement created by V3Assert
|
// TODO: should return AstNodeStmt after #6280
|
||||||
// If the statement was proven to have no overlaps and all cases
|
AstNode* convertCase(AstCase* nodep, Stats& stats) const {
|
||||||
// covered, we're done with it.
|
// Determine if we should use the fast bitwise branching tree method
|
||||||
// Else, convert to a normal statement parallel with the case statement.
|
const bool useFastBitTree = [&]() {
|
||||||
if (nodep->notParallelp() && !noOverlapsAllCovered) {
|
// Not if disabled
|
||||||
AstNode* const parp = nodep->notParallelp()->unlinkFrBackWithNext();
|
if (!v3Global.opt.fCase()) return false;
|
||||||
nodep->addNextHere(parp);
|
// 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) {
|
// Convert using the generic if/else tree method
|
||||||
// Xs in case or casez are impossible due to two state simulations
|
++stats.caseGeneric;
|
||||||
if (casep->casex()) {
|
// If a case statement is exhaustive, presume signals involved aren't forming a latch
|
||||||
} else if (casep->casez() || casep->caseInside()) {
|
// TODO: this is broken, but it is as was before
|
||||||
if (itemp->num().isAnyX()) return true;
|
if (m_alwaysp && (!m_caseDetailsValid || m_caseDetails.exhaustive)) {
|
||||||
} else {
|
m_alwaysp->fileline()->warnOff(V3ErrorCode::LATCH, true);
|
||||||
if (itemp->num().isFourState()) return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return convertCaseGeneric(nodep);
|
||||||
}
|
}
|
||||||
|
|
||||||
// VISITORS
|
// VISITORS
|
||||||
void visit(AstCase* nodep) override {
|
void visit(AstCase* nodep) override {
|
||||||
VL_RESTORER(m_caseIncomplete);
|
UASSERT_OBJ(nodep->exprp()->isPure(), nodep,
|
||||||
{ CaseLintVisitor{nodep}; }
|
"Impure case expression should have been removed by V3LiftExpr");
|
||||||
|
|
||||||
|
CaseLintVisitor::apply(nodep);
|
||||||
|
|
||||||
|
// Convert any children first
|
||||||
iterateChildren(nodep);
|
iterateChildren(nodep);
|
||||||
UINFOTREE(9, nodep, "", "case_old");
|
|
||||||
if (isCaseTreeFast(nodep) && v3Global.opt.fCase()) {
|
// Analyze this case statement
|
||||||
// It's a simple priority encoder or complete statement
|
analyzeCase(nodep);
|
||||||
// we can make a tree of statements to avoid extra comparisons
|
|
||||||
++m_statCaseFast;
|
// Take the 'notParallelp' statements under the case statement created by V3Assert.
|
||||||
VL_DO_DANGLING(replaceCaseFast(nodep), nodep);
|
// If the statement was proven to have no overlaps and all cases covered,
|
||||||
} else {
|
// it can be removed. Otherwise insert the assertion after the case statement.
|
||||||
// If a case statement is whole, presume signals involved aren't forming a latch
|
if (AstNode* const stmtp = nodep->notParallelp()) {
|
||||||
if (m_alwaysp && !m_caseIncomplete)
|
stmtp->unlinkFrBackWithNext();
|
||||||
m_alwaysp->fileline()->warnOff(V3ErrorCode::LATCH, true);
|
if (m_caseDetailsValid && m_caseDetails.exhaustive && m_caseDetails.noOverlaps) {
|
||||||
++m_statCaseSlow;
|
++m_stats.provenAssertions;
|
||||||
VL_DO_DANGLING(replaceCaseComplicated(nodep), nodep);
|
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 {
|
void visit(AstAlways* nodep) override {
|
||||||
|
|
@ -645,14 +664,11 @@ class CaseVisitor final : public VNVisitor {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// CONSTRUCTORS
|
// CONSTRUCTORS
|
||||||
explicit CaseVisitor(AstNetlist* nodep) {
|
explicit CaseVisitor(AstNetlist* nodep) { iterate(nodep); }
|
||||||
for (auto& itr : m_valueItem) itr = nullptr;
|
|
||||||
iterate(nodep);
|
|
||||||
if (m_needToClearCache) VIsCached::clearCacheTree();
|
|
||||||
}
|
|
||||||
~CaseVisitor() override {
|
~CaseVisitor() override {
|
||||||
V3Stats::addStat("Optimizations, Cases parallelized", m_statCaseFast);
|
V3Stats::addStat("Optimizations, Cases parallelized", m_stats.caseFast);
|
||||||
V3Stats::addStat("Optimizations, Cases complex", m_statCaseSlow);
|
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) {
|
void V3Case::caseLint(AstGenCase* nodep) {
|
||||||
UINFO(4, __FUNCTION__ << ": ");
|
UINFO(4, __FUNCTION__ << ": ");
|
||||||
{ CaseLintVisitor{nodep}; }
|
CaseLintVisitor::apply(nodep);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,28 +51,18 @@ class CfgLiveVariables final : VNVisitorConst {
|
||||||
|
|
||||||
// METHODS
|
// METHODS
|
||||||
|
|
||||||
// This is to match DFG, but can be extended independently - eventually should handle all
|
static bool isPacked(const AstVarScope* vscp) {
|
||||||
static bool isSupportedPackedDType(const AstNodeDType* dtypep) {
|
AstNodeDType* const dtypep = vscp->dtypep()->skipRefp();
|
||||||
dtypep = dtypep->skipRefp();
|
|
||||||
if (const AstBasicDType* const typep = VN_CAST(dtypep, BasicDType)) {
|
if (const AstBasicDType* const typep = VN_CAST(dtypep, BasicDType)) {
|
||||||
return typep->keyword().isIntNumeric();
|
return typep->keyword().isIntNumeric();
|
||||||
}
|
}
|
||||||
if (const AstPackArrayDType* const typep = VN_CAST(dtypep, PackArrayDType)) {
|
if (VN_IS(dtypep, PackArrayDType)) return true;
|
||||||
return isSupportedPackedDType(typep->subDTypep());
|
|
||||||
}
|
|
||||||
if (const AstNodeUOrStructDType* const typep = VN_CAST(dtypep, NodeUOrStructDType)) {
|
if (const AstNodeUOrStructDType* const typep = VN_CAST(dtypep, NodeUOrStructDType)) {
|
||||||
return typep->packed();
|
return typep->packed();
|
||||||
}
|
}
|
||||||
return false;
|
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) {
|
void updateGen(AstNode* nodep) {
|
||||||
UASSERT_OBJ(!m_abort, nodep, "Doing useless work");
|
UASSERT_OBJ(!m_abort, nodep, "Doing useless work");
|
||||||
m_abort = nodep->exists([&](const AstVarRef* refp) {
|
m_abort = nodep->exists([&](const AstVarRef* refp) {
|
||||||
|
|
@ -80,8 +70,6 @@ class CfgLiveVariables final : VNVisitorConst {
|
||||||
if (refp->access().isWriteOnly()) return false;
|
if (refp->access().isWriteOnly()) return false;
|
||||||
// Grab referenced variable
|
// Grab referenced variable
|
||||||
AstVarScope* const vscp = refp->varScopep();
|
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
|
// Add to gen set, if not killed - assume whole variable read
|
||||||
if (m_currp->m_kill.count(vscp)) return false;
|
if (m_currp->m_kill.count(vscp)) return false;
|
||||||
m_currp->m_gen.emplace(vscp);
|
m_currp->m_gen.emplace(vscp);
|
||||||
|
|
@ -96,11 +84,13 @@ class CfgLiveVariables final : VNVisitorConst {
|
||||||
if (refp->access().isReadOnly()) return false;
|
if (refp->access().isReadOnly()) return false;
|
||||||
// Grab referenced variable
|
// Grab referenced variable
|
||||||
AstVarScope* const vscp = refp->varScopep();
|
AstVarScope* const vscp = refp->varScopep();
|
||||||
// Bail if not a compatible type
|
// Safety case: bail on variables with unusual semantics
|
||||||
if (incompatible(vscp)) return true;
|
if (vscp->varp()->ignoreSchedWrite()) return true;
|
||||||
// If whole written, add to kill set
|
// If not a packed variable, assume it's not a whole update, don't add to kill set
|
||||||
if (refp->nextp()) return false;
|
if (!isPacked(vscp)) return false;
|
||||||
if (VN_IS(refp->abovep(), Sel)) 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);
|
m_currp->m_kill.emplace(vscp);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
@ -198,7 +188,7 @@ public:
|
||||||
CfgLiveVariables analysis{cfg};
|
CfgLiveVariables analysis{cfg};
|
||||||
// If failed, return nullptr
|
// If failed, return nullptr
|
||||||
if (analysis.m_abort) 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;
|
const std::unordered_set<AstVarScope*>& lin = analysis.m_blockState[cfg.enter()].m_liveIn;
|
||||||
std::vector<AstVarScope*>* const resultp
|
std::vector<AstVarScope*>* const resultp
|
||||||
= new std::vector<AstVarScope*>{lin.begin(), lin.end()};
|
= 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};
|
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 operandConst(const AstNode* nodep) { return VN_IS(nodep, Const); }
|
||||||
bool operandAsvConst(const AstNode* nodep) {
|
bool operandAsvConst(const AstNode* nodep) {
|
||||||
// BIASV(CONST, BIASV(CONST,...)) -> BIASV( BIASV_CONSTED(a,b), ...)
|
// BIASV(CONST, BIASV(CONST,...)) -> BIASV( BIASV_CONSTED(a,b), ...)
|
||||||
|
|
@ -2393,6 +2483,25 @@ class ConstVisitor final : public VNVisitor {
|
||||||
VL_DO_DANGLING(pushDeletep(conp), conp);
|
VL_DO_DANGLING(pushDeletep(conp), conp);
|
||||||
// Further reduce, either node may have more reductions.
|
// Further reduce, either node may have more reductions.
|
||||||
return true;
|
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)
|
} else if (m_doV && VN_IS(nodep->rhsp(), StreamR)
|
||||||
&& !VN_IS(nodep->lhsp()->dtypep()->skipRefp(), QueueDType)) {
|
&& !VN_IS(nodep->lhsp()->dtypep()->skipRefp(), QueueDType)) {
|
||||||
// The right-streaming operator on rhs of assignment does not
|
// The right-streaming operator on rhs of assignment does not
|
||||||
|
|
@ -2402,7 +2511,9 @@ class ConstVisitor final : public VNVisitor {
|
||||||
AstNodeExpr* srcp = streamp->lhsp()->unlinkFrBack();
|
AstNodeExpr* srcp = streamp->lhsp()->unlinkFrBack();
|
||||||
AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp();
|
AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp();
|
||||||
const AstNodeDType* const dstDTypep = nodep->lhsp()->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)) {
|
if (VN_IS(dstDTypep, QueueDType) || VN_IS(dstDTypep, DynArrayDType)) {
|
||||||
int srcElementBits = 0;
|
int srcElementBits = 0;
|
||||||
if (const AstNodeDType* const elemDtp = srcDTypep->subDTypep()) {
|
if (const AstNodeDType* const elemDtp = srcDTypep->subDTypep()) {
|
||||||
|
|
@ -2429,6 +2540,19 @@ class ConstVisitor final : public VNVisitor {
|
||||||
srcp = new AstShiftL{srcp->fileline(), srcp,
|
srcp = new AstShiftL{srcp->fileline(), srcp,
|
||||||
new AstConst{srcp->fileline(), offset}, packedBits};
|
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);
|
nodep->rhsp(srcp);
|
||||||
VL_DO_DANGLING(pushDeletep(streamp), streamp);
|
VL_DO_DANGLING(pushDeletep(streamp), streamp);
|
||||||
// Further reduce, any of the nodes may have more reductions.
|
// 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* streamp = nodep->lhsp()->unlinkFrBack();
|
||||||
AstNodeExpr* const dstp = VN_AS(streamp, StreamL)->lhsp()->unlinkFrBack();
|
AstNodeExpr* const dstp = VN_AS(streamp, StreamL)->lhsp()->unlinkFrBack();
|
||||||
AstNodeDType* const dstDTypep = dstp->dtypep()->skipRefp();
|
AstNodeDType* const dstDTypep = dstp->dtypep()->skipRefp();
|
||||||
AstNodeExpr* const srcp = nodep->rhsp()->unlinkFrBack();
|
AstNodeExpr* srcp = nodep->rhsp()->unlinkFrBack();
|
||||||
const AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp();
|
const AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp();
|
||||||
// Handle unpacked/queue/dynarray source -> queue/dynarray dest via
|
// Handle unpacked/queue/dynarray source -> queue/dynarray dest via
|
||||||
// CvtArrayToArray (StreamL reverses, so reverse=true)
|
// CvtArrayToArray (StreamL reverses, so reverse=true)
|
||||||
|
|
@ -2466,6 +2590,7 @@ class ConstVisitor final : public VNVisitor {
|
||||||
VL_DO_DANGLING(pushDeletep(streamp), streamp);
|
VL_DO_DANGLING(pushDeletep(streamp), streamp);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (lowerAsFixedAggregate(srcDTypep)) srcp = packFixedAggregate(srcp);
|
||||||
const int sWidth = srcp->width();
|
const int sWidth = srcp->width();
|
||||||
const int dWidth = dstp->width();
|
const int dWidth = dstp->width();
|
||||||
// Connect the rhs to the stream operator and update its width
|
// Connect the rhs to the stream operator and update its width
|
||||||
|
|
@ -2556,7 +2681,7 @@ class ConstVisitor final : public VNVisitor {
|
||||||
} else {
|
} else {
|
||||||
// Source narrower than destination: left-justify by shifting left.
|
// Source narrower than destination: left-justify by shifting left.
|
||||||
// The right stream operator packs left-to-right, so remaining
|
// 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)) {
|
if (!VN_IS(srcp->dtypep()->skipRefp(), QueueDType)) {
|
||||||
AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp};
|
AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp};
|
||||||
extendp->dtypeSetLogicSized(dWidth, VSigning::UNSIGNED);
|
extendp->dtypeSetLogicSized(dWidth, VSigning::UNSIGNED);
|
||||||
|
|
@ -2580,6 +2705,13 @@ class ConstVisitor final : public VNVisitor {
|
||||||
AstNodeExpr* srcp = streamp->lhsp();
|
AstNodeExpr* srcp = streamp->lhsp();
|
||||||
const AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp();
|
const AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp();
|
||||||
AstNodeDType* const dstDTypep = nodep->lhsp()->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)
|
if ((VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)
|
||||||
|| VN_IS(srcDTypep, UnpackArrayDType))) {
|
|| VN_IS(srcDTypep, UnpackArrayDType))) {
|
||||||
if (VN_IS(dstDTypep, QueueDType) || VN_IS(dstDTypep, DynArrayDType)) {
|
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_params; // Parameters in function/task
|
||||||
V3ControlVarResolver m_ports; // Ports in function/task
|
V3ControlVarResolver m_ports; // Ports in function/task
|
||||||
V3ControlVarResolver m_vars; // Variables 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_noinline = false; // Don't inline function/task
|
||||||
bool m_public = false; // Public function/task
|
bool m_public = false; // Public function/task
|
||||||
|
|
||||||
|
|
@ -190,7 +189,6 @@ public:
|
||||||
V3ControlFTask() = default;
|
V3ControlFTask() = default;
|
||||||
void update(const V3ControlFTask& f) {
|
void update(const V3ControlFTask& f) {
|
||||||
// Don't overwrite true with false
|
// Don't overwrite true with false
|
||||||
if (f.m_isolate) m_isolate = true;
|
|
||||||
if (f.m_noinline) m_noinline = true;
|
if (f.m_noinline) m_noinline = true;
|
||||||
if (f.m_public) m_public = true;
|
if (f.m_public) m_public = true;
|
||||||
m_params.update(f.m_params);
|
m_params.update(f.m_params);
|
||||||
|
|
@ -203,7 +201,6 @@ public:
|
||||||
V3ControlVarResolver& ports() { return m_ports; }
|
V3ControlVarResolver& ports() { return m_ports; }
|
||||||
V3ControlVarResolver& vars() { return m_vars; }
|
V3ControlVarResolver& vars() { return m_vars; }
|
||||||
|
|
||||||
void setIsolate(bool set) { m_isolate = set; }
|
|
||||||
void setNoInline(bool set) { m_noinline = set; }
|
void setNoInline(bool set) { m_noinline = set; }
|
||||||
void setPublic(bool set) { m_public = set; }
|
void setPublic(bool set) { m_public = set; }
|
||||||
|
|
||||||
|
|
@ -212,8 +209,6 @@ public:
|
||||||
ftaskp->addStmtsp(new AstPragma{ftaskp->fileline(), VPragmaType::NO_INLINE_TASK});
|
ftaskp->addStmtsp(new AstPragma{ftaskp->fileline(), VPragmaType::NO_INLINE_TASK});
|
||||||
if (m_public)
|
if (m_public)
|
||||||
ftaskp->addStmtsp(new AstPragma{ftaskp->fileline(), VPragmaType::PUBLIC_TASK});
|
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
|
// Semantics: Most of the attributes operate on signals
|
||||||
if (pattern.empty()) {
|
if (pattern.empty()) {
|
||||||
if (attr == VAttrType::VAR_ISOLATE_ASSIGNMENTS) {
|
if (attr == VAttrType::VAR_PUBLIC) {
|
||||||
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 (ftask.empty()) {
|
if (ftask.empty()) {
|
||||||
// public module, this is the only exception from var here
|
// public module, this is the only exception from var here
|
||||||
V3ControlResolver::s().modules().at(module).addModulePragma(
|
V3ControlResolver::s().modules().at(module).addModulePragma(
|
||||||
|
|
|
||||||
|
|
@ -166,10 +166,13 @@ class CoverageVisitor final : public VNVisitor {
|
||||||
|
|
||||||
// METHODS
|
// METHODS
|
||||||
|
|
||||||
|
// Return non-nullptr reason if this variable shouldn't have toggle coverage
|
||||||
const char* varIgnoreToggle(const AstVar* nodep) {
|
const char* varIgnoreToggle(const AstVar* nodep) {
|
||||||
// Return true if this shouldn't be traced
|
const bool cover = nodep->isIO() || (nodep->isSignal() && nodep->isBitLogic());
|
||||||
// See also similar rule in V3TraceDecl::varIgnoreTrace
|
if (!cover) return "Not relevant signal";
|
||||||
if (!nodep->isToggleCoverable()) return "Not relevant signal type";
|
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()) {
|
if (!v3Global.opt.coverageUnderscore()) {
|
||||||
const string prettyName = nodep->prettyName();
|
const string prettyName = nodep->prettyName();
|
||||||
if (prettyName[0] == '_') return "Leading underscore";
|
if (prettyName[0] == '_') return "Leading underscore";
|
||||||
|
|
@ -530,8 +533,10 @@ class CoverageVisitor final : public VNVisitor {
|
||||||
newent.cleanup();
|
newent.cleanup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (VN_IS(dtypep, QueueDType)) {
|
} else if (VN_IS(dtypep, QueueDType) || VN_IS(dtypep, AssocArrayDType)
|
||||||
|
|| VN_IS(dtypep, WildcardArrayDType)) {
|
||||||
// Not covered
|
// Not covered
|
||||||
|
varp->v3warn(COVERIGN, "Coverage ignored for type " << dtypep->prettyTypeName());
|
||||||
} else {
|
} else {
|
||||||
dtypep->v3fatalSrc("Unexpected node data type in toggle coverage generation: "
|
dtypep->v3fatalSrc("Unexpected node data type in toggle coverage generation: "
|
||||||
<< dtypep->prettyTypeName());
|
<< dtypep->prettyTypeName());
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,9 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
, crossBins{cb} {}
|
, crossBins{cb} {}
|
||||||
};
|
};
|
||||||
std::vector<BinInfo> m_binInfos; // All bins in current covergroup
|
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
|
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)
|
// Clear bin info for this covergroup (deleting any orphaned cross pseudo-bins)
|
||||||
clearBinInfos();
|
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 each coverpoint, generate sampling code
|
||||||
for (AstCoverpoint* cpp : m_coverpoints) generateCoverpointCode(cpp);
|
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
|
// Create implicit automatic bins if no regular bins exist
|
||||||
createImplicitAutoBins(coverpointp, exprp, autoBinMax);
|
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
|
// Generate member variables and matching code for each bin
|
||||||
// Process in two passes: first non-default bins, then default bins
|
// Process in two passes: first non-default bins, then default bins
|
||||||
std::vector<AstCoverBin*> defaultBins;
|
std::vector<AstCoverBin*> defaultBins;
|
||||||
|
|
@ -595,47 +616,179 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
UINFO(4, " Successfully added if statement for bin: " << binp->name());
|
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
|
// Generate matching code for default bins
|
||||||
// Default bins match when value doesn't match any other explicit bin
|
// Default bins match when value doesn't match any other explicit bin
|
||||||
void generateDefaultBinMatchCode(AstCoverpoint* coverpointp, AstCoverBin* defBinp,
|
void generateDefaultBinMatchCode(AstCoverpoint* coverpointp, AstCoverBin* defBinp,
|
||||||
AstNodeExpr* exprp, AstVar* hitVarp) {
|
AstNodeExpr* exprp, AstVar* hitVarp) {
|
||||||
UINFO(4, " Generating default bin match for: " << defBinp->name());
|
UINFO(4, " Generating default bin match for: " << defBinp->name());
|
||||||
|
|
||||||
// Build OR of all non-default, non-ignore bins
|
AstNodeExpr* defaultCondp = buildDefaultCondition(coverpointp, exprp, defBinp->fileline());
|
||||||
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{}};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply iff condition if present
|
// Apply iff condition if present
|
||||||
if (AstNodeExpr* iffp = coverpointp->iffp()) {
|
if (AstNodeExpr* iffp = coverpointp->iffp()) {
|
||||||
|
|
@ -1320,15 +1473,50 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
|
|
||||||
void generateCoverageMethodBody(AstFunc* funcp) {
|
void generateCoverageMethodBody(AstFunc* funcp) {
|
||||||
FileLine* const fl = funcp->fileline();
|
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;
|
int totalBins = 0;
|
||||||
for (const BinInfo& bi : m_binInfos) {
|
for (const BinInfo& bi : m_binInfos) {
|
||||||
UINFO(6, " Bin: " << bi.binp->name() << " type=" << bi.binp->binsType().ascii());
|
UINFO(6, " Bin: " << bi.binp->name() << " type=" << bi.binp->binsType().ascii());
|
||||||
if (bi.binp->binsType() != VCoverBinsType::BINS_IGNORE
|
if (bi.binp->binsType().binIsNormal()) totalBins++;
|
||||||
&& bi.binp->binsType() != VCoverBinsType::BINS_ILLEGAL) {
|
|
||||||
totalBins++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UINFO(4, " Total regular bins: " << totalBins << " of " << m_binInfos.size());
|
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%.
|
// No coverage to compute - return 100%.
|
||||||
// Any parser-generated initialization of returnVar is overridden by our assignment.
|
// Any parser-generated initialization of returnVar is overridden by our assignment.
|
||||||
UINFO(4, " Empty covergroup, returning 100.0");
|
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},
|
funcp->addStmtsp(new AstAssign{fl, new AstVarRef{fl, returnVarp, VAccess::WRITE},
|
||||||
new AstConst{fl, AstConst::RealDouble{}, 100.0}});
|
new AstConst{fl, AstConst::RealDouble{}, 100.0}});
|
||||||
UINFO(4, " Added assignment to return 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 each regular bin, if count > 0, increment covered_count
|
||||||
for (const BinInfo& bi : m_binInfos) {
|
for (const BinInfo& bi : m_binInfos) {
|
||||||
// Skip ignore_bins and illegal_bins in coverage calculation
|
// Skip ignore/illegal/default bins in coverage calculation
|
||||||
if (bi.binp->binsType() == VCoverBinsType::BINS_IGNORE
|
if (!bi.binp->binsType().binIsNormal()) continue;
|
||||||
|| bi.binp->binsType() == VCoverBinsType::BINS_ILLEGAL) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (bin_count >= at_least) covered_count++;
|
// if (bin_count >= at_least) covered_count++;
|
||||||
AstIf* ifp = new AstIf{
|
AstIf* ifp = new AstIf{
|
||||||
|
|
@ -1375,9 +1559,6 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
funcp->addStmtsp(ifp);
|
funcp->addStmtsp(ifp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the return variable
|
|
||||||
AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var);
|
|
||||||
|
|
||||||
// Calculate coverage: (covered_count / total_bins) * 100.0
|
// Calculate coverage: (covered_count / total_bins) * 100.0
|
||||||
// return_var = (double)covered_count / (double)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
|
// Note: the recursive invocation can cause a re-hash but that will not invalidate references
|
||||||
AstScope*& resultr = cache[this];
|
AstScope*& resultr = cache[this];
|
||||||
if (!resultr) {
|
if (!resultr) {
|
||||||
// Mark to prevent infinite recursion on circular graphs - should never be called on such
|
// Mark to prevent infinite recursion on circular graphs - should never be called on such
|
||||||
resultr = reinterpret_cast<AstScope*>(1);
|
resultr = reinterpret_cast<AstScope*>(1);
|
||||||
// Find scope based on sources, falling back on the root scope
|
// Find scope based on sources, falling back on the root scope,
|
||||||
AstScope* const rootp = v3Global.rootp()->topScopep()->scopep();
|
// also make sure it's not the constant pool scope, which is special.
|
||||||
AstScope* foundp = nullptr;
|
AstScope* foundp = nullptr;
|
||||||
foreachSource([&](DfgVertex& src) {
|
foreachSource([&](DfgVertex& src) {
|
||||||
AstScope* const scp = src.scopep(cache, true);
|
AstScope* const scp = src.scopep(cache, true);
|
||||||
if (scp != rootp) {
|
if (scp != rootp && scp != constPoolp) {
|
||||||
foundp = scp;
|
foundp = scp;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -234,6 +234,26 @@ private:
|
||||||
addStat("temporaries introduced", m_temporariesIntroduced);
|
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 {
|
class V3DfgRemoveUnobservableContext final : public V3DfgSubContext {
|
||||||
// Only V3DfgContext can create an instance
|
// Only V3DfgContext can create an instance
|
||||||
friend class V3DfgContext;
|
friend class V3DfgContext;
|
||||||
|
|
@ -399,6 +419,7 @@ public:
|
||||||
V3DfgPeepholeContext m_peepholeContext;
|
V3DfgPeepholeContext m_peepholeContext;
|
||||||
V3DfgPushDownSelsContext m_pushDownSelsContext;
|
V3DfgPushDownSelsContext m_pushDownSelsContext;
|
||||||
V3DfgRegularizeContext m_regularizeContext;
|
V3DfgRegularizeContext m_regularizeContext;
|
||||||
|
V3DfgRemoveSelectsContext m_removeSelectsContext;
|
||||||
V3DfgRemoveUnobservableContext m_removeUnobservableContext;
|
V3DfgRemoveUnobservableContext m_removeUnobservableContext;
|
||||||
V3DfgSynthesisContext m_synthContext;
|
V3DfgSynthesisContext m_synthContext;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,7 @@ public:
|
||||||
|
|
||||||
// Returns a Packed type of the given width
|
// Returns a Packed type of the given width
|
||||||
static const DfgDataType& packed(uint32_t width) {
|
static const DfgDataType& packed(uint32_t width) {
|
||||||
|
UASSERT(width > 0, "Width must be positive");
|
||||||
// Find or create the right sized packed type
|
// Find or create the right sized packed type
|
||||||
const DfgDataType*& entryr = s_packedTypes[width];
|
const DfgDataType*& entryr = s_packedTypes[width];
|
||||||
if (!entryr) entryr = new DfgDataType{width};
|
if (!entryr) entryr = new DfgDataType{width};
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,13 @@ class DataflowOptimize final {
|
||||||
dfg.mergeGraphs(std::move(madeAcyclicComponents));
|
dfg.mergeGraphs(std::move(madeAcyclicComponents));
|
||||||
endOfStage("breakCycles", dfg, cyclicComps);
|
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
|
// Split the acyclic DFG into [weakly] connected components
|
||||||
std::vector<std::unique_ptr<DfgGraph>> acyclicComps = dfg.splitIntoComponents("acyclic");
|
std::vector<std::unique_ptr<DfgGraph>> acyclicComps = dfg.splitIntoComponents("acyclic");
|
||||||
UASSERT(dfg.size() == 0, "DfgGraph should have become empty");
|
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) {
|
void V3DfgPasses::inlineVars(DfgGraph& dfg) {
|
||||||
for (DfgVertexVar& vtx : dfg.varVertices()) {
|
for (DfgVertexVar& vtx : dfg.varVertices()) {
|
||||||
// Nothing to inline it into
|
// Nothing to inline it into
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@ void removeUnobservable(DfgGraph&, V3DfgContext&) VL_MT_DISABLED;
|
||||||
// Synthesize DfgLogic vertices into primitive operations.
|
// Synthesize DfgLogic vertices into primitive operations.
|
||||||
// Removes all DfgLogic (even those that were not synthesized).
|
// Removes all DfgLogic (even those that were not synthesized).
|
||||||
void synthesize(DfgGraph&, V3DfgContext&) VL_MT_DISABLED;
|
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"
|
// Attempt to make the given cyclic graph into an acyclic, or "less cyclic"
|
||||||
// equivalent. If the returned pointer is null, then no improvement was
|
// equivalent. If the returned pointer is null, then no improvement was
|
||||||
// possible on the input graph. Otherwise the returned graph is an improvement
|
// possible on the input graph. Otherwise the returned graph is an improvement
|
||||||
|
|
|
||||||
|
|
@ -1188,6 +1188,102 @@ class V3DfgPeephole final : public DfgVisitor {
|
||||||
return {fromp, lsb};
|
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
|
// VISIT methods
|
||||||
|
|
||||||
void visit(DfgVertex*) override {}
|
void visit(DfgVertex*) override {}
|
||||||
|
|
@ -1789,10 +1885,24 @@ class V3DfgPeephole final : public DfgVisitor {
|
||||||
if (!idxp) return;
|
if (!idxp) return;
|
||||||
DfgVarArray* const varp = vtxp->fromp()->cast<DfgVarArray>();
|
DfgVarArray* const varp = vtxp->fromp()->cast<DfgVarArray>();
|
||||||
if (!varp) return;
|
if (!varp) return;
|
||||||
if (varp->vscp()->varp()->isForced()) return;
|
AstVar* const astVarp = varp->vscp()->varp();
|
||||||
if (varp->vscp()->varp()->isSigUserRWPublic()) return;
|
if (astVarp->isForced()) return;
|
||||||
|
if (astVarp->isSigUserRWPublic()) return;
|
||||||
DfgVertex* const srcp = varp->srcp();
|
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>()) {
|
if (DfgSpliceArray* const splicep = srcp->cast<DfgSpliceArray>()) {
|
||||||
DfgVertex* const driverp = splicep->driverAt(idxp->toSizeT());
|
DfgVertex* const driverp = splicep->driverAt(idxp->toSizeT());
|
||||||
|
|
@ -2834,56 +2944,62 @@ class V3DfgPeephole final : public DfgVisitor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DfgConcat* const tConcatp = thenp->cast<DfgConcat>()) {
|
if (!thenp->is<DfgConst>() && !elsep->is<DfgConst>()) {
|
||||||
if (DfgConcat* const eConcatp = elsep->cast<DfgConcat>()) {
|
const std::pair<DfgVertex*, uint32_t> cLSBs = commonLSBs(thenp, elsep);
|
||||||
DfgVertex* const tRhsp = tConcatp->rhsp();
|
if (cLSBs.first) {
|
||||||
DfgVertex* const tLhsp = tConcatp->lhsp();
|
APPLYING(REPLACE_COND_COMMON_LSBS) {
|
||||||
DfgVertex* const eRhsp = eConcatp->rhsp();
|
// Create new RHS
|
||||||
DfgVertex* const eLhsp = eConcatp->lhsp();
|
const uint32_t rWidth = cLSBs.second;
|
||||||
|
DfgVertex* rhsp = cLSBs.first;
|
||||||
if (isSame(tRhsp, eRhsp)) {
|
if (rWidth != rhsp->width()) {
|
||||||
APPLYING(REPLACE_COND_SAME_CAT_RHS) {
|
const DfgDataType& rDtype = DfgDataType::packed(rWidth);
|
||||||
DfgCond* const newCondp
|
rhsp = make<DfgSel>(flp, rDtype, rhsp, 0U);
|
||||||
= make<DfgCond>(flp, tLhsp->dtype(), condp, tLhsp, eLhsp);
|
}
|
||||||
replace(make<DfgConcat>(vtxp, newCondp, tRhsp));
|
// If it's all the same, just replace
|
||||||
return;
|
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);
|
||||||
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));
|
|
||||||
return;
|
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()) {
|
const std::pair<DfgVertex*, uint32_t> cMSBs = commonMSBs(thenp, elsep);
|
||||||
if (DfgConcat* const tRCatp = tConcatp->rhsp()->cast<DfgConcat>()) {
|
if (cMSBs.first) {
|
||||||
if (!tRCatp->hasMultipleSinks()) {
|
APPLYING(REPLACE_COND_COMMON_MSBS) {
|
||||||
if (DfgSel* const tLSelp = tConcatp->lhsp()->cast<DfgSel>()) {
|
// Create new LHS
|
||||||
if (DfgSel* const tRRSelp = tRCatp->rhsp()->cast<DfgSel>()) {
|
const uint32_t lWidth = cMSBs.second;
|
||||||
if (tLSelp->lsb() == tRCatp->width() //
|
DfgVertex* lhsp = cMSBs.first;
|
||||||
&& tRRSelp->lsb() == 0 //
|
if (lWidth != lhsp->width()) {
|
||||||
&& isSame(tLSelp->fromp(), elsep) //
|
const DfgDataType& lDtype = DfgDataType::packed(lWidth);
|
||||||
&& isSame(tRRSelp->fromp(), elsep)) {
|
lhsp = make<DfgSel>(flp, lDtype, lhsp, lhsp->width() - lWidth);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// 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).
|
// Enumeration of each peephole optimization. Must be kept in sorted order (enforced by tests).
|
||||||
// clang-format off
|
// clang-format off
|
||||||
#define FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(macro) \
|
#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_LHS_OF_RHS) \
|
||||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY_RHS_OF_LHS) \
|
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY_RHS_OF_LHS) \
|
||||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_BINARY) \
|
_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_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_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_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_ONES_ZERO) \
|
||||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ONE_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_ONE) \
|
||||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ZERO_ONES) \
|
_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_DEC) \
|
||||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_INC) \
|
_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_LHS) \
|
||||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_OR_THEN_COND_RHS) \
|
_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_ELSE) \
|
||||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_COND_THEN) \
|
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_COND_THEN) \
|
||||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_THEN_OR_LHS) \
|
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_THEN_OR_LHS) \
|
||||||
|
|
|
||||||
|
|
@ -299,6 +299,7 @@ public:
|
||||||
void fromp(DfgVertex* vtxp) { srcp(vtxp); }
|
void fromp(DfgVertex* vtxp) { srcp(vtxp); }
|
||||||
uint32_t lsb() const { return m_lsb; }
|
uint32_t lsb() const { return m_lsb; }
|
||||||
void lsb(uint32_t value) { m_lsb = value; }
|
void lsb(uint32_t value) { m_lsb = value; }
|
||||||
|
uint32_t msb() const { return m_lsb + width() - 1; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class DfgUnitArray final : public DfgVertexUnary {
|
class DfgUnitArray final : public DfgVertexUnary {
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
#include "V3EmitCFunc.h"
|
#include "V3EmitCFunc.h"
|
||||||
|
|
||||||
#include "V3TSP.h"
|
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -573,8 +573,14 @@ public:
|
||||||
+ "\", " + std::to_string(nodep->fileline()->lineno()) + ")->"),
|
+ "\", " + std::to_string(nodep->fileline()->lineno()) + ")->"),
|
||||||
memberVarp, resetp->constructing());
|
memberVarp, resetp->constructing());
|
||||||
} else {
|
} else {
|
||||||
AstVar* const varp = VN_AS(fromp, NodeVarRef)->varp();
|
AstNodeVarRef* const fromVarRefp = VN_AS(fromp, NodeVarRef);
|
||||||
emitVarReset("", varp, resetp->constructing());
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1128,7 +1134,7 @@ public:
|
||||||
}
|
}
|
||||||
void visit(AstFFlush* nodep) override {
|
void visit(AstFFlush* nodep) override {
|
||||||
if (!nodep->filep()) {
|
if (!nodep->filep()) {
|
||||||
putns(nodep, "Verilated::runFlushCallbacks();\n");
|
putns(nodep, "VL_FFLUSH_MT();");
|
||||||
} else {
|
} else {
|
||||||
putns(nodep, "VL_FFLUSH_I(");
|
putns(nodep, "VL_FFLUSH_I(");
|
||||||
iterateAndNextConstNull(nodep->filep());
|
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.mtasks()) puts("#include \"verilated_threads.h\"\n");
|
||||||
if (v3Global.opt.savable()) puts("#include \"verilated_save.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_cov.h\"\n");
|
||||||
|
if (v3Global.opt.coverage()) puts("#include \"verilated_covergroup.h\"\n");
|
||||||
if (v3Global.usesTiming()) puts("#include \"verilated_timing.h\"\n");
|
if (v3Global.usesTiming()) puts("#include \"verilated_timing.h\"\n");
|
||||||
if (v3Global.useRandomizeMethods()) puts("#include \"verilated_random.h\"\n");
|
if (v3Global.useRandomizeMethods()) puts("#include \"verilated_random.h\"\n");
|
||||||
if (v3Global.usesForce()) puts("#include \"verilated_force.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.mtasks()) puts("#include \"verilated_threads.h\"\n");
|
||||||
if (v3Global.opt.savable()) puts("#include \"verilated_save.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_cov.h\"\n");
|
||||||
|
if (v3Global.opt.coverage()) puts("#include \"verilated_covergroup.h\"\n");
|
||||||
if (v3Global.dpi()) puts("#include \"svdpi.h\"\n");
|
if (v3Global.dpi()) puts("#include \"svdpi.h\"\n");
|
||||||
|
|
||||||
// Declare foreign instances up front to make C++ happy
|
// Declare foreign instances up front to make C++ happy
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
#include "V3Ast.h"
|
#include "V3Ast.h"
|
||||||
#include "V3Control.h"
|
#include "V3Control.h"
|
||||||
#include "V3Graph.h"
|
#include "V3Graph.h"
|
||||||
|
#include "V3UniqueNames.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
|
@ -987,14 +988,23 @@ class FsmDetectVisitor final : public VNVisitor {
|
||||||
return assp;
|
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,
|
static AstNodeAssign* nodeStateVarAssign(AstNode* nodep, AstVarScope*& stateVscp,
|
||||||
AstVarScope*& fromVscp) {
|
AstVarScope*& fromVscp) {
|
||||||
AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign);
|
AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign);
|
||||||
if (!assp) return nullptr;
|
if (!assp) return nullptr;
|
||||||
AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef);
|
AstVarRef* const lhsp = tryExtractVarRef(assp->lhsp());
|
||||||
UASSERT_OBJ(lhsp, assp, "register commit lhs should be normalized to a VarRef");
|
|
||||||
AstVarRef* const rhsp = VN_CAST(assp->rhsp(), VarRef);
|
AstVarRef* const rhsp = VN_CAST(assp->rhsp(), VarRef);
|
||||||
if (!rhsp) return nullptr;
|
if (!rhsp || !lhsp) return nullptr;
|
||||||
stateVscp = lhsp->varScopep();
|
stateVscp = lhsp->varScopep();
|
||||||
fromVscp = rhsp->varScopep();
|
fromVscp = rhsp->varScopep();
|
||||||
return assp;
|
return assp;
|
||||||
|
|
@ -1006,11 +1016,9 @@ class FsmDetectVisitor final : public VNVisitor {
|
||||||
FsmStateValue& resetValue) {
|
FsmStateValue& resetValue) {
|
||||||
AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign);
|
AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign);
|
||||||
if (!assp) return nullptr;
|
if (!assp) return nullptr;
|
||||||
AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef);
|
AstVarRef* const lhsp = tryExtractVarRef(assp->lhsp());
|
||||||
UASSERT_OBJ(lhsp, assp,
|
|
||||||
"conditional register commit lhs should be normalized to a VarRef");
|
|
||||||
AstCond* const rhsp = VN_CAST(assp->rhsp(), Cond);
|
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 (AstVarRef* const elsep = VN_CAST(rhsp->elsep(), VarRef)) {
|
||||||
if (constValueStatus(rhsp->thenp(), resetValue) != ConstValueStatus::OK)
|
if (constValueStatus(rhsp->thenp(), resetValue) != ConstValueStatus::OK)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
@ -1033,7 +1041,7 @@ class FsmDetectVisitor final : public VNVisitor {
|
||||||
FsmStateValue& value) {
|
FsmStateValue& value) {
|
||||||
AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign);
|
AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign);
|
||||||
if (!assp) return nullptr;
|
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,
|
UASSERT_OBJ(lhsp, assp,
|
||||||
"direct constant state assignment lhs should be normalized to a VarRef");
|
"direct constant state assignment lhs should be normalized to a VarRef");
|
||||||
if (constValueStatus(assp->rhsp(), value) != ConstValueStatus::OK) return nullptr;
|
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);
|
AstVarRef* vrefp = VN_CAST(eqp->lhsp(), VarRef);
|
||||||
AstNodeExpr* valuep = eqp->rhsp();
|
AstNodeExpr* valuep = eqp->rhsp();
|
||||||
if (!vrefp) {
|
if (!vrefp) {
|
||||||
vrefp = VN_AS(eqp->rhsp(), VarRef);
|
vrefp = tryExtractVarRef(eqp->rhsp());
|
||||||
|
if (!vrefp) { return false; }
|
||||||
valuep = eqp->lhsp();
|
valuep = eqp->lhsp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2082,6 +2091,7 @@ public:
|
||||||
class FsmLowerVisitor final {
|
class FsmLowerVisitor final {
|
||||||
// STATE - across all visitors
|
// STATE - across all visitors
|
||||||
const FsmState& m_state;
|
const FsmState& m_state;
|
||||||
|
V3UniqueNames m_fsmBuildNames;
|
||||||
|
|
||||||
// METHODS
|
// METHODS
|
||||||
// Rebuild a state-typed constant using the tracked state variable
|
// Rebuild a state-typed constant using the tracked state variable
|
||||||
|
|
@ -2133,8 +2143,8 @@ class FsmLowerVisitor final {
|
||||||
AstNodeModule* const modp = scopep->modp();
|
AstNodeModule* const modp = scopep->modp();
|
||||||
AstNodeDType* const prevDTypep = scopep->findLogicDType(
|
AstNodeDType* const prevDTypep = scopep->findLogicDType(
|
||||||
sampleVscp->width(), sampleVscp->width(), sampleVscp->dtypep()->numeric());
|
sampleVscp->width(), sampleVscp->width(), sampleVscp->dtypep()->numeric());
|
||||||
AstVarScope* const prevVscp
|
const std::string tmpName = m_fsmBuildNames.get(stateVscp->varp()->shortName());
|
||||||
= scopep->createTemp("__Vfsmcov_prev__" + stateVscp->varp()->shortName(), prevDTypep);
|
AstVarScope* const prevVscp = scopep->createTemp(tmpName, prevDTypep);
|
||||||
// The saved previous-state temp crosses the scheduler's pre/post split
|
// 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
|
// 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.
|
// 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
|
// concrete coverage instrumentation while the saved scoped pointers are
|
||||||
// still valid in the same pass.
|
// still valid in the same pass.
|
||||||
explicit FsmLowerVisitor(const FsmState& state)
|
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); }
|
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
|
// Logic block with pending substitutions are stored in this map, together with their ordinal
|
||||||
std::unordered_map<AstNode*, size_t> m_hasPending;
|
std::unordered_map<AstNode*, size_t> m_hasPending;
|
||||||
size_t m_statInlined = 0; // Statistic tracking - signals inlined
|
size_t m_statInlined = 0; // Statistic tracking - signals inlined
|
||||||
size_t m_statRefs = 0; // Statistic tracking
|
size_t m_statNotInlined = 0; // Statistic tracking - signals not inlined due to cost
|
||||||
size_t m_statExcluded = 0; // Statistic tracking
|
size_t m_statRefs = 0; // Statistic tracking - number of input variable references replaced
|
||||||
|
|
||||||
// METHODS
|
// 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 (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;
|
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();
|
// AstArraySel is cheap if the fromp is cheap
|
||||||
return VN_IS(exprp, Const) || VN_IS(exprp, NodeVarRef);
|
if (const AstArraySel* const aselp = VN_CAST(exprp, ArraySel)) {
|
||||||
}
|
return isCheap(aselp->fromp());
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// Otherwise it is not cheap
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool shouldInline(GateVarVertex* vVtxp, GateLogicVertex* lVtxp, size_t nReads,
|
bool shouldInline(GateVarVertex* vVtxp, GateLogicVertex* lVtxp, size_t nReads,
|
||||||
AstNodeExpr* substp, bool allowMultiIn) {
|
AstNodeExpr* substp, bool allowMultiIn) {
|
||||||
AstVarScope* const vscp = vVtxp->varScp();
|
|
||||||
|
|
||||||
// Always inline constants
|
// Always inline constants
|
||||||
if (VN_IS(substp, Const)) return true;
|
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;
|
if (lVtxp->staticInit()) return false;
|
||||||
// Inline simple variable references
|
// Inline simple variable references
|
||||||
if (VN_IS(substp, VarRef)) return true;
|
if (VN_IS(substp, VarRef)) return true;
|
||||||
// Only inline arrays if a simple variable or constant
|
// Only inline arrays if a simple variable or constant
|
||||||
if (VN_IS(vscp->dtypep()->skipRefp(), UnpackArrayDType)) return false;
|
if (VN_IS(vVtxp->varScp()->dtypep()->skipRefp(), UnpackArrayDType)) return false;
|
||||||
// Inline constant array selects
|
// Inline if reads no variables - unfolded constant expression, nullary builtin e.g.: $time
|
||||||
if (VN_IS(substp, ArraySel) && nReads <= 1) return true;
|
if (nReads == 0) return true;
|
||||||
|
// If it reads one variable, inline if not wide, or if cheap
|
||||||
// Don't inline expensive wide operations
|
if (nReads == 1 && (!substp->isWide() || isCheap(substp))) return true;
|
||||||
if (excludedWide(vVtxp, substp)) {
|
// Don't inline on first round if reads more than one variable
|
||||||
++m_statExcluded;
|
if (nReads > 1 && !allowMultiIn) return false;
|
||||||
UINFO(9, "Gate inline exclude '" << vVtxp->name() << "'");
|
// Reads multiple variables, or is expensive to compute.
|
||||||
vVtxp->clearReducible("Excluded wide"); // Check once.
|
// Inline if used only once, ignoring slow code, or dead code that can be deleted.
|
||||||
return false;
|
int n = 0;
|
||||||
}
|
for (V3GraphEdge& edge : vVtxp->outEdges()) {
|
||||||
|
const GateLogicVertex* const dstVtxp = edge.top()->as<GateLogicVertex>();
|
||||||
if (nReads == 0) {
|
// Ignore slow code, or if the destination is not used
|
||||||
// Reads no variables, likely unfolded constant expression
|
if (dstVtxp->slow()) continue;
|
||||||
return true;
|
if (dstVtxp->outEmpty() && !dstVtxp->consumed()) continue;
|
||||||
} else if (nReads == 1) {
|
n += edge.weight();
|
||||||
// Reads one variable
|
if (n > 1) {
|
||||||
return true;
|
++m_statNotInlined;
|
||||||
} else {
|
return false;
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void recordSubstitution(AstVarScope* vscp, AstNodeExpr* substp, AstNode* logicp) {
|
void recordSubstitution(AstVarScope* vscp, AstNodeExpr* substp, AstNode* logicp) {
|
||||||
|
|
@ -724,7 +697,7 @@ class GateInline final {
|
||||||
if (!okVisitor.varAssigned(vVtxp->varScp())) continue;
|
if (!okVisitor.varAssigned(vVtxp->varScp())) continue;
|
||||||
|
|
||||||
// Expression we are considering to substitute with
|
// 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
|
// Number of variables read by the substitution
|
||||||
const size_t nReads = okVisitor.readVscps().size();
|
const size_t nReads = okVisitor.readVscps().size();
|
||||||
|
|
||||||
|
|
@ -832,9 +805,9 @@ class GateInline final {
|
||||||
}
|
}
|
||||||
|
|
||||||
~GateInline() {
|
~GateInline() {
|
||||||
V3Stats::addStat("Optimizations, Gate sigs deleted", m_statInlined);
|
V3Stats::addStat("Optimizations, Gate signals inlined", m_statInlined);
|
||||||
V3Stats::addStat("Optimizations, Gate inputs replaced", m_statRefs);
|
V3Stats::addStat("Optimizations, Gate signals not inlined due to cost", m_statNotInlined);
|
||||||
V3Stats::addStat("Optimizations, Gate excluded wide expressions", m_statExcluded);
|
V3Stats::addStat("Optimizations, Gate reads replaced", m_statRefs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
|
|
@ -245,6 +245,7 @@ std::vector<std::string> V3Global::verilatedCppFiles() {
|
||||||
if (v3Global.opt.vpi()) result.emplace_back("verilated_vpi.cpp");
|
if (v3Global.opt.vpi()) result.emplace_back("verilated_vpi.cpp");
|
||||||
if (v3Global.opt.savable()) result.emplace_back("verilated_save.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_cov.cpp");
|
||||||
|
if (v3Global.opt.coverage()) result.emplace_back("verilated_covergroup.cpp");
|
||||||
for (const string& base : v3Global.opt.traceSourceBases())
|
for (const string& base : v3Global.opt.traceSourceBases())
|
||||||
result.emplace_back(base + "_c.cpp");
|
result.emplace_back(base + "_c.cpp");
|
||||||
if (v3Global.usesProbDist()) result.emplace_back("verilated_probdist.cpp");
|
if (v3Global.usesProbDist()) result.emplace_back("verilated_probdist.cpp");
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ public:
|
||||||
|
|
||||||
class LifeVarEntry final {
|
class LifeVarEntry final {
|
||||||
// Last assignment to this varscope, nullptr if no longer relevant
|
// 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
|
AstConst* m_constp = nullptr; // Known constant value
|
||||||
bool m_isNew = true; // Is just created
|
bool m_isNew = true; // Is just created
|
||||||
// First access was a set (and thus block above may have a set that can be deleted
|
// 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_isNew = false;
|
||||||
m_setBeforeUse = setBeforeUse;
|
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
|
void simpleAssign(AstNodeStmt* nodep) { // New simple A=.... assignment
|
||||||
UASSERT_OBJ(!m_isNew, nodep, "Uninitialized new entry");
|
UASSERT_OBJ(!m_isNew, nodep, "Uninitialized new entry");
|
||||||
|
|
@ -84,7 +95,10 @@ public:
|
||||||
m_constp = nullptr;
|
m_constp = nullptr;
|
||||||
m_everSet = true;
|
m_everSet = true;
|
||||||
if (AstNodeAssign* const assp = VN_CAST(nodep, Assign)) {
|
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
|
void complexAssign() { // A[x]=... or some complicated assignment
|
||||||
|
|
@ -179,6 +193,7 @@ public:
|
||||||
// Aha, variable is constant; substitute in.
|
// Aha, variable is constant; substitute in.
|
||||||
// We'll later constant propagate
|
// We'll later constant propagate
|
||||||
UINFO(4, " replaceconst: " << varrefp);
|
UINFO(4, " replaceconst: " << varrefp);
|
||||||
|
UINFO(9, " replaceval: " << constp);
|
||||||
varrefp->replaceWith(constp->cloneTree(false));
|
varrefp->replaceWith(constp->cloneTree(false));
|
||||||
m_replacedVref = true;
|
m_replacedVref = true;
|
||||||
VL_DO_DANGLING(varrefp->deleteTree(), varrefp);
|
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
|
LifeBlock* m_lifep = nullptr; // Current active lifetime map for current scope
|
||||||
|
|
||||||
// METHODS
|
// METHODS
|
||||||
void setNoopt() {
|
void setNoopt(const char* reasonp) {
|
||||||
|
(void)reasonp;
|
||||||
|
// UINFO(9, "setNoopt " << reasonp);
|
||||||
m_noopt = true;
|
m_noopt = true;
|
||||||
m_lifep->clear();
|
m_lifep->clear();
|
||||||
}
|
}
|
||||||
|
|
@ -310,7 +327,7 @@ class LifeVisitor final : public VNVisitor {
|
||||||
void visit(AstNodeAssign* nodep) override {
|
void visit(AstNodeAssign* nodep) override {
|
||||||
if (nodep->isTimingControl() || VN_IS(nodep, AssignForce)) {
|
if (nodep->isTimingControl() || VN_IS(nodep, AssignForce)) {
|
||||||
// V3Life doesn't understand time sense nor force assigns - don't optimize
|
// V3Life doesn't understand time sense nor force assigns - don't optimize
|
||||||
setNoopt();
|
setNoopt("timing|force");
|
||||||
if (nodep->isTimingControl()) m_containsTiming = true;
|
if (nodep->isTimingControl()) m_containsTiming = true;
|
||||||
iterateChildren(nodep);
|
iterateChildren(nodep);
|
||||||
return;
|
return;
|
||||||
|
|
@ -325,7 +342,7 @@ class LifeVisitor final : public VNVisitor {
|
||||||
// V3Life doesn't understand time sense
|
// V3Life doesn't understand time sense
|
||||||
if (nodep->isTimingControl()) {
|
if (nodep->isTimingControl()) {
|
||||||
// Don't optimize
|
// Don't optimize
|
||||||
setNoopt();
|
setNoopt("assigndly");
|
||||||
m_containsTiming = true;
|
m_containsTiming = true;
|
||||||
}
|
}
|
||||||
// Don't treat as normal assign
|
// Don't treat as normal assign
|
||||||
|
|
@ -337,19 +354,19 @@ class LifeVisitor final : public VNVisitor {
|
||||||
UINFO(4, " IF " << nodep);
|
UINFO(4, " IF " << nodep);
|
||||||
// Condition is part of PREVIOUS block
|
// Condition is part of PREVIOUS block
|
||||||
iterateAndNextNull(nodep->condp());
|
iterateAndNextNull(nodep->condp());
|
||||||
LifeBlock* const prevLifep = m_lifep;
|
LifeBlock* const ifLifep = new LifeBlock{m_lifep, m_statep};
|
||||||
LifeBlock* const ifLifep = new LifeBlock{prevLifep, m_statep};
|
LifeBlock* const elseLifep = new LifeBlock{m_lifep, m_statep};
|
||||||
LifeBlock* const elseLifep = new LifeBlock{prevLifep, m_statep};
|
|
||||||
{
|
{
|
||||||
|
VL_RESTORER(m_lifep);
|
||||||
m_lifep = ifLifep;
|
m_lifep = ifLifep;
|
||||||
iterateAndNextNull(nodep->thensp());
|
iterateAndNextNull(nodep->thensp());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
VL_RESTORER(m_lifep);
|
||||||
m_lifep = elseLifep;
|
m_lifep = elseLifep;
|
||||||
iterateAndNextNull(nodep->elsesp());
|
iterateAndNextNull(nodep->elsesp());
|
||||||
}
|
}
|
||||||
m_lifep = prevLifep;
|
UINFO(4, " join " << nodep);
|
||||||
UINFO(4, " join ");
|
|
||||||
// Find sets on both flows
|
// Find sets on both flows
|
||||||
m_lifep->dualBranch(ifLifep, elseLifep);
|
m_lifep->dualBranch(ifLifep, elseLifep);
|
||||||
// For the next assignments, clear any variables that were read or written in the block
|
// 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();
|
elseLifep->lifeToAbove();
|
||||||
VL_DO_DANGLING(delete ifLifep, ifLifep);
|
VL_DO_DANGLING(delete ifLifep, ifLifep);
|
||||||
VL_DO_DANGLING(delete elseLifep, elseLifep);
|
VL_DO_DANGLING(delete elseLifep, elseLifep);
|
||||||
|
UINFO(4, " if-done " << nodep);
|
||||||
}
|
}
|
||||||
void visit(AstLoop* nodep) override {
|
void visit(AstLoop* nodep) override {
|
||||||
// Similar problem to AstJumpBlock, don't optimize loop bodies - most are unrolled
|
// 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_noopt);
|
||||||
VL_RESTORER(m_lifep);
|
VL_RESTORER(m_lifep);
|
||||||
m_lifep = new LifeBlock{m_lifep, m_statep};
|
m_lifep = new LifeBlock{m_lifep, m_statep};
|
||||||
setNoopt();
|
setNoopt("loop");
|
||||||
iterateAndNextNull(nodep->stmtsp());
|
iterateAndNextNull(nodep->stmtsp());
|
||||||
UINFO(4, " joinloop");
|
UINFO(4, " joinloop");
|
||||||
// For the next assignments, clear any variables that were read or written in the block
|
// For the next assignments, clear any variables that were read or written in the block
|
||||||
m_lifep->lifeToAbove();
|
m_lifep->lifeToAbove();
|
||||||
VL_DO_DANGLING(delete m_lifep, m_lifep);
|
VL_DO_DANGLING(delete m_lifep, m_lifep);
|
||||||
}
|
}
|
||||||
if (m_containsTiming) setNoopt();
|
if (m_containsTiming) setNoopt("timing");
|
||||||
}
|
}
|
||||||
void visit(AstJumpBlock* nodep) override {
|
void visit(AstJumpBlock* nodep) override {
|
||||||
// As with Loop's we can't predict if a JumpGo will kill us or not
|
// 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_noopt);
|
||||||
VL_RESTORER(m_lifep);
|
VL_RESTORER(m_lifep);
|
||||||
m_lifep = new LifeBlock{m_lifep, m_statep};
|
m_lifep = new LifeBlock{m_lifep, m_statep};
|
||||||
setNoopt();
|
setNoopt("jumpblock");
|
||||||
iterateAndNextNull(nodep->stmtsp());
|
iterateAndNextNull(nodep->stmtsp());
|
||||||
UINFO(4, " joinjump");
|
UINFO(4, " joinjump");
|
||||||
// For the next assignments, clear any variables that were read or written in the block
|
// For the next assignments, clear any variables that were read or written in the block
|
||||||
m_lifep->lifeToAbove();
|
m_lifep->lifeToAbove();
|
||||||
VL_DO_DANGLING(delete m_lifep, m_lifep);
|
VL_DO_DANGLING(delete m_lifep, m_lifep);
|
||||||
}
|
}
|
||||||
if (m_containsTiming) setNoopt();
|
if (m_containsTiming) setNoopt("timing");
|
||||||
}
|
}
|
||||||
void visit(AstNodeCCall* nodep) override {
|
void visit(AstNodeCCall* nodep) override {
|
||||||
// UINFO(4, " CCALL " << nodep);
|
// UINFO(4, " CCALL " << nodep);
|
||||||
|
|
@ -399,7 +417,7 @@ class LifeVisitor final : public VNVisitor {
|
||||||
// Enter the function and trace it
|
// Enter the function and trace it
|
||||||
// else is non-inline or public function we optimize separately
|
// else is non-inline or public function we optimize separately
|
||||||
if (nodep->funcp()->entryPoint()) {
|
if (nodep->funcp()->entryPoint()) {
|
||||||
setNoopt();
|
setNoopt("ccall");
|
||||||
} else {
|
} else {
|
||||||
m_tracingCall = true;
|
m_tracingCall = true;
|
||||||
iterate(nodep->funcp());
|
iterate(nodep->funcp());
|
||||||
|
|
@ -409,8 +427,8 @@ class LifeVisitor final : public VNVisitor {
|
||||||
// UINFO(4, " CFUNC " << nodep);
|
// UINFO(4, " CFUNC " << nodep);
|
||||||
if (!m_tracingCall && !nodep->entryPoint()) return;
|
if (!m_tracingCall && !nodep->entryPoint()) return;
|
||||||
m_tracingCall = false;
|
m_tracingCall = false;
|
||||||
if (nodep->recursive()) setNoopt();
|
if (nodep->recursive()) setNoopt("recursive");
|
||||||
if (nodep->noLife()) setNoopt();
|
if (nodep->noLife()) setNoopt("nolife");
|
||||||
if (nodep->dpiImportPrototype() && !nodep->dpiPure()) {
|
if (nodep->dpiImportPrototype() && !nodep->dpiPure()) {
|
||||||
m_sideEffect = true; // If appears on assign RHS, don't ever delete the assignment
|
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 {
|
void visit(AstNode* nodep) override {
|
||||||
if (nodep->isTimingControl()) {
|
if (nodep->isTimingControl()) {
|
||||||
// V3Life doesn't understand time sense - don't optimize
|
// V3Life doesn't understand time sense - don't optimize
|
||||||
setNoopt();
|
setNoopt("timing");
|
||||||
m_containsTiming = true;
|
m_containsTiming = true;
|
||||||
}
|
}
|
||||||
iterateChildren(nodep);
|
iterateChildren(nodep);
|
||||||
|
|
|
||||||
|
|
@ -1727,7 +1727,6 @@ class LinkDotFindVisitor final : public VNVisitor {
|
||||||
newvarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
|
newvarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
|
||||||
newvarp->funcReturn(true);
|
newvarp->funcReturn(true);
|
||||||
newvarp->trace(false); // Not user visible
|
newvarp->trace(false); // Not user visible
|
||||||
newvarp->attrIsolateAssign(nodep->attrIsolateAssign());
|
|
||||||
nodep->fvarp(newvarp);
|
nodep->fvarp(newvarp);
|
||||||
// Explicit insert required, as the var name shadows the upper level's task name
|
// Explicit insert required, as the var name shadows the upper level's task name
|
||||||
m_statep->insertSym(m_curSymp, newvarp->name(), newvarp, nullptr /*classOrPackagep*/);
|
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;
|
if (it != m_beginDisableBegins.end()) return it->second;
|
||||||
|
|
||||||
AstBegin* const beginBodyp = new AstBegin{fl, "", nullptr, false};
|
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());
|
if (beginp->stmtsp()) beginBodyp->addStmtsp(beginp->stmtsp()->unlinkFrBackWithNext());
|
||||||
|
|
||||||
AstFork* const forkp = new AstFork{fl, VJoinType::JOIN};
|
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...
|
varp->sigPublic(true); // User needs to be able to get to it...
|
||||||
oldvarp->primaryIO(false);
|
oldvarp->primaryIO(false);
|
||||||
varp->primaryIO(true);
|
varp->primaryIO(true);
|
||||||
|
varp->icoMaybeWritten(oldvarp->icoMaybeWritten());
|
||||||
if (varp->isRef() || varp->isConstRef()) {
|
if (varp->isRef() || varp->isConstRef()) {
|
||||||
varp->v3warn(E_UNSUPPORTED,
|
varp->v3warn(E_UNSUPPORTED,
|
||||||
"Unsupported: ref/const ref as primary input/output: "
|
"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");
|
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
|
||||||
m_varp->sigUserRWPublic(true);
|
m_varp->sigUserRWPublic(true);
|
||||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
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) {
|
} else if (nodep->attrType() == VAttrType::VAR_SFORMAT) {
|
||||||
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
|
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
|
||||||
m_varp->attrSFormat(true);
|
m_varp->attrSFormat(true);
|
||||||
|
|
|
||||||
|
|
@ -1077,6 +1077,9 @@ void V3Options::notify() VL_MT_DISABLED {
|
||||||
// Preprocessor defines based on options used
|
// Preprocessor defines based on options used
|
||||||
if (timing().isSetTrue()) V3PreShell::defineCmdLine("VERILATOR_TIMING", "1");
|
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
|
// === Leave last
|
||||||
// Mark options as available
|
// Mark options as available
|
||||||
m_available = true;
|
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-balance-cat", FOnOff, &m_fFuncBalanceCat);
|
||||||
DECL_OPTION("-ffunc-opt-split-cat", FOnOff, &m_fFuncSplitCat);
|
DECL_OPTION("-ffunc-opt-split-cat", FOnOff, &m_fFuncSplitCat);
|
||||||
DECL_OPTION("-fgate", FOnOff, &m_fGate);
|
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", FOnOff, &m_fInline);
|
||||||
DECL_OPTION("-finline-funcs", FOnOff, &m_fInlineFuncs);
|
DECL_OPTION("-finline-funcs", FOnOff, &m_fInlineFuncs);
|
||||||
DECL_OPTION("-finline-funcs-eager", FOnOff, &m_fInlineFuncsEager);
|
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_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_fFuncSplitCat = true; // main switch: -fno-func-split-cat: expansion of C macros
|
||||||
bool m_fGate; // main switch: -fno-gate: gate wire elimination
|
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_fInline; // main switch: -fno-inline: module inlining
|
||||||
bool m_fInlineFuncs = true; // main switch: -fno-inline-funcs: function 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
|
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 fFuncSplitCat() const { return m_fFuncSplitCat; }
|
||||||
bool fFunc() const { return fFuncSplitCat() || fFuncBalanceCat(); }
|
bool fFunc() const { return fFuncSplitCat() || fFuncBalanceCat(); }
|
||||||
bool fGate() const { return m_fGate; }
|
bool fGate() const { return m_fGate; }
|
||||||
|
VOptionBool fIcoChangeDetect() const { return m_fIcoChangeDetect; }
|
||||||
bool fInline() const { return m_fInline; }
|
bool fInline() const { return m_fInline; }
|
||||||
bool fInlineFuncs() const { return m_fInlineFuncs; }
|
bool fInlineFuncs() const { return m_fInlineFuncs; }
|
||||||
bool fInlineFuncsEager() const { return m_fInlineFuncsEager; }
|
bool fInlineFuncsEager() const { return m_fInlineFuncsEager; }
|
||||||
|
|
|
||||||
|
|
@ -1509,6 +1509,41 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||||
iterate(sump);
|
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 {
|
void visit(AstRedOr* nodep) override {
|
||||||
if (editFormat(nodep)) return;
|
if (editFormat(nodep)) return;
|
||||||
// Convert to (x != 0)
|
// Convert to (x != 0)
|
||||||
|
|
|
||||||
|
|
@ -51,8 +51,6 @@ class SampledVisitor final : public VNVisitor {
|
||||||
AstVar* const newvarp = new AstVar{flp, VVarType::MODULETEMP, newvarname, varp->dtypep()};
|
AstVar* const newvarp = new AstVar{flp, VVarType::MODULETEMP, newvarname, varp->dtypep()};
|
||||||
m_scopep->modp()->addStmtsp(newvarp);
|
m_scopep->modp()->addStmtsp(newvarp);
|
||||||
AstVarScope* const newvscp = new AstVarScope{flp, m_scopep, 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);
|
newvarp->sampled(true);
|
||||||
vscp->user1p(newvscp);
|
vscp->user1p(newvscp);
|
||||||
m_scopep->addVarsp(newvscp);
|
m_scopep->addVarsp(newvscp);
|
||||||
|
|
|
||||||
|
|
@ -529,8 +529,46 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
|
||||||
+ entry.m_memberp->name());
|
+ 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
|
// 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, {},
|
const TriggerKit trigKit = TriggerKit::create(netlistp, initFuncp, senExprBuilder, {},
|
||||||
senTreeps, "ico", extraTriggers, false, false);
|
senTreeps, "ico", extraTriggers, false, false);
|
||||||
std::ignore = senExprBuilder.getAndClearResults();
|
std::ignore = senExprBuilder.getAndClearResults();
|
||||||
|
|
@ -543,14 +581,14 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
|
||||||
|
|
||||||
// Remap sensitivities
|
// Remap sensitivities
|
||||||
remapSensitivities(logic, trigKit.mapVec());
|
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
|
// Create the inverse map from trigger ref AstSenTree to original AstSenTree
|
||||||
V3Order::TrigToSenMap trigToSen;
|
V3Order::TrigToSenMap trigToSen;
|
||||||
invertAndMergeSenTreeMap(trigToSen, trigKit.mapVec());
|
invertAndMergeSenTreeMap(trigToSen, trigKit.mapVec());
|
||||||
|
|
||||||
// The trigger top level inputs (first iteration)
|
// The 'first iteration' trigger for top level inputs - lazy constructed only if needed
|
||||||
AstSenTree* const inputChanged
|
AstSenTree* firstIterTriggerp = nullptr;
|
||||||
= trigKit.newExtraTriggerSenTree(trigKit.vscp(), firstIterationTrigger);
|
|
||||||
|
|
||||||
// The DPI Export trigger
|
// The DPI Export trigger
|
||||||
AstSenTree* const dpiExportTriggered
|
AstSenTree* const dpiExportTriggered
|
||||||
|
|
@ -565,9 +603,19 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
|
||||||
netlistp, {&logic}, trigToSen, "ico", false, false,
|
netlistp, {&logic}, trigToSen, "ico", false, false,
|
||||||
[&](const AstVarScope* vscp, std::vector<AstSenTree*>& out) {
|
[&](const AstVarScope* vscp, std::vector<AstSenTree*>& out) {
|
||||||
AstVar* const varp = vscp->varp();
|
AstVar* const varp = vscp->varp();
|
||||||
if (varp->isPrimaryInish() || varp->isSigUserRWPublic()) {
|
// If it has an explicit change detect trigger, use that,
|
||||||
out.push_back(inputChanged);
|
// 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 (varp->isWrittenByDpi()) out.push_back(dpiExportTriggered);
|
||||||
if (vscp->varp()->sensIfacep() || vscp->varp()->isVirtIface()) {
|
if (vscp->varp()->sensIfacep() || vscp->varp()->isVirtIface()) {
|
||||||
const auto& ifaceTriggered
|
const auto& ifaceTriggered
|
||||||
|
|
@ -593,8 +641,14 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
|
||||||
// Work statements: Invoke the 'ico' function
|
// Work statements: Invoke the 'ico' function
|
||||||
util::callVoidFunc(icoFuncp));
|
util::callVoidFunc(icoFuncp));
|
||||||
|
|
||||||
// Add the first iteration trigger to the trigger computation function
|
// Add the first iteration trigger to the trigger computation function - if used
|
||||||
trigKit.addExtraTriggerAssignment(icoLoop.firstIterp, firstIterationTrigger, false);
|
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;
|
return icoLoop.stmtsp;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -351,8 +351,7 @@ std::string reportLoopVars(FileLine* /*warnFl*/, Graph* graphp, SchedAcyclicVarV
|
||||||
|
|
||||||
if (splittable) {
|
if (splittable) {
|
||||||
ss << V3Error::warnMore()
|
ss << V3Error::warnMore()
|
||||||
<< "... Suggest add /*verilator split_var*/ or /*verilator "
|
<< "... Suggest add /*verilator split_var*/ to appropriate variables above.\n";
|
||||||
"isolate_assignments*/ to appropriate variables above.\n";
|
|
||||||
}
|
}
|
||||||
V3Stats::addStat("Scheduling, split_var, candidates", splittable);
|
V3Stats::addStat("Scheduling, split_var, candidates", splittable);
|
||||||
return ss.str();
|
return ss.str();
|
||||||
|
|
|
||||||
|
|
@ -150,8 +150,8 @@ public:
|
||||||
: SchedReplicateVertex{graphp}
|
: SchedReplicateVertex{graphp}
|
||||||
, m_vscp{vscp} {
|
, m_vscp{vscp} {
|
||||||
// Top level inputs are
|
// Top level inputs are
|
||||||
if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->isWrittenByDpi()
|
if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->sampled()
|
||||||
|| varp()->sensIfacep() || varp()->isVirtIface()) {
|
|| varp()->isWrittenByDpi() || varp()->sensIfacep() || varp()->isVirtIface()) {
|
||||||
addDrivingRegions(INPUT);
|
addDrivingRegions(INPUT);
|
||||||
}
|
}
|
||||||
// Currently we always execute suspendable processes at the beginning of
|
// 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");
|
UASSERT_OBJ(nodep->taskp(), nodep, "Unlinked task");
|
||||||
TaskFTaskVertex* const taskVtxp = getFTaskVertex(nodep->taskp());
|
TaskFTaskVertex* const taskVtxp = getFTaskVertex(nodep->taskp());
|
||||||
new TaskEdge{&m_callGraph, m_curVxp, taskVtxp};
|
new TaskEdge{&m_callGraph, m_curVxp, taskVtxp};
|
||||||
if (isVirtualIfaceMethodCall(nodep) && isIfaceFTaskScope(getScope(nodep->taskp()))) {
|
// Virtual-interface method calls dispatch through a runtime handle and
|
||||||
taskVtxp->needsNonInlineCFunc(true);
|
// must not be inlined.
|
||||||
}
|
if (isVirtualIfaceMethodCall(nodep)) taskVtxp->needsNonInlineCFunc(true);
|
||||||
// Do we have to disable inlining the function?
|
// Do we have to disable inlining the function?
|
||||||
const V3TaskConnects tconnects = V3Task::taskConnects(nodep, nodep->taskp()->stmtsp());
|
const V3TaskConnects tconnects = V3Task::taskConnects(nodep, nodep->taskp()->stmtsp());
|
||||||
if (!taskVtxp->noInline()) { // Else short-circuit below
|
if (!taskVtxp->noInline()) { // Else short-circuit below
|
||||||
|
|
@ -1638,6 +1638,7 @@ class TaskVisitor final : public VNVisitor {
|
||||||
// Create cloned statements
|
// Create cloned statements
|
||||||
AstNode* beginp;
|
AstNode* beginp;
|
||||||
AstCNew* cnewp = nullptr;
|
AstCNew* cnewp = nullptr;
|
||||||
|
// getScope() is safe here: TaskStateVisitor stamped all FTask scopes before this pass.
|
||||||
const bool virtualIfaceCall
|
const bool virtualIfaceCall
|
||||||
= TaskStateVisitor::isVirtualIfaceMethodCall(nodep)
|
= TaskStateVisitor::isVirtualIfaceMethodCall(nodep)
|
||||||
&& TaskStateVisitor::isIfaceFTaskScope(m_statep->getScope(nodep->taskp()));
|
&& TaskStateVisitor::isIfaceFTaskScope(m_statep->getScope(nodep->taskp()));
|
||||||
|
|
|
||||||
|
|
@ -51,15 +51,14 @@ class UnknownVisitor final : public VNVisitor {
|
||||||
static const std::string s_xrandPrefix;
|
static const std::string s_xrandPrefix;
|
||||||
|
|
||||||
// STATE - across all visitors
|
// 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
|
V3UniqueNames m_lvboundNames; // For generating unique temporary variable names
|
||||||
std::unique_ptr<V3UniqueNames> m_xrandNames; // 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)
|
// STATE - for current visit position (use VL_RESTORER)
|
||||||
AstNodeModule* m_modp = nullptr; // Current module
|
AstNodeModule* m_modp = nullptr; // Current module
|
||||||
AstNodeFTask* m_ftaskp = nullptr; // Current function/task
|
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_constXCvt = false; // Convert X's
|
||||||
bool m_allowXUnique = true; // Allow unique assignments
|
bool m_allowXUnique = true; // Allow unique assignments
|
||||||
|
|
||||||
|
|
@ -69,38 +68,21 @@ class UnknownVisitor final : public VNVisitor {
|
||||||
if (m_ftaskp) {
|
if (m_ftaskp) {
|
||||||
varp->funcLocal(true);
|
varp->funcLocal(true);
|
||||||
varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
|
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 {
|
} 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) {
|
void replaceBoundLvalue(AstNodeExpr* nodep, AstNodeExpr* condp) {
|
||||||
// Spec says a out-of-range LHS SEL results in a NOP.
|
// Spec says a out-of-range LHS SEL results in a NOP.
|
||||||
// This is a PITA. We could:
|
// We wrap the expression into IF
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
AstNodeExpr* prep = nodep;
|
AstNodeExpr* prep = nodep;
|
||||||
|
|
||||||
// Scan back to put the condlvalue above all selects (IE top of the lvalue)
|
// 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,
|
// 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.
|
// Saves us teaching V3Const how to optimize, and it won't be needed again.
|
||||||
if (const AstIf* const ifp = VN_AS(prep->user2p(), If)) {
|
if (const AstIf* const ifp = VN_AS(prep->user2p(), If)) {
|
||||||
UASSERT_OBJ(!needDly, prep, "Should have already converted to non-delay");
|
|
||||||
VNRelinker replaceHandle;
|
VNRelinker replaceHandle;
|
||||||
AstNodeExpr* const earliercondp = ifp->condp()->unlinkFrBack(&replaceHandle);
|
AstNodeExpr* const earliercondp = ifp->condp()->unlinkFrBack(&replaceHandle);
|
||||||
AstNodeExpr* const newp = new AstLogAnd{condp->fileline(), condp, earliercondp};
|
AstNodeExpr* const newp = new AstLogAnd{condp->fileline(), condp, earliercondp};
|
||||||
UINFO(4, "Edit BOUNDLVALUE " << newp);
|
UINFO(4, "Edit BOUNDLVALUE " << newp);
|
||||||
replaceHandle.relink(newp);
|
replaceHandle.relink(newp);
|
||||||
} else {
|
} 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'
|
AstNode* stmtp = prep->backp(); // Grab above point before we replace 'prep'
|
||||||
while (!VN_IS(stmtp, NodeStmt)) stmtp = stmtp->backp();
|
while (!VN_IS(stmtp, NodeStmt)) stmtp = stmtp->backp();
|
||||||
|
VNRelinker replaceHandle;
|
||||||
prep->replaceWith(new AstVarRef{fl, varp, VAccess::WRITE});
|
AstNode* const origStmtp = stmtp->unlinkFrBack(&replaceHandle);
|
||||||
if (m_timingControlp) m_timingControlp->unlinkFrBack();
|
AstNode* elseStmtp = nullptr;
|
||||||
AstIf* const newp = new AstIf{
|
const bool hasSideEffects = origStmtp->exists(
|
||||||
fl, condp,
|
[](AstNode* const np) { return !np->isPure() || np->isTimingControl(); });
|
||||||
(needDly
|
if (hasSideEffects) {
|
||||||
? static_cast<AstNode*>(new AstAssignDly{
|
// Copy original statement and replace `prep` with reference to tmp var to make
|
||||||
fl, prep, new AstVarRef{fl, varp, VAccess::READ}, m_timingControlp})
|
// sure that side effect will take place
|
||||||
: static_cast<AstNode*>(new AstAssign{
|
m_statElses++;
|
||||||
fl, prep, new AstVarRef{fl, varp, VAccess::READ}, m_timingControlp}))};
|
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->branchPred(VBranchPred::BP_LIKELY);
|
||||||
newp->isBoundsCheck(true);
|
newp->isBoundsCheck(true);
|
||||||
UINFOTREE(9, newp, "", "_new");
|
|
||||||
stmtp->addNextHere(newp);
|
|
||||||
prep->user2p(newp); // Save so we may LogAnd it next time
|
prep->user2p(newp); // Save so we may LogAnd it next time
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -208,23 +193,6 @@ class UnknownVisitor final : public VNVisitor {
|
||||||
m_ftaskp = nodep;
|
m_ftaskp = nodep;
|
||||||
iterateChildren(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 {
|
void visit(AstCaseItem* nodep) override {
|
||||||
VL_RESTORER(m_constXCvt);
|
VL_RESTORER(m_constXCvt);
|
||||||
m_constXCvt = false; // Avoid losing the X's in casex
|
m_constXCvt = false; // Avoid losing the X's in casex
|
||||||
|
|
@ -584,6 +552,7 @@ public:
|
||||||
}
|
}
|
||||||
~UnknownVisitor() override { //
|
~UnknownVisitor() override { //
|
||||||
V3Stats::addStat("Unknowns, variables created", m_statUnkVars);
|
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
|
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) {
|
static void packIfUnpacked(AstNodeExpr* const nodep) {
|
||||||
if (AstUnpackArrayDType* const unpackDTypep = VN_CAST(nodep->dtypep(), UnpackArrayDType)) {
|
if (AstUnpackArrayDType* const unpackDTypep = VN_CAST(nodep->dtypep(), UnpackArrayDType)) {
|
||||||
const int elementsNum = unpackDTypep->arrayUnpackedElements();
|
const int elementsNum = unpackDTypep->arrayUnpackedElements();
|
||||||
|
|
@ -274,6 +267,9 @@ class WidthVisitor final : public VNVisitor {
|
||||||
nodep->findLogicDType(unpackBits, unpackMinBits, VSigning::UNSIGNED)});
|
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
|
// When fromp() is a DType (e.g. unlinked RefDType), resolve through
|
||||||
// the ref chain; when it's an expression, dtypep() is already resolved.
|
// the ref chain; when it's an expression, dtypep() is already resolved.
|
||||||
static AstNodeDType* fromDTypep(AstNode* fromp) {
|
static AstNodeDType* fromDTypep(AstNode* fromp) {
|
||||||
|
|
@ -979,7 +975,10 @@ class WidthVisitor final : public VNVisitor {
|
||||||
}
|
}
|
||||||
const AstNodeDType* const lhsDtypep = nodep->lhsp()->dtypep()->skipRefToEnump();
|
const AstNodeDType* const lhsDtypep = nodep->lhsp()->dtypep()->skipRefToEnump();
|
||||||
if (VN_IS(lhsDtypep, DynArrayDType) || VN_IS(lhsDtypep, QueueDType)
|
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();
|
nodep->dtypeSetStream();
|
||||||
} else if (lhsDtypep->isCompound()) {
|
} else if (lhsDtypep->isCompound()) {
|
||||||
nodep->v3warn(E_UNSUPPORTED,
|
nodep->v3warn(E_UNSUPPORTED,
|
||||||
|
|
@ -3074,6 +3073,7 @@ class WidthVisitor final : public VNVisitor {
|
||||||
UASSERT_OBJ(nodep->dtypep(), nodep, "LHS var should be dtype completed");
|
UASSERT_OBJ(nodep->dtypep(), nodep, "LHS var should be dtype completed");
|
||||||
}
|
}
|
||||||
// UINFOTREE(9, nodep, "", "VRout");
|
// UINFOTREE(9, nodep, "", "VRout");
|
||||||
|
if (nodep->access().isWriteOrRW()) nodep->varp()->icoMaybeWritten(true);
|
||||||
if (nodep->access().isWriteOrRW() && nodep->varp()->direction() == VDirection::CONSTREF) {
|
if (nodep->access().isWriteOrRW() && nodep->varp()->direction() == VDirection::CONSTREF) {
|
||||||
nodep->v3error("Assigning to const ref variable: " << nodep->prettyNameQ());
|
nodep->v3error("Assigning to const ref variable: " << nodep->prettyNameQ());
|
||||||
} else if (nodep->access().isWriteOrRW() && nodep->varp()->isInput()
|
} else if (nodep->access().isWriteOrRW() && nodep->varp()->isInput()
|
||||||
|
|
@ -4352,6 +4352,12 @@ class WidthVisitor final : public VNVisitor {
|
||||||
}
|
}
|
||||||
void methodCallAssoc(AstMethodCall* nodep, AstAssocArrayDType* adtypep) {
|
void methodCallAssoc(AstMethodCall* nodep, AstAssocArrayDType* adtypep) {
|
||||||
AstCMethodHard* newp = nullptr;
|
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()
|
if (nodep->name() == "num" // function int num()
|
||||||
|| nodep->name() == "size") {
|
|| nodep->name() == "size") {
|
||||||
methodOkArguments(nodep, 0, 0);
|
methodOkArguments(nodep, 0, 0);
|
||||||
|
|
@ -6289,14 +6295,14 @@ class WidthVisitor final : public VNVisitor {
|
||||||
userIterateAndNext(nodep->rhsp(), WidthVP{nodep->dtypep(), PRELIM}.p());
|
userIterateAndNext(nodep->rhsp(), WidthVP{nodep->dtypep(), PRELIM}.p());
|
||||||
//
|
//
|
||||||
// UINFOTREE(1, nodep, "", "assign");
|
// UINFOTREE(1, nodep, "", "assign");
|
||||||
AstNodeDType* const lhsDTypep
|
AstNodeDType* lhsDTypep
|
||||||
= nodep->lhsp()->dtypep(); // Note we use rhsp for context determined
|
= nodep->lhsp()->dtypep(); // Note we use rhsp for context determined
|
||||||
|
|
||||||
// Check width of stream and wrap if needed
|
// Check width of stream and wrap if needed
|
||||||
if (AstNodeStream* const streamp = VN_CAST(nodep->rhsp(), NodeStream)) {
|
if (AstNodeStream* const streamp = VN_CAST(nodep->rhsp(), NodeStream)) {
|
||||||
AstNodeDType* const lhsDTypeSkippedRefp = lhsDTypep->skipRefp();
|
AstNodeDType* const lhsDTypeSkippedRefp = lhsDTypep->skipRefp();
|
||||||
const int lwidth = widthUnpacked(lhsDTypeSkippedRefp);
|
const int lwidth = lhsDTypeSkippedRefp->widthStream();
|
||||||
const int rwidth = widthUnpacked(streamp->lhsp()->dtypep()->skipRefp());
|
const int rwidth = streamp->lhsp()->dtypep()->skipRefp()->widthStream();
|
||||||
if (lwidth != 0 && lwidth < rwidth) {
|
if (lwidth != 0 && lwidth < rwidth) {
|
||||||
nodep->v3widthWarn(lwidth, rwidth,
|
nodep->v3widthWarn(lwidth, rwidth,
|
||||||
"Target fixed size variable ("
|
"Target fixed size variable ("
|
||||||
|
|
@ -6308,16 +6314,18 @@ class WidthVisitor final : public VNVisitor {
|
||||||
const int queueElementSize = streamp->lhsp()->dtypep()->subDTypep()->width();
|
const int queueElementSize = streamp->lhsp()->dtypep()->subDTypep()->width();
|
||||||
UASSERT_OBJ(queueElementSize <= lwidth, nodep, "LHS < RHS");
|
UASSERT_OBJ(queueElementSize <= lwidth, nodep, "LHS < RHS");
|
||||||
}
|
}
|
||||||
if (VN_IS(lhsDTypeSkippedRefp, UnpackArrayDType)) {
|
if (VN_IS(lhsDTypeSkippedRefp, UnpackArrayDType)
|
||||||
|
|| lowerAsFixedAggregate(lhsDTypeSkippedRefp)) {
|
||||||
streamp->unlinkFrBack();
|
streamp->unlinkFrBack();
|
||||||
nodep->rhsp(new AstCvtPackedToArray{streamp->fileline(), streamp,
|
nodep->rhsp(new AstCvtPackedToArray{streamp->fileline(), streamp,
|
||||||
lhsDTypeSkippedRefp});
|
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 AstNodeDType* const rhsDTypep = nodep->rhsp()->dtypep()->skipRefp();
|
||||||
const int lwidth = widthUnpacked(streamp->lhsp()->dtypep()->skipRefp());
|
AstNodeDType* const lhsStreamDTypep = streamp->lhsp()->dtypep()->skipRefp();
|
||||||
const int rwidth = widthUnpacked(rhsDTypep);
|
const int lwidth = lhsStreamDTypep->widthStream();
|
||||||
|
const int rwidth = rhsDTypep->widthStream();
|
||||||
if (rwidth != 0 && rwidth < lwidth) {
|
if (rwidth != 0 && rwidth < lwidth) {
|
||||||
nodep->v3widthWarn(lwidth, rwidth,
|
nodep->v3widthWarn(lwidth, rwidth,
|
||||||
"Stream target requires "
|
"Stream target requires "
|
||||||
|
|
@ -6325,7 +6333,27 @@ class WidthVisitor final : public VNVisitor {
|
||||||
<< " bits, but source expression only provides "
|
<< " bits, but source expression only provides "
|
||||||
<< rwidth << " bits (IEEE 1800-2023 11.4.14.3)");
|
<< 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();
|
AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack();
|
||||||
nodep->rhsp(
|
nodep->rhsp(
|
||||||
new AstCvtArrayToPacked{rhsp->fileline(), rhsp, streamp->dtypep()});
|
new AstCvtArrayToPacked{rhsp->fileline(), rhsp, streamp->dtypep()});
|
||||||
|
|
|
||||||
|
|
@ -98,12 +98,10 @@
|
||||||
#include "V3Scoreboard.h"
|
#include "V3Scoreboard.h"
|
||||||
#include "V3Slice.h"
|
#include "V3Slice.h"
|
||||||
#include "V3Split.h"
|
#include "V3Split.h"
|
||||||
#include "V3SplitAs.h"
|
|
||||||
#include "V3SplitVar.h"
|
#include "V3SplitVar.h"
|
||||||
#include "V3Stats.h"
|
#include "V3Stats.h"
|
||||||
#include "V3String.h"
|
#include "V3String.h"
|
||||||
#include "V3Subst.h"
|
#include "V3Subst.h"
|
||||||
#include "V3TSP.h"
|
|
||||||
#include "V3Table.h"
|
#include "V3Table.h"
|
||||||
#include "V3Task.h"
|
#include "V3Task.h"
|
||||||
#include "V3ThreadPool.h"
|
#include "V3ThreadPool.h"
|
||||||
|
|
@ -261,6 +259,7 @@ static void process() {
|
||||||
|
|
||||||
// Assertion insertion
|
// Assertion insertion
|
||||||
// After we've added block coverage, but before other nasty transforms
|
// After we've added block coverage, but before other nasty transforms
|
||||||
|
V3AssertCommon::collectDefaultDisable(v3Global.rootp());
|
||||||
V3AssertNfa::assertNfaAll(v3Global.rootp());
|
V3AssertNfa::assertNfaAll(v3Global.rootp());
|
||||||
// V3AssertProp removed: NFA subsumes multi-cycle property lowering.
|
// V3AssertProp removed: NFA subsumes multi-cycle property lowering.
|
||||||
// Unsupported constructs fall through to V3AssertPre.
|
// Unsupported constructs fall through to V3AssertPre.
|
||||||
|
|
@ -421,7 +420,6 @@ static void process() {
|
||||||
|
|
||||||
// Split single ALWAYS blocks into multiple blocks for better ordering chances
|
// Split single ALWAYS blocks into multiple blocks for better ordering chances
|
||||||
if (v3Global.opt.fSplit()) V3Split::splitAll(v3Global.rootp());
|
if (v3Global.opt.fSplit()) V3Split::splitAll(v3Global.rootp());
|
||||||
V3SplitAs::splitAsAll(v3Global.rootp());
|
|
||||||
|
|
||||||
// Create tracing sample points, before we start eliminating signals
|
// Create tracing sample points, before we start eliminating signals
|
||||||
if (v3Global.opt.trace()) V3TraceDecl::traceDeclAll(v3Global.rootp());
|
if (v3Global.opt.trace()) V3TraceDecl::traceDeclAll(v3Global.rootp());
|
||||||
|
|
@ -734,7 +732,6 @@ static bool verilate(const string& argString) {
|
||||||
VHashSha256::selfTest();
|
VHashSha256::selfTest();
|
||||||
VSpellCheck::selfTest();
|
VSpellCheck::selfTest();
|
||||||
V3Graph::selfTest();
|
V3Graph::selfTest();
|
||||||
V3TSP::selfTest();
|
|
||||||
V3ScoreboardBase::selfTest();
|
V3ScoreboardBase::selfTest();
|
||||||
V3Order::selfTestParallel();
|
V3Order::selfTestParallel();
|
||||||
V3ExecGraph::selfTest();
|
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)
|
out.append(line)
|
||||||
lines = out
|
lines = out
|
||||||
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:
|
with open(outname, "w", encoding="utf-8") as fh:
|
||||||
tmpy = re.escape(tmp_prefix() + ".y")
|
tmpy = re.escape(tmp_prefix() + ".y")
|
||||||
|
|
|
||||||
|
|
@ -137,8 +137,6 @@ constParameterPointer:src/V3Tristate.cpp
|
||||||
constParameterReference:src/V3Tristate.cpp
|
constParameterReference:src/V3Tristate.cpp
|
||||||
constVariablePointer:src/V3Tristate.cpp
|
constVariablePointer:src/V3Tristate.cpp
|
||||||
constVariableReference:src/V3Tristate.cpp
|
constVariableReference:src/V3Tristate.cpp
|
||||||
constVariablePointer:src/V3TSP.cpp
|
|
||||||
constVariableReference:src/V3TSP.cpp
|
|
||||||
constParameterPointer:src/V3Undriven.cpp
|
constParameterPointer:src/V3Undriven.cpp
|
||||||
constVariablePointer:src/V3Undriven.cpp
|
constVariablePointer:src/V3Undriven.cpp
|
||||||
constParameterPointer:src/V3Unroll.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 { $$ = 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_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_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_BIGUINT { $$ = new AstAttrOf{$1, VAttrType::VAR_SC_BIGUINT}; }
|
||||||
| yVL_SC_BV { $$ = new AstAttrOf{$1, VAttrType::VAR_SC_BV}; }
|
| yVL_SC_BV { $$ = new AstAttrOf{$1, VAttrType::VAR_SC_BV}; }
|
||||||
| yVL_SFORMAT { $$ = new AstAttrOf{$1, VAttrType::VAR_SFORMAT}; }
|
| 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
|
function_declaration<nodeFTaskp>: // IEEE: function_declaration + function_body_declaration
|
||||||
yFUNCTION dynamic_override_specifiersE lifetimeE funcId funcIsolateE tfGuts yENDFUNCTION endLabelE
|
yFUNCTION dynamic_override_specifiersE lifetimeE funcId funcIsolateE tfGuts yENDFUNCTION endLabelE
|
||||||
{ $$ = $4; $4->attrIsolateAssign($5); $$->addStmtsp($6);
|
{ $$ = $4; $$->addStmtsp($6);
|
||||||
$$->baseOverride($2);
|
$$->baseOverride($2);
|
||||||
$$->lifetime($3);
|
$$->lifetime($3);
|
||||||
GRAMMARP->endLabel($<fl>8, $$, $8); }
|
GRAMMARP->endLabel($<fl>8, $$, $8); }
|
||||||
| yFUNCTION dynamic_override_specifiersE lifetimeE funcIdNew funcIsolateE tfNewGuts yENDFUNCTION endLabelE
|
| yFUNCTION dynamic_override_specifiersE lifetimeE funcIdNew funcIsolateE tfNewGuts yENDFUNCTION endLabelE
|
||||||
{ $$ = $4; $4->attrIsolateAssign($5); $$->addStmtsp($6);
|
{ $$ = $4; $$->addStmtsp($6);
|
||||||
$$->baseOverride($2);
|
$$->baseOverride($2);
|
||||||
$$->lifetime($3);
|
$$->lifetime($3);
|
||||||
GRAMMARP->endLabel($<fl>8, $$, $8); }
|
GRAMMARP->endLabel($<fl>8, $$, $8); }
|
||||||
|
|
@ -6497,14 +6497,34 @@ concurrent_assertion_statement<nodeStmtp>: // ==IEEE: concurrent_assertion_stat
|
||||||
| yCOVER yPROPERTY '(' property_spec ')' stmt
|
| yCOVER yPROPERTY '(' property_spec ')' stmt
|
||||||
{ $$ = new AstCover{$1, $4, $6, VAssertType::CONCURRENT}; }
|
{ $$ = new AstCover{$1, $4, $6, VAssertType::CONCURRENT}; }
|
||||||
// // IEEE: cover_sequence_statement
|
// // 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
|
| yCOVER ySEQUENCE '(' sexpr ')' stmt
|
||||||
{ $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover sequence"); DEL($4, $6); }
|
{ AstCover* const coverp = new AstCover{$1,
|
||||||
// // IEEE: yCOVER ySEQUENCE '(' clocking_event sexpr ')' stmt
|
new AstPropSpec{$4->fileline(), nullptr, nullptr, $4},
|
||||||
// // sexpr already includes "clocking_event sexpr"
|
$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
|
| 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
|
| 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
|
// // IEEE: restrict_property_statement
|
||||||
| yRESTRICT yPROPERTY '(' property_spec ')' ';'
|
| yRESTRICT yPROPERTY '(' property_spec ')' ';'
|
||||||
{ $$ = new AstRestrict{$1, $4}; }
|
{ $$ = new AstRestrict{$1, $4}; }
|
||||||
|
|
@ -6664,14 +6684,8 @@ property_spec<propSpecp>: // IEEE: property_spec
|
||||||
{ $$ = new AstPropSpec{$1, $3, nullptr, $5}; }
|
{ $$ = new AstPropSpec{$1, $3, nullptr, $5}; }
|
||||||
| '@' senitemVar pexpr
|
| '@' senitemVar pexpr
|
||||||
{ $$ = new AstPropSpec{$1, $2, nullptr, $3}; }
|
{ $$ = new AstPropSpec{$1, $2, nullptr, $3}; }
|
||||||
// // Disable applied after the event occurs,
|
| yDISABLE yIFF '(' expr ')' '@' '(' senitem ')' pexpr
|
||||||
// // so no existing AST can represent this
|
{ $$ = new AstPropSpec{$1, $8, $4, $10}; }
|
||||||
| 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 ')' pexpr { $$ = new AstPropSpec{$4->fileline(), nullptr, $4, $6}; }
|
| yDISABLE yIFF '(' expr ')' pexpr { $$ = new AstPropSpec{$4->fileline(), nullptr, $4, $6}; }
|
||||||
| pexpr { $$ = new AstPropSpec{$1->fileline(), nullptr, nullptr, $1}; }
|
| pexpr { $$ = new AstPropSpec{$1->fileline(), nullptr, nullptr, $1}; }
|
||||||
;
|
;
|
||||||
|
|
@ -8559,8 +8573,7 @@ vltVarAttrSpecE<strp>:
|
||||||
;
|
;
|
||||||
|
|
||||||
vltVarAttrFront<attrtypeen>:
|
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 { $$ = VAttrType::VAR_PUBLIC; v3Global.dpi(true); }
|
||||||
| yVLT_PUBLIC_FLAT { $$ = VAttrType::VAR_PUBLIC_FLAT; 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); }
|
| yVLT_PUBLIC_FLAT_RD { $$ = VAttrType::VAR_PUBLIC_FLAT_RD; v3Global.dpi(true); }
|
||||||
|
|
@ -8574,6 +8587,7 @@ vltVarAttrFront<attrtypeen>:
|
||||||
vltVarAttrFrontDeprecated:
|
vltVarAttrFrontDeprecated:
|
||||||
yVLT_CLOCK_ENABLE { }
|
yVLT_CLOCK_ENABLE { }
|
||||||
| yVLT_CLOCKER { }
|
| yVLT_CLOCKER { }
|
||||||
|
| yVLT_ISOLATE_ASSIGNMENTS { }
|
||||||
| yVLT_NO_CLOCKER { }
|
| 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.
|
[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:51: Verilog $stop, ignored due to +verilator+error+limit
|
-Info: t/t_assert_ctl_immediate.v:52: 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:58: 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:46: 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: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 *-*
|
*-* All Finished *-*
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ module t (
|
||||||
|
|
||||||
module_with_assert module_with_assert (clk);
|
module_with_assert module_with_assert (clk);
|
||||||
module_with_assertctl module_with_assertctl (clk);
|
module_with_assertctl module_with_assertctl (clk);
|
||||||
|
module_with_method_ctl module_with_method_ctl ();
|
||||||
|
|
||||||
always @(posedge clk) begin
|
always @(posedge clk) begin
|
||||||
assert (0);
|
assert (0);
|
||||||
|
|
@ -63,3 +64,146 @@ module module_with_assertctl (
|
||||||
f_assert();
|
f_assert();
|
||||||
end
|
end
|
||||||
endmodule
|
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'
|
: ... 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
|
... 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'
|
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:28:5: Unsupported: $assertcontrol control_type '2'
|
||||||
27 | $assertcontrol(Unlock);
|
28 | $assertcontrol(Unlock);
|
||||||
| ^~~~~~~~~~~~~~
|
| ^~~~~~~~~~~~~~
|
||||||
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:29:5: Unsupported: $assertcontrol control_type '6'
|
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:30:5: Unsupported: $assertcontrol control_type '6'
|
||||||
29 | $assertcontrol(PassOn);
|
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
|
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:31:5: Unsupported: assert control assertion_type
|
||||||
: ... note: In instance 't.unsupported_ctl_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
|
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:32:5: Unsupported: assert control assertion_type
|
||||||
: ... note: In instance 't.unsupported_ctl_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'
|
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:33:5: Unsupported: assert control assertion_type
|
||||||
34 | $assertcontrol(PassOff);
|
|
||||||
| ^~~~~~~~~~~~~~
|
|
||||||
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:35:5: Unsupported: assert control assertion_type
|
|
||||||
: ... note: In instance 't.unsupported_ctl_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
|
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:36:5: Unsupported: assert control assertion_type
|
||||||
: ... note: In instance 't.unsupported_ctl_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
|
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:37:5: Unsupported: assert control assertion_type
|
||||||
: ... note: In instance 't.unsupported_ctl_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'
|
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:38:5: Unsupported: assert control assertion_type
|
||||||
39 | $assertcontrol(FailOn);
|
|
||||||
| ^~~~~~~~~~~~~~
|
|
||||||
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:40:5: Unsupported: assert control assertion_type
|
|
||||||
: ... note: In instance 't.unsupported_ctl_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
|
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:41:5: Unsupported: assert control assertion_type
|
||||||
: ... note: In instance 't.unsupported_ctl_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
|
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:42:5: Unsupported: assert control assertion_type
|
||||||
: ... note: In instance 't.unsupported_ctl_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'
|
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:43:5: Unsupported: assert control assertion_type
|
||||||
44 | $assertcontrol(FailOff);
|
|
||||||
| ^~~~~~~~~~~~~~
|
|
||||||
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:45:5: Unsupported: assert control assertion_type
|
|
||||||
: ... note: In instance 't.unsupported_ctl_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
|
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:46:5: Unsupported: assert control assertion_type
|
||||||
: ... note: In instance 't.unsupported_ctl_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
|
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:47:5: Unsupported: assert control assertion_type
|
||||||
: ... note: In instance 't.unsupported_ctl_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'
|
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:48:5: Unsupported: assert control assertion_type
|
||||||
49 | $assertcontrol(NonvacuousOn);
|
|
||||||
| ^~~~~~~~~~~~~~
|
|
||||||
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:50:5: Unsupported: assert control assertion_type
|
|
||||||
: ... note: In instance 't.unsupported_ctl_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
|
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:51:5: Unsupported: assert control assertion_type
|
||||||
: ... note: In instance 't.unsupported_ctl_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
|
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:52:5: Unsupported: assert control assertion_type
|
||||||
: ... note: In instance 't.unsupported_ctl_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'
|
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:53:5: Unsupported: assert control assertion_type
|
||||||
54 | $assertcontrol(VacuousOff);
|
|
||||||
| ^~~~~~~~~~~~~~
|
|
||||||
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:55:5: Unsupported: assert control assertion_type
|
|
||||||
: ... note: In instance 't.unsupported_ctl_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
|
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:56:5: Unsupported: assert control assertion_type
|
||||||
: ... note: In instance 't.unsupported_ctl_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
|
%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:57:5: Unsupported: assert control assertion_type
|
||||||
: ... note: In instance 't.unsupported_ctl_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'
|
: ... 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
|
%Error: Exiting due to
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,16 @@
|
||||||
// SPDX-FileCopyrightText: 2024 Antmicro
|
// SPDX-FileCopyrightText: 2024 Antmicro
|
||||||
// SPDX-License-Identifier: CC0-1.0
|
// SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
module t(input logic clk);
|
module t (
|
||||||
unsupported_ctl_type unsupported_ctl_type(clk ? 1 : 2);
|
input logic clk
|
||||||
unsupported_ctl_type_expr unsupported_ctl_type_expr();
|
);
|
||||||
assert_class assert_class();
|
unsupported_ctl_type unsupported_ctl_type (clk ? 1 : 2);
|
||||||
assert_iface assert_iface();
|
unsupported_ctl_type_expr unsupported_ctl_type_expr ();
|
||||||
assert_iface_class assert_iface_class();
|
|
||||||
endmodule
|
endmodule
|
||||||
|
|
||||||
module unsupported_ctl_type(input int a);
|
module unsupported_ctl_type (
|
||||||
|
input int a
|
||||||
|
);
|
||||||
initial begin
|
initial begin
|
||||||
let Lock = 1;
|
let Lock = 1;
|
||||||
let Unlock = 2;
|
let Unlock = 2;
|
||||||
|
|
@ -64,122 +65,3 @@ module unsupported_ctl_type_expr;
|
||||||
$assertcontrol(ctl_type);
|
$assertcontrol(ctl_type);
|
||||||
end
|
end
|
||||||
endmodule
|
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.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 combined\s+(\d+)', 3)
|
||||||
test.file_grep(test.stats, r'Assertions, assertOn checks hoisted\s+(\d+)', 11)
|
test.file_grep(test.stats, r'Assertions, assertOn checks hoisted\s+(\d+)', 15)
|
||||||
|
|
||||||
test.passes()
|
test.passes()
|
||||||
|
|
|
||||||
|
|
@ -58,4 +58,12 @@ module t;
|
||||||
end
|
end
|
||||||
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
|
endmodule
|
||||||
|
|
|
||||||
|
|
@ -22,15 +22,23 @@ module t;
|
||||||
int qe[int]; // Empty
|
int qe[int]; // Empty
|
||||||
int qv[$]; // Value returns
|
int qv[$]; // Value returns
|
||||||
int qi[$]; // Index 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_q[int];
|
||||||
point points_qe[int]; // Empty points
|
point points_qe[int]; // Empty points
|
||||||
point points_qv[$];
|
point points_qv[$];
|
||||||
int i;
|
int i;
|
||||||
bit b;
|
bit b;
|
||||||
|
bit[229:0] w;
|
||||||
|
|
||||||
q = '{10: 1, 11: 2, 12: 2, 13: 4, 14: 3};
|
q = '{10: 1, 11: 2, 12: 2, 13: 4, 14: 3};
|
||||||
`checkp(q, "'{'ha:'h1, 'hb:'h2, 'hc:'h2, 'hd:'h4, 'he:'h3}");
|
`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
|
// NOT tested: with ... selectors
|
||||||
|
|
||||||
//q.sort; // Not legal on assoc - see t_assoc_meth_bad
|
//q.sort; // Not legal on assoc - see t_assoc_meth_bad
|
||||||
|
|
@ -43,16 +51,34 @@ module t;
|
||||||
`checkp(qv, "'{'h1, 'h2, 'h4, 'h3}");
|
`checkp(qv, "'{'h1, 'h2, 'h4, 'h3}");
|
||||||
qv = qe.unique;
|
qv = qe.unique;
|
||||||
`checkp(qv, "'{}");
|
`checkp(qv, "'{}");
|
||||||
|
|
||||||
|
qwv = qw.unique;
|
||||||
|
`checkp(qwv, "'{'h1, 'h2, 'h4, 'h3}");
|
||||||
|
qwv = qwe.unique;
|
||||||
|
`checkp(qwv, "'{}");
|
||||||
|
|
||||||
qi = q.unique_index;
|
qi = q.unique_index;
|
||||||
qi.sort;
|
qi.sort;
|
||||||
`checkp(qi, "'{'ha, 'hb, 'hd, 'he}");
|
`checkp(qi, "'{'ha, 'hb, 'hd, 'he}");
|
||||||
qi = qe.unique_index;
|
qi = qe.unique_index;
|
||||||
`checkp(qi, "'{}");
|
`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[0] = point'{1, 2};
|
||||||
points_q[1] = point'{2, 4};
|
points_q[1] = point'{2, 4};
|
||||||
points_q[5] = point'{1, 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);
|
points_qv = points_q.unique(p) with (p.x);
|
||||||
`checkh(points_qv.size, 2);
|
`checkh(points_qv.size, 2);
|
||||||
qi = points_q.unique_index (p) with (p.x + p.y);
|
qi = points_q.unique_index (p) with (p.x + p.y);
|
||||||
|
|
@ -113,6 +139,10 @@ module t;
|
||||||
|
|
||||||
qv = q.min;
|
qv = q.min;
|
||||||
`checkp(qv, "'{'h1}");
|
`checkp(qv, "'{'h1}");
|
||||||
|
|
||||||
|
qwv = qw.min;
|
||||||
|
`checkp(qwv, "'{'h1}");
|
||||||
|
|
||||||
points_qv = points_q.min(p) with (p.x + p.y);
|
points_qv = points_q.min(p) with (p.x + p.y);
|
||||||
if (points_qv[0].x != 1 || points_qv[0].y != 2) $stop;
|
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);
|
qv = qe.max(x) with (x + 1);
|
||||||
`checkp(qv, "'{}");
|
`checkp(qv, "'{}");
|
||||||
|
|
||||||
// Reduction methods
|
// Wide
|
||||||
|
qwv = qwe.min;
|
||||||
|
`checkp(qwv, "'{}");
|
||||||
|
qwv = qwe.max;
|
||||||
|
`checkp(qwv, "'{}");
|
||||||
|
|
||||||
|
// Reduction methods
|
||||||
i = q.sum;
|
i = q.sum;
|
||||||
`checkh(i, 32'hc);
|
`checkh(i, 32'hc);
|
||||||
i = q.sum with (item + 1);
|
i = q.sum with (item + 1);
|
||||||
|
|
@ -141,6 +176,12 @@ module t;
|
||||||
i = q.product with (item + 1);
|
i = q.product with (item + 1);
|
||||||
`checkh(i, 32'h168);
|
`checkh(i, 32'h168);
|
||||||
|
|
||||||
|
// Wide
|
||||||
|
w = qw.sum;
|
||||||
|
`checkh(w, 230'hc);
|
||||||
|
w = qw.product;
|
||||||
|
`checkh(w, 230'h30);
|
||||||
|
|
||||||
i = qe.sum;
|
i = qe.sum;
|
||||||
`checkh(i, 32'h0);
|
`checkh(i, 32'h0);
|
||||||
i = qe.sum with (item + 1);
|
i = qe.sum with (item + 1);
|
||||||
|
|
@ -150,6 +191,12 @@ module t;
|
||||||
i = qe.product with (item + 1);
|
i = qe.product with (item + 1);
|
||||||
`checkh(i, 32'h0);
|
`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};
|
q = '{10: 32'b1100, 11: 32'b1010};
|
||||||
i = q.and;
|
i = q.and;
|
||||||
`checkh(i, 32'b1000);
|
`checkh(i, 32'b1000);
|
||||||
|
|
@ -164,6 +211,14 @@ module t;
|
||||||
i = q.xor with (item + 1);
|
i = q.xor with (item + 1);
|
||||||
`checkh(i, 32'b0110);
|
`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;
|
i = qe.and;
|
||||||
`checkh(i, 32'b0);
|
`checkh(i, 32'b0);
|
||||||
i = qe.and with (item + 1);
|
i = qe.and with (item + 1);
|
||||||
|
|
@ -177,6 +232,14 @@ module t;
|
||||||
i = qe.xor with (item + 1);
|
i = qe.xor with (item + 1);
|
||||||
`checkh(i, 32'b0);
|
`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();
|
i = q.and();
|
||||||
`checkh(i, 32'b1000);
|
`checkh(i, 32'b1000);
|
||||||
i = q.and() with (item + 1);
|
i = q.and() with (item + 1);
|
||||||
|
|
@ -190,6 +253,14 @@ module t;
|
||||||
i = q.xor() with (item + 1);
|
i = q.xor() with (item + 1);
|
||||||
`checkh(i, 32'b0110);
|
`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();
|
i = qe.and();
|
||||||
`checkh(i, 32'b0);
|
`checkh(i, 32'b0);
|
||||||
i = qe.or();
|
i = qe.or();
|
||||||
|
|
@ -197,6 +268,14 @@ module t;
|
||||||
i = qe.xor();
|
i = qe.xor();
|
||||||
`checkh(i, 32'b0);
|
`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};
|
q = '{10: 1, 11: 2};
|
||||||
qe = '{10: 1, 11: 2};
|
qe = '{10: 1, 11: 2};
|
||||||
`checkh(q == qe, 1'b1);
|
`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
|
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()
|
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.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()
|
test.passes()
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ import vltest_bootstrap
|
||||||
|
|
||||||
test.scenarios('simulator')
|
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()
|
test.execute()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,6 @@ test.compile(verilator_flags2=['--stats'])
|
||||||
|
|
||||||
test.execute()
|
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()
|
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