diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 772fc1c95..d9fea2352 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -28,6 +28,38 @@ concurrency: jobs: + build-2604-gcc: + name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} + uses: ./.github/workflows/reusable-build.yml + with: + sha: ${{ github.sha }} + os: ${{ matrix.os }} + os-name: linux + cc: ${{ matrix.cc }} + dev-asan: ${{ matrix.asan }} + dev-gcov: 0 + strategy: + fail-fast: false + matrix: + include: + - {os: ubuntu-26.04, cc: gcc, asan: 0} + + build-2604-clang: + name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} + uses: ./.github/workflows/reusable-build.yml + with: + sha: ${{ github.sha }} + os: ${{ matrix.os }} + os-name: linux + cc: ${{ matrix.cc }} + dev-asan: ${{ matrix.asan }} + dev-gcov: 0 + strategy: + fail-fast: false + matrix: + include: + - {os: ubuntu-26.04, cc: clang, asan: 1} + build-2404-gcc: name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} uses: ./.github/workflows/reusable-build.yml @@ -58,7 +90,7 @@ jobs: fail-fast: false matrix: include: - - {os: ubuntu-24.04, cc: clang, asan: 1} + - {os: ubuntu-24.04, cc: clang, asan: 0} build-2204-gcc: name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} @@ -76,22 +108,6 @@ jobs: include: - {os: ubuntu-22.04, cc: gcc, asan: 0} - build-2204-clang: - name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} - uses: ./.github/workflows/reusable-build.yml - with: - sha: ${{ github.sha }} - os: ${{ matrix.os }} - os-name: linux - cc: ${{ matrix.cc }} - dev-asan: ${{ matrix.asan }} - dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - - {os: ubuntu-22.04, cc: clang, asan: 0} - build-osx-gcc: name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} uses: ./.github/workflows/reusable-build.yml @@ -160,6 +176,54 @@ jobs: path: ${{ github.workspace }}/repo/verilator.zip name: verilator-win.zip + test-2604-gcc: + name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} + needs: build-2604-gcc + uses: ./.github/workflows/reusable-test.yml + with: + archive: ${{ needs.build-2604-gcc.outputs.archive }} + os: ${{ matrix.os }} + cc: ${{ matrix.cc }} + reloc: ${{ matrix.reloc }} + suite: ${{ matrix.suite }} + dev-gcov: 0 + strategy: + fail-fast: false + matrix: + include: + # Ubuntu 26.04 gcc + - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-0} + - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-1} + - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-2} + - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-3} + - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-0} + - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-1} + - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-2} + + test-2604-clang: + name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} + needs: build-2604-clang + uses: ./.github/workflows/reusable-test.yml + with: + archive: ${{ needs.build-2604-clang.outputs.archive }} + os: ${{ matrix.os }} + cc: ${{ matrix.cc }} + reloc: ${{ matrix.reloc }} + suite: ${{ matrix.suite }} + dev-gcov: 0 + strategy: + fail-fast: false + matrix: + include: + # Ubuntu 26.04 clang + - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-0} + - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-1} + - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-2} + - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-3} + - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-0} + - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-1} + - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-2} + test-2404-gcc: name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} needs: build-2404-gcc @@ -232,30 +296,6 @@ jobs: - {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-1} - {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-2} - test-2204-clang: - name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} - needs: build-2204-clang - uses: ./.github/workflows/reusable-test.yml - with: - archive: ${{ needs.build-2204-clang.outputs.archive }} - os: ${{ matrix.os }} - cc: ${{ matrix.cc }} - reloc: ${{ matrix.reloc }} - suite: ${{ matrix.suite }} - dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - # Ubuntu 22.04 clang, also test relocation - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-0} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-1} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-2} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-3} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: vltmt-0} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: vltmt-1} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: vltmt-2} - lint-py: name: Lint Python uses: ./.github/workflows/reusable-lint-py.yml @@ -264,17 +304,19 @@ jobs: name: Test suite passed if: always() needs: + - build-2604-gcc + - build-2604-clang - build-2404-gcc - build-2404-clang - build-2204-gcc - - build-2204-clang - build-osx-gcc - build-osx-clang - build-windows + - build-2404-gcc + - build-2404-clang - test-2404-gcc - test-2404-clang - test-2204-gcc - - test-2204-clang - lint-py runs-on: ubuntu-24.04 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 439143c85..1601a67d7 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -90,7 +90,7 @@ jobs: find obj_coverage -type f | paste -sd, | sed "s/^/files=/" >> "$GITHUB_OUTPUT" - name: Upload to codecov.io - uses: codecov/codecov-action@v6 + uses: codecov/codecov-action@v7 with: disable_file_fixes: true disable_search: true @@ -197,7 +197,7 @@ jobs: id: generate-token uses: actions/create-github-app-token@v3.2.0 with: - app-id: ${{ vars.VERILATOR_CI_ID }} + client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} owner: verilator repositories: verilator diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 4d1523ac2..3718abc11 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -76,7 +76,7 @@ jobs: id: generate-token uses: actions/create-github-app-token@v3.2.0 with: - app-id: ${{ vars.VERILATOR_CI_ID }} + client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} permission-actions: write permission-pull-requests: write diff --git a/.github/workflows/rtlmeter.yml b/.github/workflows/rtlmeter.yml index d2880b476..b36646698 100644 --- a/.github/workflows/rtlmeter.yml +++ b/.github/workflows/rtlmeter.yml @@ -292,7 +292,7 @@ jobs: id: generate-token uses: actions/create-github-app-token@v3.2.0 with: - app-id: ${{ vars.VERILATOR_CI_ID }} + client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} owner: verilator repositories: verilator-rtlmeter-results @@ -394,7 +394,7 @@ jobs: id: generate-token uses: actions/create-github-app-token@v3.2.0 with: - app-id: ${{ vars.VERILATOR_CI_ID }} + client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} owner: verilator repositories: verilator diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..2f5f06eda --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,142 @@ + + +# 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_.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. diff --git a/Changes b/Changes index fcb769eae..afe5731e2 100644 --- a/Changes +++ b/Changes @@ -15,7 +15,7 @@ Verilator 5.049 devel **Important:** -* Support covergroups, coverpoints, and bins (#784) (#7117). [Matthew Ballance] +* Support covergroups, coverpoints, and bins (#784) (#7117) (#7728). [Matthew Ballance] * Support new FST writer API (#6871) (#6992). [Yu-Sheng Lin] Use of FST may requiring installing liblz4 and/or liblz4-dev packages, see docs/install.rst. @@ -27,9 +27,11 @@ Verilator 5.049 devel * Add `--coverage-per-instance` (#7636). [Yogish Sekhar] * Add NOTREDOP error on reduction and negation operators (#7417) (#7623) (#7624). * Add hierarchy-aware reporting to `verilator_coverage` (#7657). [Yogish Sekhar] +* Deprecate isolate_assignments attribute (#7774) (#7144). [Geza Lore, Testorrent USA, Inc.] * Improve `--coverage-fsm` (#7490) (#7529) (#7561) (#7573) (#7619). [Yogish Sekhar] * Change `+verilator+seed` to default to 1, and 0 to randomly select (#7325) (#7516). [Miguel] * Change JSON to include parameter constant mnemonics for FSM Coverage (#7531). [Yogish Sekhar] +* Support assert property 'default disable iff` (#4848) (#7723). [Artur Bieniek, Antmicro Ltd.] * Support printing enum names for %p and %s (#5523) (#7338 repair) (#7521) (#7527). [Nick Brereton] * Support weak `until` / `until_with` property operators (#7290) (#7548) (#7685). [Yilou Wang] * Support `s_eventually` (#7291) (#7508). [Bartłomiej Chmiel, Antmicro Ltd.] @@ -59,6 +61,14 @@ Verilator 5.049 devel * Support if/if-else in properties (#7692). [Artur Bieniek, Antmicro Ltd.] * Support process::self().srand() (#7695). [Igor Zaworski, Antmicro Ltd.] * Support MacOS lldb (#7697). [Tracy Narine] +* Support assoc array methods with wide value types (#7680). [pawelktk] +* Support property case (#7721). [Artur Bieniek, Antmicro Ltd.] +* Support `s_until` and `s_until_with`(#7722). [Artur Bieniek, Antmicro Ltd.] +* Support covergroup runtime model Phase A1 (#7728). [Matthew Ballance] +* Support reduction XOR/AND operations in constraints (#7753). [Kornel Uriasz, Antmicro Ltd.] +* Support assertion control system tasks in classes and interfaces (#7761). [Yilou Wang] +* Support cover sequence statement (#7764). [Yilou Wang] +* Support unpacked struct stream (#7767). [Nick Brereton] * Optimize emitting to_string() for compiler speedup (#7468). [Jakub Michalski, Antmicro Ltd.] * Optimize additional DFG peephole cases (#7553). [Varun Koyyalagunta, Testorrent USA, Inc.] * Optimize forced signal handling (#7554 partial) (#7572) (#7594) (#7596). [Krzysztof Bieganski, Artur Bieniek, Antmicro Ltd.] @@ -71,8 +81,17 @@ Verilator 5.049 devel * Optimize runtime assertOn() checks (#7707). [Geza Lore, Testorrent USA, Inc.] * Optimize $countones and $onehot in DFG. [Geza Lore, Testorrent USA, Inc.] * Optimize procedural loop unrolling. [Geza Lore, Testorrent USA, Inc.] +* Optimize V3Gate inlining heuristic (#7716). [Geza Lore, Testorrent USA, Inc.] +* Optimize reset in DFG (#7737). [Geza Lore, Testorrent USA, Inc.] +* Optimize DFG with relaxed live variable analysis (#7739). [Geza Lore, Testorrent USA, Inc.] +* Optimize conditional patterns sharing common MBSs/LSBs in DfgPeephole (#7760). [Geza Lore, Testorrent USA, Inc.] +* Optimize bit select removal earlier in DFG (#7762). [Geza Lore, Testorrent USA, Inc.] +* Optimize away proven redundant case statement assertions (#7771). [Geza Lore, Testorrent USA, Inc.] +* Optimize table lookups in DFG (#7772). [Geza Lore, Testorrent USA, Inc.] +* Optimize input combinational logic by change detection (#7784). [Geza Lore, Testorrent USA, Inc.] * Fix TSP variable ordering for mtasks (#5342) (#7610). [Muzaffer Kal] * Fix inlining static initializer in V3Gate (#5381) (#7503). [Andrew Nolte] [Geza Lore, Testorrent USA, Inc.] +* Fix timed nested fork block with disable (#6720) (#7743). [Marco Bartoli] * Fix segmentation fault when using --trace with --lib-create (#7299) (#7518). [anonkey] * Fix destructive event state before dynamic waits (#7340). [Nick Brereton] * Fix ALWCOMBORDER on variable ordering (#7350) (#7608). [Cookie] @@ -128,6 +147,7 @@ Verilator 5.049 devel * Fix loss of events due to bit shift (#7670). [Artur Bieniek, Antmicro Ltd.] * Fix parameter read through locally-declared interface instance (#7679). [Nick Brereton] * Fix skipping nulls in $sscanf (#7689). +* Fix bounds checks in expressions with read/write references (#7694). [Ryszard Rozak, Antmicro Ltd.] * Fix (const) ref default task argument handling (#7698). [Nick Brereton] * Fix `ref` argument type check for packed arrays with differing range directions (#7700). [Nick Brereton] * Fix ignoring not-found modules with encoded names (#7706). [Igor Zaworski, Antmicro Ltd.] @@ -135,6 +155,17 @@ Verilator 5.049 devel * Fix Makefile action to not write to ${srcdir} (#7715). [Larry Doolittle] * Fix splitting functions containing fork logic (#7717). [Mateusz Gancarz, Antmicro Ltd.] * Fix optimizations of assignments with timing controls (#7718). [Ryszard Rozak, Antmicro Ltd.] +* Fix s_eventually on interface (#7731) (#7733). [Marco Bartoli] +* Fix parameter values in coverage bins widths (#7732) (#7734). [Marco Bartoli] +* Fix configure fall back on dynamic malloc libraries (#7736). [Geza Lore, Testorrent USA, Inc.] +* Fix crash on overlapping priority case. [Geza Lore, Testorrent USA, Inc.] +* Fix s_eventually in parameterized interfaces (#7741). [Nick Brereton] +* Fix dpi export pointers (#7742) (#7751). [Yilin Li] +* Fix FSM detect unchecked casts and variable redeclaration (#7758). [Adam Kostrzewski, Antmicro Ltd.] +* Fix no-scope internal error on virtual interface method calls (#7759). [Yilou Wang] +* Fix 'case (_) inside' with x wildcards (#7766). [Geza Lore, Testorrent USA, Inc.] +* Fix not failing assertion when RHS of a range window rejects once (#7773). [Artur Bieniek, Antmicro Ltd.] +* Fix $fflush and autoflush with --threads (#7782). Verilator 5.048 2026-04-26 diff --git a/Makefile.in b/Makefile.in index c2b60d004..6bcbdbaa5 100644 --- a/Makefile.in +++ b/Makefile.in @@ -495,6 +495,11 @@ MAKE_FILES = \ src/Makefile*.in \ test_regress/Makefile* \ +# Markdown +MD_FILES = \ + *.md \ + */*.md \ + # Perl programs PERL_PROGRAMS = \ bin/redirect \ @@ -553,7 +558,7 @@ YAML_FILES = \ # Format format: - $(MAKE) -j 5 format-c format-cmake format-exec format-py format-yaml + $(MAKE) -j 5 format-c format-cmake format-exec format-md format-py format-yaml BEAUTYSH = beautysh BEAUTYSH_FLAGS = --indent-size 2 @@ -590,6 +595,13 @@ format-make mbake: $(MBAKE) --version $(MBAKE) $(MBAKE_FLAGS) $(MAKE_FILES) +MDFORMAT = mdformat +MDFORMAT_FLAGS = + +format-md: + $(MDFORMAT) --version + $(MDFORMAT) $(MDFORMAT_FLAGS) $(MD_FILES) + YAPF = yapf YAPF_FLAGS = -i --parallel diff --git a/docs/AGENTS.md b/docs/AGENTS.md new file mode 100644 index 000000000..1c0524c78 --- /dev/null +++ b/docs/AGENTS.md @@ -0,0 +1,38 @@ + + +# 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. diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index 880b5608a..877011165 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -9,6 +9,7 @@ Please see the Verilator manual for 200+ additional contributors. Thanks to all. 404allen404 Adam Bagley +Adam Kostrzewski Adrian Sampson Adrien Le Masle أحمد المحمودي (Ahmed El-Mahmoudy) @@ -158,6 +159,7 @@ Kefa Chen Keith Colbert Kevin Kiningham Kevin Nygaard +Kornel Uriasz Kritik Bhimani Krzysztof Bieganski Krzysztof Boronski @@ -325,3 +327,5 @@ Yogish Sekhar 24bit-xjkp Zubin Jain Muzaffer Kal +Yilin Li +Shashvat Prabhu diff --git a/docs/guide/control.rst b/docs/guide/control.rst index a01396040..c26159d56 100644 --- a/docs/guide/control.rst +++ b/docs/guide/control.rst @@ -187,6 +187,10 @@ The grammar of control commands is as follows: .. option:: isolate_assignments -module "" [-task ""] -var "" + Deprecated and has no effect (ignored). + + In versions before 5.050: + Used to indicate that the assignments to this signal in any blocks should be isolated into new blocks. Same as :option:`/*verilator&32;isolate_assignments*/` metacomment. diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 705bf3f71..29e3876c4 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -737,6 +737,21 @@ Summary: this is not recommended as may cause additional warnings and ordering issues. +.. option:: -fno-ico-change-detect + + Rarely needed. Disable input change detection in the input combinational + ('ico') region. With change detection enabled (the default, unless + :vlopt:`--vpi` is passed), the input combinational logic is evaluated only + when a top level input has actually changed, rather than unconditionally on + the first scheduling iteration. + + The change detection logic assumes a top level input only ever changes + externally between evaluations. The optimization is automatically disabled + for top level input signals that are written within the design. Accesses via + the VPI cannot be analyzed at compile time, therefore :vlopt:`--vpi` + disables this optimization for all inputs; it may be turned back on by + explicitly passing :vlopt:`-fico-change-detect <-fno-ico-change-detect>`. + .. option:: -fno-inline .. option:: -fno-inline-funcs diff --git a/docs/guide/extensions.rst b/docs/guide/extensions.rst index 0e944860e..4ea636af0 100644 --- a/docs/guide/extensions.rst +++ b/docs/guide/extensions.rst @@ -341,6 +341,10 @@ or "`ifdef`"'s may break other tools. .. option:: /*verilator&32;isolate_assignments*/ + Deprecated and has no effect (ignored). + + In versions before 5.050: + Used after a signal declaration to indicate the assignments to this signal in any blocks should be isolated into new blocks. When large combinatorial block results in a :option:`UNOPTFLAT` warning, attaching diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index d1818afd5..db41d8405 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -2366,11 +2366,6 @@ List Of Warnings the conflict. If you run with :vlopt:`--report-unoptflat`, Verilator will suggest possible candidates for :option:`/*verilator&32;split_var*/`. - The UNOPTFLAT warning may also occur where outputs from a block of logic - are independent, but occur in the same always block. To fix this, use - the :option:`/*verilator&32;isolate_assignments*/` metacomment described - above. - Before version 5.000, the UNOPTFLAT warning may also have been due to clock enables, identified from the reported path going through a clock gating instance. To fix these, the clock_enable meta comment was used. diff --git a/docs/spelling.txt b/docs/spelling.txt index a3565cb1d..a21f66ed5 100644 --- a/docs/spelling.txt +++ b/docs/spelling.txt @@ -861,6 +861,7 @@ hx hyperthreading hyperthreads icecream +ico idmap ifdef ifdefed @@ -924,6 +925,7 @@ localparams localtime logicals longint +lookups lossy lsb lubc diff --git a/include/AGENTS.md b/include/AGENTS.md new file mode 100644 index 000000000..0ac1fcbfe --- /dev/null +++ b/include/AGENTS.md @@ -0,0 +1,60 @@ + + +# 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` | diff --git a/include/verilated.cpp b/include/verilated.cpp index 6bf868cad..a7d78f3bd 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -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 static size_t _vl_snprintf_string(std::string& str, const char* format, snprintf_args_ts... args) VL_MT_SAFE { diff --git a/include/verilated_cov_model.h b/include/verilated_cov_model.h new file mode 100644 index 000000000..519fd214c --- /dev/null +++ b/include/verilated_cov_model.h @@ -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 +#include + +// 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 +// 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 diff --git a/include/verilated_covergroup.cpp b/include/verilated_covergroup.cpp new file mode 100644 index 000000000..6dc7006f1 --- /dev/null +++ b/include/verilated_covergroup.cpp @@ -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); + } + } +} diff --git a/include/verilated_covergroup.h b/include/verilated_covergroup.h new file mode 100644 index 000000000..1ffc98ff7 --- /dev/null +++ b/include/verilated_covergroup.h @@ -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 +#include +#include + +class VerilatedCovContext; + +// How a namer builds the names of the bins it covers. +enum class VlCovBinNaming : uint8_t { + Single, // "" one bin + Array, // "[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 m_counts; // [m_total], one per bin + std::vector 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 diff --git a/include/verilated_funcs.h b/include/verilated_funcs.h index db4c39e5c..8aebd1e34 100644 --- a/include/verilated_funcs.h +++ b/include/verilated_funcs.h @@ -74,10 +74,8 @@ extern void VL_FATAL_MT(const char* filename, int linenum, const char* hier, extern void VL_WARN_MT(const char* filename, int linenum, const char* hier, const char* msg) VL_MT_SAFE; -// clang-format off /// Print a string, multithread safe. Eventually VL_PRINTF will get called. extern void VL_PRINTF_MT(const char* formatp, ...) VL_ATTR_PRINTF(1) VL_MT_SAFE; -// clang-format on /// Print a debug message from internals with standard prefix, with printf style format extern void VL_DBG_MSGF(const char* formatp, ...) VL_ATTR_PRINTF(1) VL_MT_SAFE; @@ -85,6 +83,9 @@ extern void VL_DBG_MSGF(const char* formatp, ...) VL_ATTR_PRINTF(1) VL_MT_SAFE; /// Print a debug message from string via VL_DBG_MSGF inline void VL_DBG_MSGS(const std::string& str) VL_MT_SAFE { VL_DBG_MSGF("%s", str.c_str()); } +/// Flush stdout +extern void VL_FFLUSH_MT() VL_MT_SAFE; + // EMIT_RULE: VL_RANDOM: oclean=dirty inline IData VL_RANDOM_I() VL_MT_SAFE { return vl_rand64(); } inline QData VL_RANDOM_Q() VL_MT_SAFE { return vl_rand64(); } diff --git a/include/verilated_types.h b/include/verilated_types.h index c2502bcdf..40847b65e 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -97,6 +97,28 @@ struct VlWide final { bool operator!=(const VlWide& that) const VL_PURE { return !(*this == that); } EData& operator[](size_t index) VL_MT_SAFE { return m_storage[index]; } const EData& operator[](size_t index) const VL_MT_SAFE { return m_storage[index]; } + VlWide& operator&=(const VlWide& rhs) { + VL_AND_W(N_Words, *this, *this, rhs); + return *this; + } + VlWide& operator|=(const VlWide& rhs) { + VL_OR_W(N_Words, *this, *this, rhs); + return *this; + } + VlWide& operator^=(const VlWide& rhs) { + VL_XOR_W(N_Words, *this, *this, rhs); + return *this; + } + VlWide& operator+=(const VlWide& rhs) { + VL_ADD_W(N_Words, *this, *this, rhs); + return *this; + } + VlWide& operator*=(const VlWide& rhs) { + VlWide out{}; + VL_MUL_W(N_Words, out, *this, rhs); + for (size_t i = 0; i < N_Words; ++i) m_storage[i] = out.m_storage[i]; + return *this; + } // METHODS EData& at(size_t index) VL_MT_SAFE { return m_storage[index]; } @@ -1280,7 +1302,7 @@ public: } T_Value r_sum() const { - T_Value out(0); // Type must have assignment operator + T_Value out = T_Value{}; for (const auto& i : m_map) out += i.second; return out; } @@ -1291,8 +1313,9 @@ public: return out; } T_Value r_product() const { - if (m_map.empty()) return T_Value(0); // The big three do it this way - T_Value out = T_Value(1); + // The big three return 0 when assoc array is empty + if (m_map.empty()) return T_Value{}; + T_Value out = T_Value{1}; for (const auto& i : m_map) out *= i.second; return out; } @@ -1304,8 +1327,9 @@ public: return out; } T_Value r_and() const { - if (m_map.empty()) return T_Value(0); // The big three do it this way - T_Value out = ~T_Value(0); + // The big three return 0 when assoc array is empty + if (m_map.empty()) return T_Value{}; + T_Value out = m_map.cbegin()->second; for (const auto& i : m_map) out &= i.second; return out; } @@ -1317,7 +1341,7 @@ public: return out; } T_Value r_or() const { - T_Value out = T_Value(0); + T_Value out = T_Value{}; for (const auto& i : m_map) out |= i.second; return out; } @@ -1328,7 +1352,7 @@ public: return out; } T_Value r_xor() const { - T_Value out = T_Value(0); + T_Value out = T_Value{}; for (const auto& i : m_map) out ^= i.second; return out; } diff --git a/python-dev-requirements.txt b/python-dev-requirements.txt index 11f9fad84..d6bcba7b1 100644 --- a/python-dev-requirements.txt +++ b/python-dev-requirements.txt @@ -21,6 +21,7 @@ compiledb==0.10.7 distro==1.9.0 gersemi==0.23.1 mbake==1.4.3 +mdformat==1.0.0 mypy==1.19.0 pylint==3.0.2 ruff==0.14.8 diff --git a/src/AGENTS.md b/src/AGENTS.md new file mode 100644 index 000000000..cb53dc31c --- /dev/null +++ b/src/AGENTS.md @@ -0,0 +1,241 @@ + + +# 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` 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 `` 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 | diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ad2178203..ba8f6a7b7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -175,7 +175,6 @@ set(HEADERS V3Simulate.h V3Slice.h V3Split.h - V3SplitAs.h V3SplitVar.h V3StackCount.h V3Stats.h @@ -183,7 +182,6 @@ set(HEADERS V3String.h V3Subst.h V3SymTable.h - V3TSP.h V3Table.h V3Task.h V3ThreadPool.h @@ -351,14 +349,12 @@ set(COMMON_SOURCES V3Scoreboard.cpp V3Slice.cpp V3Split.cpp - V3SplitAs.cpp V3SplitVar.cpp V3StackCount.cpp V3Stats.cpp V3StatsReport.cpp V3String.cpp V3Subst.cpp - V3TSP.cpp V3Table.cpp V3Task.cpp V3ThreadPool.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index 08c8899fc..eb3b1d50a 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -333,11 +333,9 @@ RAW_OBJS_PCH_ASTNOMT = \ V3Scoreboard.o \ V3Slice.o \ V3Split.o \ - V3SplitAs.o \ V3SplitVar.o \ V3StackCount.o \ V3Subst.o \ - V3TSP.o \ V3Table.o \ V3Task.o \ V3Timing.o \ diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index 0ccf162fa..47c1874f3 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -23,6 +23,79 @@ VL_DEFINE_DEBUG_FUNCTIONS; +namespace { + +class DefaultDisableLocalVisitor final : public VNVisitor { + // STATE + AstNode* m_scopep = nullptr; + + // VISITORS + void visit(AstNodeModule* nodep) override { + VL_RESTORER(m_scopep); + m_scopep = nodep; + nodep->defaultDisablep(nullptr); + iterateChildren(nodep); + } + void visit(AstGenBlock* nodep) override { + VL_RESTORER(m_scopep); + m_scopep = nodep; + nodep->defaultDisablep(nullptr); + iterateChildren(nodep); + } + void visit(AstDefaultDisable* nodep) override { + UASSERT_OBJ(nodep, m_scopep, + "default disable iff must be inside a module or generate block"); + AstDefaultDisable* defaultp = nullptr; + if (const AstNodeModule* const modp = VN_CAST(m_scopep, NodeModule)) { + defaultp = modp->defaultDisablep(); + } else { + defaultp = VN_AS(m_scopep, GenBlock)->defaultDisablep(); + } + if (VL_UNLIKELY(defaultp)) { + nodep->v3error("Only one 'default disable iff' allowed per " + << (VN_IS(m_scopep, NodeModule) ? "module" : "generate block") + << " (IEEE 1800-2023 16.15)"); + } else if (AstNodeModule* const modp = VN_CAST(m_scopep, NodeModule)) { + modp->defaultDisablep(nodep); + } else { + VN_AS(m_scopep, GenBlock)->defaultDisablep(nodep); + } + } + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + explicit DefaultDisableLocalVisitor(AstNetlist* nodep) { iterate(nodep); } +}; + +class DefaultDisablePropagateVisitor final : public VNVisitor { + // STATE + AstDefaultDisable* m_defaultDisablep = nullptr; + + // VISITORS + void visit(AstNodeModule* nodep) override { + VL_RESTORER(m_defaultDisablep); + m_defaultDisablep = nodep->defaultDisablep(); + iterateChildren(nodep); + } + void visit(AstGenBlock* nodep) override { + VL_RESTORER(m_defaultDisablep); + if (!nodep->defaultDisablep()) nodep->defaultDisablep(m_defaultDisablep); + m_defaultDisablep = nodep->defaultDisablep(); + iterateChildren(nodep); + } + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + explicit DefaultDisablePropagateVisitor(AstNetlist* nodep) { iterate(nodep); } +}; + +} // namespace + +void V3AssertCommon::collectDefaultDisable(AstNetlist* nodep) { + { DefaultDisableLocalVisitor{nodep}; } + { DefaultDisablePropagateVisitor{nodep}; } +} + //###################################################################### // AssertDeFutureVisitor // If any AstFuture, then move all non-future varrefs to be one cycle behind, @@ -586,6 +659,34 @@ class AssertVisitor final : public VNVisitor { iterateChildren(nodep); } + if (nodep->user2()) { + // Combine consecutive assertOn checks if possible + if (AstIf* const backp = VN_CAST(nodep->backp(), If)) { + if (backp->nextp() == nodep // + && backp->user2() // + && backp->condp()->sameTree(nodep->condp())) { + ++m_statAssertOnCombined; + backp->addThensp(nodep->thensp()->unlinkFrBackWithNext()); + nodep->unlinkFrBack(); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } + } + // Combine nested assertOn checks if possible + if (nodep->thensp() && !nodep->thensp()->nextp() && isEmptyStmt(nodep->elsesp())) { + AstIf* const checkp = VN_CAST(nodep->thensp(), If); + if (checkp // + && checkp->user2() // + && checkp->condp()->sameTree(nodep->condp())) { + ++m_statAssertOnCombined; + nodep->addThensp(checkp->thensp()->unlinkFrBackWithNext()); + VL_DO_DANGLING(pushDeletep(checkp->unlinkFrBack()), checkp); + return; + } + } + return; + } + // Swap assertOn check with single statement 'if' statement to bubble up for combining // Note we can't just swap the conditions as they two Ifs have different flags, // so swapping the Ifs themeselves then swapping back the bodies. @@ -611,20 +712,6 @@ class AssertVisitor final : public VNVisitor { } } } - - // Combine consecutive assertOn checks if possible - if (nodep->user2()) { - if (AstIf* const backp = VN_CAST(nodep->backp(), If)) { - if (backp->nextp() == nodep // - && backp->user2() // - && backp->condp()->sameTree(nodep->condp())) { - ++m_statAssertOnCombined; - backp->addThensp(nodep->thensp()->unlinkFrBackWithNext()); - nodep->unlinkFrBack(); - VL_DO_DANGLING(pushDeletep(nodep), nodep); - } - } - } } //========== Case assertions @@ -914,12 +1001,6 @@ class AssertVisitor final : public VNVisitor { visitAssertionIterate(nodep, nodep->failsp()); } void visit(AstAssertCtl* nodep) override { - if (VN_IS(m_modp, Class) || VN_IS(m_modp, Iface)) { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: assertcontrols in classes or interfaces"); - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); - return; - } - iterateChildren(nodep); if (!resolveAssertType(nodep)) { @@ -1007,10 +1088,12 @@ class AssertVisitor final : public VNVisitor { VL_RESTORER(m_modPastNum); VL_RESTORER(m_modStrobeNum); VL_RESTORER(m_modExpr2Sen2DelayedAlwaysp); + VL_RESTORER(m_finalp); m_modp = nodep; m_modPastNum = 0; m_modStrobeNum = 0; m_modExpr2Sen2DelayedAlwaysp.clear(); + m_finalp = nullptr; iterateChildren(nodep); } void visit(AstNodeProcedure* nodep) override { diff --git a/src/V3Assert.h b/src/V3Assert.h index dbaac13bf..f144688b6 100644 --- a/src/V3Assert.h +++ b/src/V3Assert.h @@ -24,6 +24,11 @@ //============================================================================ +class V3AssertCommon final { +public: + static void collectDefaultDisable(AstNetlist* nodep) VL_MT_DISABLED; +}; + class V3Assert final { public: static void assertAll(AstNetlist* nodep) VL_MT_DISABLED; diff --git a/src/V3AssertNfa.cpp b/src/V3AssertNfa.cpp index bf50e97a1..a7f2a8c1b 100644 --- a/src/V3AssertNfa.cpp +++ b/src/V3AssertNfa.cpp @@ -26,6 +26,7 @@ #include "V3AssertNfa.h" +#include "V3Assert.h" #include "V3Const.h" #include "V3Graph.h" #include "V3Task.h" @@ -109,6 +110,9 @@ public: // Reject when source is active and condp is false; set only on // outermost required-step Link bool m_rejectOnFail = false; + // Optional dynamic condition vertex for m_rejectOnFail. Used when the + // success condition is another NFA state rather than a static expression. + SvaStateVertex* m_condVtxp = nullptr; // CONSTRUCTORS SvaTransEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, AstNodeExpr* condp, @@ -176,6 +180,10 @@ struct BuildResult final { // Mid-window sources for range delays (pure boolean RHS): match-only (isUnbounded) std::vector midSources; bool errorEmitted = false; // Builder already emitted specific error; skip generic + // For cover_sequence: when true, midSources already enumerate every + // end-of-match, so wireMatchAndMidSources must NOT add the main + // termVtxp -> matchVertex Link (would double-count via the merge vertex). + bool termIsMidMerge = false; bool valid() const { return termVertexp != nullptr; } static BuildResult fail(bool errored = false) { return {nullptr, nullptr, {}, errored}; } static BuildResult failWithError() { return {nullptr, nullptr, {}, true}; } @@ -198,6 +206,16 @@ class SvaNfaBuilder final { // (IEEE 1800-2023 16.12.14 outer-wraps-inner). std::vector m_outerAbortStack; bool m_inUnboundedScope = false; // Sticky: nodes created after inherit liveness + // IEEE 1800-2023 16.14.3 cover sequence: each end-of-match fires the action, + // not just the first. Builder builds parallel-branch (no first-match-wins) + // topology when true. Default false preserves cover_property semantics. + bool m_isCoverSeq = false; + + struct RangeDelayRejectInfo final { + SvaStateVertex* startp = nullptr; + int range = 0; + int rhsLen = 0; + }; AstNodeExpr* throughoutCond(AstNodeExpr* baseCondp, FileLine* flp) { if (m_throughoutStack.empty()) return baseCondp; @@ -365,7 +383,7 @@ class SvaNfaBuilder final { // failure, sets outErrorEmitted per semantic-error policy and returns false. bool applyRangeDelay(AstDelay* delayp, AstNodeExpr* rhsExprp, SvaStateVertex*& currentp, std::vector& midSources, FileLine* flp, - bool& outErrorEmitted) { + bool& outErrorEmitted, RangeDelayRejectInfo* rangeRejectInfop = nullptr) { const int minDelay = getConstInt(delayp->lhsp()); if (minDelay < 0) { delayp->v3error("Range delay minimum is not a non-negative elaboration-time constant" @@ -405,6 +423,16 @@ class SvaNfaBuilder final { // count is O(1) in range regardless of user input; no adversarial N // blowup is possible. constexpr int kChainLimit = 256; + // IEEE 1800-2023 16.14.3: only a small bounded range before a plain + // boolean enumerates every end-of-match below. The counter FSM drops + // overlapping ends and the nested-sequence merge collapses them, so + // reject those for a cover sequence rather than under-count. + if (m_isCoverSeq && (range > kChainLimit || VN_IS(rhsExprp, SExpr))) { + flp->v3warn(COVERIGN, + "Ignoring unsupported: cover sequence with this ranged cycle delay"); + outErrorEmitted = true; + return false; + } if (range > kChainLimit) { // Large range: counter FSM. Overlapping triggers during an active // count are dropped (non-overlapping semantics only). @@ -414,8 +442,14 @@ class SvaNfaBuilder final { guardedEdge(currentp, counterVtxp, flp); currentp = counterVtxp; } else if (VN_IS(rhsExprp, SExpr)) { - // Nested-SExpr RHS: merge all [M,N] positions; continuation is per-attempt. + // Nested-SExpr RHS: merge all [M,N] positions. Candidate-local misses + // are not assertion rejects while a later position can still match. + if (rangeRejectInfop) { + const int rhsLen = fixedLength(rhsExprp); + if (rhsLen >= 0) *rangeRejectInfop = {currentp, range, rhsLen}; + } SvaStateVertex* const mergeVtxp = scopedCreateVertex(); + mergeVtxp->m_isUnbounded = true; guardedLink(currentp, mergeVtxp, flp); for (int i = 0; i < range; ++i) { SvaStateVertex* const nextVtxp = scopedCreateVertex(); @@ -424,16 +458,25 @@ class SvaNfaBuilder final { currentp = nextVtxp; } currentp = mergeVtxp; + m_inUnboundedScope = true; } else { // Pure boolean RHS: register chain. Each mid-position links to // match (match-only); last position is the reject source. - AstVar* const hoistVarp = tryHoistSampled(rhsExprp, flp, range); + // For cover_sequence (IEEE 1800-2023 16.14.3) the advance edge is + // unconditional so every (start, end) pair fires independently -- + // dropping NOT(b) turns "first-match-wins" into "every end fires". + AstVar* const hoistVarp + = m_isCoverSeq ? nullptr : tryHoistSampled(rhsExprp, flp, range); midSources.push_back(currentp); for (int i = 0; i < range; ++i) { SvaStateVertex* const nextVtxp = scopedCreateVertex(); - AstNodeExpr* const notExprp - = new AstLogNot{flp, sampledRefOrClone(hoistVarp, rhsExprp, flp)}; - guardedEdge(currentp, nextVtxp, notExprp, flp); + if (m_isCoverSeq) { + guardedEdge(currentp, nextVtxp, flp); + } else { + AstNodeExpr* const notExprp + = new AstLogNot{flp, sampledRefOrClone(hoistVarp, rhsExprp, flp)}; + guardedEdge(currentp, nextVtxp, notExprp, flp); + } if (i < range - 1) midSources.push_back(nextVtxp); currentp = nextVtxp; } @@ -441,12 +484,44 @@ class SvaNfaBuilder final { return true; } + void addFiniteRangeReject(const RangeDelayRejectInfo& info, const BuildResult& result, + FileLine* flp) { + if (!info.startp) return; + + SvaStateVertex* const expiryVtxp + = addDelayChain(info.startp, info.range + info.rhsLen, flp); + SvaStateVertex* const expiryMatchp = scopedCreateVertex(); + std::vector sources = result.midSources; + sources.push_back(result.termVertexp); + for (SvaStateVertex* const srcp : sources) { + AstNodeExpr* const condp + = result.finalCondp ? sampled(result.finalCondp->cloneTreePure(false)) : nullptr; + SvaStateVertex* const successNowp = scopedCreateVertex(); + guardedLink(srcp, successNowp, condp, flp); + SvaStateVertex* stagep = successNowp; + guardedLink(stagep, expiryMatchp, flp); + for (int i = 0; i < info.range; ++i) { + SvaStateVertex* const nextp = scopedCreateVertex(); + guardedEdge(stagep, nextp, flp); + stagep = nextp; + guardedLink(stagep, expiryMatchp, flp); + } + } + + SvaStateVertex* const sinkVtxp = m_graph.createStateVertex(); + sinkVtxp->m_isRejectSink = true; + SvaTransEdge* const rejectp = m_graph.addLink(expiryVtxp, sinkVtxp); + rejectp->m_rejectOnFail = true; + rejectp->m_condVtxp = expiryMatchp; + } + BuildResult buildSExpr(AstSExpr* sexprp, SvaStateVertex* entryVtxp, bool isTopLevelStep = false) { AstDelay* const delayp = VN_CAST(sexprp->delayp(), Delay); if (!delayp || !delayp->isCycleDelay()) return BuildResult::fail(); FileLine* const flp = sexprp->fileline(); + AstNodeExpr* const exprp = sexprp->exprp(); // Handle LHS (preExpr) SvaStateVertex* currentp = entryVtxp; @@ -469,10 +544,12 @@ class SvaNfaBuilder final { // Handle delay std::vector rangeMidSources; + RangeDelayRejectInfo rangeRejectInfo; + const bool addRangeReject = isTopLevelStep && !m_inUnboundedScope; if (delayp->isRangeDelay()) { bool errorEmitted = false; if (!applyRangeDelay(delayp, sexprp->exprp(), currentp, rangeMidSources, flp, - errorEmitted)) { + errorEmitted, addRangeReject ? &rangeRejectInfo : nullptr)) { return BuildResult::fail(errorEmitted); } } else { @@ -487,8 +564,11 @@ class SvaNfaBuilder final { } // Multi-cycle RHS: recurse (only plain boolean is returned as finalCondp). - AstNodeExpr* const exprp = sexprp->exprp(); - if (exprp->isMultiCycleSva()) return buildExpr(exprp, currentp, isTopLevelStep); + if (exprp->isMultiCycleSva()) { + const BuildResult result = buildExpr(exprp, currentp, isTopLevelStep); + if (result.valid()) addFiniteRangeReject(rangeRejectInfo, result, flp); + return result; + } return {currentp, exprp, std::move(rangeMidSources)}; } @@ -516,6 +596,10 @@ class SvaNfaBuilder final { if (exceedsAssertUnrollLimit(repp, totalSites)) return BuildResult::failWithError(); AstVar* const hoistVarp = tryHoistSampled(exprp, flp, totalSites); + // Cover-sequence (IEEE 1800-2023 16.14.3): collect each end-of-match + // position so they all fire the action, not just the merged terminal. + std::vector consMidSources; + SvaStateVertex* currentp = entryVtxp; for (int i = 0; i < minN; ++i) { if (i > 0) { @@ -531,6 +615,10 @@ class SvaNfaBuilder final { if (isTopLevelStep && (i == 0 || i == minN - 1)) { linkp->m_rejectOnFail = true; } currentp = condVtxp; } + // After minN: currentp is the first valid end-of-match position for [*m:n]. + if (m_isCoverSeq && (repp->unbounded() || repp->maxCountp())) { + consMidSources.push_back(currentp); + } if (repp->unbounded()) { if (minN == 0) { @@ -564,11 +652,19 @@ class SvaNfaBuilder final { guardedLink(nextVtxp, checkVtxp, sampledRefOrClone(hoistVarp, exprp, flp), flp); guardedLink(checkVtxp, mergeVtxp, flp); currentp = checkVtxp; + if (m_isCoverSeq) consMidSources.push_back(checkVtxp); } currentp = mergeVtxp; } // finalCond = nullptr (already checked via Links) - return {currentp, nullptr, {}}; + BuildResult res; + res.termVertexp = currentp; + res.finalCondp = nullptr; + res.midSources = std::move(consMidSources); + // mergeVtxp is the OR of all the end-positions we already pushed to + // midSources, so the main termVtxp -> matchVertex Link would duplicate. + res.termIsMidMerge = m_isCoverSeq && !res.midSources.empty(); + return res; } // always[lo:hi] / s_always[lo:hi] (IEEE 1800-2023 16.12.11). @@ -607,6 +703,16 @@ class SvaNfaBuilder final { UASSERT_OBJ(maxN >= minN, repp, "GotoRep range max < min (V3Width invariant)"); if (exceedsAssertUnrollLimit(repp, maxN)) return BuildResult::failWithError(); + // IEEE 1800-2023 16.14.3: a ranged goto repetition b[->M:N] ends at every + // M..N-th match, but only the shared merge vertex below reaches the + // terminal, so a cover sequence would under-count. Reject the ranged form + // (the single-count b[->N] has one end and is enumerated correctly). + if (m_isCoverSeq && hasMax && maxN > minN) { + flp->v3warn(COVERIGN, + "Ignoring unsupported: cover sequence with a ranged goto repetition"); + return BuildResult::failWithError(); + } + // Wait + match per iter -> 2 sites per iteration; range form needs // sites for every iteration in [0..maxN). NOT($sampled(x)) matches // $sampled(NOT(x)) at the value level (IEEE 1800-2023 16.9.9); @@ -661,6 +767,16 @@ class SvaNfaBuilder final { if (!lhs.valid() || !rhs.valid()) { // LCOV_EXCL_START -- sub-build fail bail return BuildResult::fail(lhs.errorEmitted || rhs.errorEmitted); } // LCOV_EXCL_STOP + // IEEE 1800-2023 16.14.3: a cover sequence counts every end-of-match. A + // sequence operand of 'or' can end more than once, but only its final + // end reaches the merge vertex below, so reject sequence operands rather + // than under-count. Plain boolean disjunction has one end per cycle and + // is handled by the OR-fold. + if (m_isCoverSeq && (lhs.termVertexp != entryVtxp || rhs.termVertexp != entryVtxp)) { + flp->v3warn(COVERIGN, + "Ignoring unsupported: cover sequence with a sequence operand of 'or'"); + return BuildResult::failWithError(); + } SvaStateVertex* const mergeVtxp = scopedCreateVertex(); if (lhs.finalCondp) { guardedLink(lhs.termVertexp, mergeVtxp, sampled(lhs.finalCondp->cloneTreePure(false)), @@ -970,10 +1086,12 @@ class SvaNfaBuilder final { } public: - SvaNfaBuilder(SvaGraph& graph, AstNodeModule* modp, V3UniqueNames& propTempNames) + SvaNfaBuilder(SvaGraph& graph, AstNodeModule* modp, V3UniqueNames& propTempNames, + bool isCoverSeq = false) : m_graph{graph} , m_modp{modp} - , m_propTempNames{propTempNames} {} + , m_propTempNames{propTempNames} + , m_isCoverSeq{isCoverSeq} {} // Reset scope between antecedent and consequent: liveness must not leak. void resetScope() { @@ -1346,8 +1464,11 @@ class SvaNfaLowering final { // throughout-drop reject; clean up intermediate state signals. // Phase 3: terminalActive and rejectBase from Links to matchVertex. // Builder only adds Links (non-clocked) to matchVertex via addLink in - // wireMatchAndMidSources. - void computeTerminalMatchAndReject(LowerCtx& c, AstNodeExpr* snapshotOkp, SignalSet& sigs) { + // wireMatchAndMidSources. When outPerMidSrcsp is non-null, also collect + // the per-edge match signal (IEEE 1800-2023 16.14.3 cover sequence: each + // end-of-match fires the action independently, no OR-fold). + void computeTerminalMatchAndReject(LowerCtx& c, AstNodeExpr* snapshotOkp, SignalSet& sigs, + std::vector* outPerMidSrcsp = nullptr) { for (const SvaTransEdge* const tep : c.edges) { if (tep->toVtxp() != c.graph.m_matchVertexp) continue; const int fi = tep->fromVtxp()->color(); @@ -1359,6 +1480,18 @@ class SvaNfaLowering final { if (snapshotOkp) { srcSigp = new AstLogAnd{c.flp, srcSigp, snapshotOkp->cloneTreePure(false)}; } + if (outPerMidSrcsp) { + // Per-mid signal must also AND in matchCondp (the final boolean + // check, e.g. sampled(b) for `a ##[1:3] b`). assembleResult does + // this for the OR-collapsed terminalActivep; we replicate it + // per-edge here so each end-of-match is gated identically. + AstNodeExpr* perMidp = srcSigp->cloneTreePure(false); + if (c.matchCondp) { + perMidp = new AstLogAnd{c.flp, perMidp, + sampled(c.matchCondp->cloneTreePure(false))}; + } + outPerMidSrcsp->push_back(perMidp); + } if (tep->fromVtxp()->m_isCounter) { sigs.terminalActivep @@ -1409,7 +1542,8 @@ class SvaNfaLowering final { } } - SignalSet computeSignals(LowerCtx& c, std::vector* outRequiredStepSrcsp) { + SignalSet computeSignals(LowerCtx& c, std::vector* outRequiredStepSrcsp, + std::vector* outPerMidSrcsp = nullptr) { SignalSet sigs; // Snapshot comparison expression for disable-iff counter. @@ -1421,18 +1555,28 @@ class SvaNfaLowering final { new AstVarRef{c.flp, c.disableCntVarp, VAccess::READ}}; } - computeTerminalMatchAndReject(c, snapshotOkp, sigs); + computeTerminalMatchAndReject(c, snapshotOkp, sigs, outPerMidSrcsp); // Phase 3a: required-step rejection. - // Builder only sets m_rejectOnFail on non-clocked Links with m_condp, - // and the source always has a resolved stateSig. + // Builder only sets m_rejectOnFail on non-clocked Links with m_condp + // or m_condVtxp, and the source always has a resolved stateSig. for (const SvaTransEdge* const tep : c.edges) { if (!tep->m_rejectOnFail) continue; const int fi = tep->fromVtxp()->color(); - UASSERT_OBJ(c.vtx[fi]->datap()->stateSigp && tep->m_condp, tep->fromVtxp(), - "rejectOnFail Link must have condp and source stateSig"); + UASSERT_OBJ(c.vtx[fi]->datap()->stateSigp && (tep->m_condp || tep->m_condVtxp), + tep->fromVtxp(), + "rejectOnFail Link must have condp/condVtxp and source stateSig"); AstNodeExpr* const srcSigp = c.vtx[fi]->datap()->stateSigp->cloneTreePure(false); - AstNodeExpr* const notCondp = new AstLogNot{c.flp, tep->m_condp->cloneTreePure(false)}; + AstNodeExpr* condp = nullptr; + if (tep->m_condVtxp) { + const int ci = tep->m_condVtxp->color(); + UASSERT_OBJ(c.vtx[ci]->datap()->stateSigp, tep->m_condVtxp, + "rejectOnFail condVtxp missing stateSig"); + condp = c.vtx[ci]->datap()->stateSigp->cloneTreePure(false); + } else { + condp = tep->m_condp->cloneTreePure(false); + } + AstNodeExpr* const notCondp = new AstLogNot{c.flp, condp}; AstNodeExpr* const failp = new AstLogAnd{c.flp, srcSigp, notCondp}; if (outRequiredStepSrcsp) { outRequiredStepSrcsp->push_back(failp->cloneTreePure(false)); @@ -1648,7 +1792,8 @@ public: AstNodeExpr* disableExprp = nullptr, bool negated = false, AstNodeExpr** outMatchpp = nullptr, AstVar* disableCntVarp = nullptr, AstVar* snapshotVarp = nullptr, - std::vector* outRequiredStepSrcsp = nullptr) { + std::vector* outRequiredStepSrcsp = nullptr, + std::vector* outPerMidSrcsp = nullptr) { const std::string baseName = m_names.get(""); // Number vertices with sequential colors for array indexing. @@ -1734,7 +1879,7 @@ public: emitAndCombinerDoneLatchNba(c); // Phase 3/3a/3b: Compute terminal match/reject signals (cleans up stateSig). - const SignalSet sigs = computeSignals(c, outRequiredStepSrcsp); + const SignalSet sigs = computeSignals(c, outRequiredStepSrcsp, outPerMidSrcsp); AstNodeExpr* const resultp = assembleResult( flp, isCover, negated, matchCondp, sigs.terminalActivep, sigs.rejectBasep, @@ -1765,7 +1910,10 @@ class AssertNfaVisitor final : public VNVisitor { // Wire match vertex and mid-window sources for a successful NFA build. static void wireMatchAndMidSources(SvaGraph& graph, const BuildResult& result, FileLine* flp) { graph.createMatchVertex(); - graph.addLink(result.termVertexp, graph.m_matchVertexp); + // Skip the main term Link when midSources already cover every + // end-of-match (cover_sequence path); otherwise the per-mid extraction + // double-counts via the merge vertex. + if (!result.termIsMidMerge) { graph.addLink(result.termVertexp, graph.m_matchVertexp); } for (SvaStateVertex* srcVtxp : result.midSources) { AstNodeExpr* condp = nullptr; for (AstNodeExpr* const tc : srcVtxp->m_throughoutConds) { @@ -2234,9 +2382,11 @@ class AssertNfaVisitor final : public VNVisitor { FileLine* const flp = assertp->fileline(); const bool isCover = VN_IS(assertp, Cover); + AstCover* const coverp = VN_CAST(assertp, Cover); + const bool isCoverSeq = coverp && coverp->isCoverSeq(); SvaGraph graph; - SvaNfaBuilder builder{graph, m_modp, m_propTempNames}; + SvaNfaBuilder builder{graph, m_modp, m_propTempNames, isCoverSeq}; const BuildResult result = buildAssertionGraph(builder, graph, seqBodyp, parts, flp); if (result.valid()) wireMatchAndMidSources(graph, result, flp); @@ -2268,12 +2418,18 @@ class AssertNfaVisitor final : public VNVisitor { = !isCover && !parts.hasImplication && assertWithFailp && assertWithFailp->failsp(); std::vector 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 perMidSrcs; + AstNodeExpr* const alwaysTriggerp = new AstConst{flp, AstConst::BitTrue{}}; - AstNodeExpr* const outputExprp - = m_loweringp->lower(flp, graph, alwaysTriggerp, senTreep, result.finalCondp, isCover, - disableExprp ? disableExprp->cloneTreePure(false) : nullptr, - negated, needMatch ? &matchExprp : nullptr, disableCntVarp, - snapshotVarp, needPerSrcFail ? &requiredStepSrcs : nullptr); + AstNodeExpr* const outputExprp = m_loweringp->lower( + flp, graph, alwaysTriggerp, senTreep, result.finalCondp, isCover, + disableExprp ? disableExprp->cloneTreePure(false) : nullptr, negated, + needMatch ? &matchExprp : nullptr, disableCntVarp, snapshotVarp, + needPerSrcFail ? &requiredStepSrcs : nullptr, isCoverSeq ? &perMidSrcs : nullptr); AstSenTree* const perSrcSenTreep = (requiredStepSrcs.size() >= 2) ? senTreep->cloneTree(false) : nullptr; @@ -2286,9 +2442,38 @@ class AssertNfaVisitor final : public VNVisitor { attachMatchHandlers(flp, assertAssertp, assertWithFailp, needMatch ? matchExprp : nullptr, perSrcSenTreep, requiredStepSrcs); - AstNode* const innerPropp = propSpecp->propp(); - innerPropp->replaceWith(outputExprp); - VL_DO_DANGLING(pushDeletep(innerPropp), innerPropp); + if (isCoverSeq && perMidSrcs.size() > 1) { + // Clone AstCover (N-1) times, each gated by its own per-mid signal. + // V3Assert sees N independent covers and emits N `if (cond_i) {coverinc; + // userAction}` bodies; the shared AstCoverDecl bucket is incremented + // per fire, matching IEEE "executed each time the sequence matches." + // Clones reuse AstCover->propp's original SVA tree, but we overwrite + // each clone's inner propp with the corresponding per-mid signal + // BEFORE the next iterator step, so hasMultiCycleExpr() returns false + // and processAssertion skips them on revisit. + std::vector coverList; + coverList.push_back(coverp); + for (size_t i = 1; i < perMidSrcs.size(); ++i) { + AstCover* const clonep = coverp->cloneTree(false); + coverp->addNextHere(clonep); + coverList.push_back(clonep); + } + for (size_t i = 0; i < perMidSrcs.size(); ++i) { + AstPropSpec* const clonePropSpecp = VN_CAST(coverList[i]->propp(), PropSpec); + AstNode* const innerp = clonePropSpecp->propp(); + innerp->replaceWith(perMidSrcs[i]); + VL_DO_DANGLING(pushDeletep(innerp), innerp); + } + // Discard the OR-collapsed fallback signal -- cover_sequence path + // does not use it. + VL_DO_DANGLING(outputExprp->deleteTree(), outputExprp); + } else { + AstNode* const innerPropp = propSpecp->propp(); + innerPropp->replaceWith(outputExprp); + VL_DO_DANGLING(pushDeletep(innerPropp), innerPropp); + // If we collected per-mid (N==1) but didn't clone, drop the spare. + for (AstNodeExpr* const sp : perMidSrcs) pushDeletep(sp); + } UINFO(4, "NFA converted assertion at " << flp << endl); } @@ -2301,7 +2486,7 @@ class AssertNfaVisitor final : public VNVisitor { VL_RESTORER(m_defaultDisablep); m_modp = nodep; m_defaultClockingp = nullptr; - m_defaultDisablep = nullptr; + m_defaultDisablep = nodep->defaultDisablep(); SvaNfaLowering lowering{nodep}; m_loweringp = &lowering; iterateChildren(nodep); @@ -2310,9 +2495,12 @@ class AssertNfaVisitor final : public VNVisitor { if (nodep->isDefault() && !m_defaultClockingp) m_defaultClockingp = nodep; iterateChildren(nodep); } - void visit(AstDefaultDisable* nodep) override { - if (!m_defaultDisablep) m_defaultDisablep = nodep; + void visit(AstGenBlock* nodep) override { + VL_RESTORER(m_defaultDisablep); + m_defaultDisablep = nodep->defaultDisablep(); + iterateChildren(nodep); } + void visit(AstDefaultDisable* nodep) override {} void visit(AstAssert* nodep) override { processAssertion(nodep); } void visit(AstCover* nodep) override { processAssertion(nodep); } void visit(AstRestrict* nodep) override { diff --git a/src/V3AssertPre.cpp b/src/V3AssertPre.cpp index c61299a66..05fb804fc 100644 --- a/src/V3AssertPre.cpp +++ b/src/V3AssertPre.cpp @@ -23,6 +23,7 @@ #include "V3AssertPre.h" +#include "V3Assert.h" #include "V3Const.h" #include "V3Task.h" #include "V3UniqueNames.h" @@ -1438,12 +1439,6 @@ private: } void visit(AstDefaultDisable* nodep) override { - if (m_defaultDisablep) { - nodep->v3error("Only one 'default disable iff' allowed per module" - " (IEEE 1800-2023 16.15)"); - } else { - m_defaultDisablep = nodep; - } VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); } void visit(AstInferredDisable* nodep) override { @@ -1565,10 +1560,15 @@ private: VL_RESTORER(m_modp); m_defaultClockingp = nullptr; m_defaultClkEvtVarp = nullptr; - m_defaultDisablep = nullptr; + m_defaultDisablep = nodep->defaultDisablep(); m_modp = nodep; iterateChildren(nodep); } + void visit(AstGenBlock* nodep) override { + VL_RESTORER(m_defaultDisablep); + m_defaultDisablep = nodep->defaultDisablep(); + iterateChildren(nodep); + } void visit(AstProperty* nodep) override { // The body will be visited when will be substituted in place of property reference // (AstFuncRef) diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index c12627886..096923300 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -342,7 +342,6 @@ public: VAR_PUBLIC_FLAT, // V3LinkParse moves to AstVar::sigPublic VAR_PUBLIC_FLAT_RD, // V3LinkParse moves to AstVar::sigPublic VAR_PUBLIC_FLAT_RW, // V3LinkParse moves to AstVar::sigPublic - VAR_ISOLATE_ASSIGNMENTS, // V3LinkParse moves to AstVar::attrIsolateAssign VAR_SC_BIGUINT, // V3LinkParse moves to AstVar::attrScBigUint VAR_SC_BV, // V3LinkParse moves to AstVar::attrScBv VAR_SFORMAT, // V3LinkParse moves to AstVar::attrSFormat @@ -364,7 +363,7 @@ public: "TYPEID", "TYPENAME", "VAR_BASE", "VAR_FORCEABLE", "VAR_FSM_ARC_INCLUDE_COND", "VAR_FSM_RESET_ARC", "VAR_FSM_STATE", "VAR_PORT_DTYPE", "VAR_PUBLIC", "VAR_PUBLIC_FLAT", - "VAR_PUBLIC_FLAT_RD", "VAR_PUBLIC_FLAT_RW", "VAR_ISOLATE_ASSIGNMENTS", + "VAR_PUBLIC_FLAT_RD", "VAR_PUBLIC_FLAT_RW", "VAR_SC_BIGUINT", "VAR_SC_BV", "VAR_SFORMAT", "VAR_SPLIT_VAR" }; // clang-format on @@ -1191,6 +1190,19 @@ public: = {"user", "array", "auto", "ignore", "illegal", "default", "wildcard", "transition"}; return names[m_e]; } + // VlCovBinKind enumerator naming the bin's set + const char* binSetEnum() const { + switch (m_e) { + case BINS_IGNORE: return "VlCovBinKind::KIND_IGNORE"; + case BINS_ILLEGAL: return "VlCovBinKind::KIND_ILLEGAL"; + case BINS_DEFAULT: return "VlCovBinKind::KIND_DEFAULT"; + default: return "VlCovBinKind::KIND_NORMAL"; + } + } + // Normal bins (feed coverage) are anything but ignore/illegal/default + bool binIsNormal() const { + return m_e != BINS_IGNORE && m_e != BINS_ILLEGAL && m_e != BINS_DEFAULT; + } }; constexpr bool operator==(const VCoverBinsType& lhs, VCoverBinsType::en rhs) { return lhs.m_e == rhs; diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index 1253c8527..4600911d2 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -170,6 +170,10 @@ public: void generic(bool flag) { m_generic = flag; } std::pair dimensions(bool includeBasic) const; uint32_t arrayUnpackedElements() const; // 1, or total multiplication of all dimensions + // Fixed aggregate streaming properties + bool isStreamableFixedAggregate() const; + bool containsUnpackedStruct() const; + int widthStream() const; static int uniqueNumInc() { return ++s_uniqueNum; } const char* charIQWN() const { return (isString() ? "N" : isWide() ? "W" : isDouble() ? "D" : isQuad() ? "Q" : "I"); diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 903130658..f9518bf63 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -97,7 +97,6 @@ class AstNodeFTask VL_NOT_FINAL : public AstNode { string m_ifacePortName; // Interface port name for out-of-block definition (IEEE 25.8) uint64_t m_dpiOpenParent = 0; // DPI import open array, if !=0, how many callees bool m_taskPublic : 1; // Public task - bool m_attrIsolateAssign : 1; // User isolate_assignments attribute bool m_classMethod : 1; // Class method bool m_didProto : 1; // Did prototype processing bool m_prototype : 1; // Just a prototype @@ -130,7 +129,6 @@ protected: : AstNode{t, fl} , m_name{name} , m_taskPublic{false} - , m_attrIsolateAssign{false} , m_classMethod{false} , m_didProto{false} , m_prototype{false} @@ -179,8 +177,6 @@ public: uint64_t dpiOpenParent() const { return m_dpiOpenParent; } bool taskPublic() const { return m_taskPublic; } void taskPublic(bool flag) { m_taskPublic = flag; } - bool attrIsolateAssign() const { return m_attrIsolateAssign; } - void attrIsolateAssign(bool flag) { m_attrIsolateAssign = flag; } bool classMethod() const { return m_classMethod; } void classMethod(bool flag) { m_classMethod = flag; } bool didProto() const { return m_didProto; } @@ -301,6 +297,7 @@ class AstNodeModule VL_NOT_FINAL : public AstNode { VLifetime m_lifetime; // Lifetime VTimescale m_timeunit; // Global time unit VOptionBool m_unconnectedDrive; // State of `unconnected_drive + AstDefaultDisable* m_defaultDisablep = nullptr; // Default disable iff in this scope bool m_modPublic : 1; // Module has public references bool m_modTrace : 1; // Tracing this module @@ -351,6 +348,8 @@ public: string origName() const override { return m_origName; } string someInstanceName() const VL_MT_SAFE { return m_someInstanceName; } void someInstanceName(const string& name) { m_someInstanceName = name; } + AstDefaultDisable* defaultDisablep() const { return m_defaultDisablep; } + void defaultDisablep(AstDefaultDisable* nodep) { m_defaultDisablep = nodep; } bool inLibrary() const { return m_inLibrary; } void inLibrary(bool flag) { m_inLibrary = flag; } void depth(int value) { m_depth = value; } @@ -987,6 +986,7 @@ public: bool maybePointedTo() const override VL_MT_SAFE { return true; } void cloneRelink() override { V3ERROR_NA; } // Not cloneable AstModule* modp() const { return m_modp; } + AstScope* scopep() const { return m_scopep; } // Find a table (unpacked array) within the constant pool which is initialized with the // given value, or create one if one does not already exists. The returned VarScope *might* @@ -2130,7 +2130,6 @@ class AstVar final : public AstNode { bool m_funcReturn : 1; // Return variable for a function bool m_attrScBv : 1; // User force bit vector attribute bool m_attrScBigUint : 1; // User force sc_biguint attribute - bool m_attrIsolateAssign : 1; // User isolate_assignments attribute bool m_attrSFormat : 1; // User sformat attribute bool m_attrSplitVar : 1; // declared with split_var metacomment bool m_attrFsmState : 1; // declared with fsm_state metacomment @@ -2139,6 +2138,7 @@ class AstVar final : public AstNode { bool m_attrFsmArcInclCond : 1; // declared with fsm_arc_include_cond metacomment bool m_fileDescr : 1; // File descriptor bool m_gotNansiType : 1; // Linker saw Non-ANSI type declaration + bool m_icoMaybeWritten : 1; // Design might write this input signal - for ico change detect bool m_isConst : 1; // Table contains constant data bool m_isContinuously : 1; // Ever assigned continuously (for force/release) bool m_hasStrengthAssignment : 1; // Is on LHS of assignment with strength specifier @@ -2193,7 +2193,6 @@ class AstVar final : public AstNode { m_funcReturn = false; m_attrScBv = false; m_attrScBigUint = false; - m_attrIsolateAssign = false; m_attrSFormat = false; m_attrSplitVar = false; m_attrFsmState = false; @@ -2202,6 +2201,7 @@ class AstVar final : public AstNode { m_attrFsmArcInclCond = false; m_fileDescr = false; m_gotNansiType = false; + m_icoMaybeWritten = false; m_isConst = false; m_isContinuously = false; m_hasStrengthAssignment = false; @@ -2342,7 +2342,6 @@ public: void attrFileDescr(bool flag) { m_fileDescr = flag; } void attrScBv(bool flag) { m_attrScBv = flag; } void attrScBigUint(bool flag) { m_attrScBigUint = flag; } - void attrIsolateAssign(bool flag) { m_attrIsolateAssign = flag; } void attrSFormat(bool flag) { m_attrSFormat = flag; } void attrSplitVar(bool flag) { m_attrSplitVar = flag; } void attrFsmState(bool flag) { m_attrFsmState = flag; } @@ -2382,6 +2381,8 @@ public: void hasStrengthAssignment(bool flag) { m_hasStrengthAssignment = flag; } bool hasUserInit() const { return m_hasUserInit; } void hasUserInit(bool flag) { m_hasUserInit = flag; } + void icoMaybeWritten(bool flag) { m_icoMaybeWritten = flag; } + bool icoMaybeWritten() const { return m_icoMaybeWritten; } bool isDpiOpenArray() const VL_MT_SAFE { return m_isDpiOpenArray; } void isDpiOpenArray(bool flag) { m_isDpiOpenArray = flag; } bool isHideLocal() const { return m_isHideLocal; } @@ -2461,12 +2462,6 @@ public: bool isWor() const { return varType().isWor(); } bool isWiredNet() const { return varType().isWiredNet(); } bool isTemp() const { return varType().isTemp(); } - bool isToggleCoverable() const { - return ((isIO() || isSignal()) - && (isIO() || isBitLogic()) - // Wrapper would otherwise duplicate wrapped module's coverage - && !isSc() && !isPrimaryIO() && !isConst() && !isDouble() && !isString()); - } bool isClassMember() const { return varType() == VVarType::MEMBER; } bool isVirtIface() const { if (AstIfaceRefDType* const dtp = VN_CAST(dtypep(), IfaceRefDType)) { @@ -2518,7 +2513,6 @@ public: bool attrFsmRegisterWrapper() const { return m_attrFsmRegisterWrapper; } bool attrFsmResetArc() const { return m_attrFsmResetArc; } bool attrFsmArcInclCond() const { return m_attrFsmArcInclCond; } - bool attrIsolateAssign() const { return m_attrIsolateAssign; } AstIface* sensIfacep() const { return m_sensIfacep; } VRandAttr rand() const { return m_rand; } string verilogKwd() const override; @@ -2530,7 +2524,6 @@ public: // This is getting connected to fromp; keep attributes // Note the method below too if (fromp->attrFileDescr()) attrFileDescr(true); - if (fromp->attrIsolateAssign()) attrIsolateAssign(true); if (fromp->isContinuously()) isContinuously(true); } void propagateWrapAttrFrom(const AstVar* fromp) { @@ -2809,6 +2802,7 @@ class AstGenBlock final : public AstNodeGen { std::string m_name; // Name of block const bool m_unnamed; // Originally unnamed (name change does not affect this) const bool m_implied; // Not inserted by user + AstDefaultDisable* m_defaultDisablep = nullptr; // Default disable iff in this scope public: AstGenBlock(FileLine* fl, const string& name, AstNode* itemsp, bool implied) @@ -2825,6 +2819,8 @@ public: void name(const std::string& name) override { m_name = name; } bool unnamed() const { return m_unnamed; } bool implied() const { return m_implied; } + AstDefaultDisable* defaultDisablep() const { return m_defaultDisablep; } + void defaultDisablep(AstDefaultDisable* nodep) { m_defaultDisablep = nodep; } }; class AstGenCase final : public AstNodeGen { // Generate 'case' diff --git a/src/V3AstNodeStmt.h b/src/V3AstNodeStmt.h index 336da7bae..46c49b515 100644 --- a/src/V3AstNodeStmt.h +++ b/src/V3AstNodeStmt.h @@ -59,6 +59,7 @@ protected: public: ASTGEN_MEMBERS_AstNodeAssign; // Clone single node, just get same type back. + void dump(std::ostream& str) const override; virtual AstNodeAssign* cloneType(AstNodeExpr* lhsp, AstNodeExpr* rhsp) = 0; bool hasDType() const override VL_MT_SAFE { return true; } virtual bool cleanRhs() const { return true; } @@ -1606,12 +1607,18 @@ public: }; class AstCover final : public AstNodeCoverOrAssert { // @astgen op3 := coverincsp: List[AstNode] // Coverage node + bool m_isCoverSeq = false; // 'cover sequence' (IEEE 1800-2023 16.14.3): fires per + // end-of-match, not per property success public: ASTGEN_MEMBERS_AstCover; AstCover(FileLine* fl, AstNode* propp, AstNode* stmtsp, VAssertType type, const string& name = "") : ASTGEN_SUPER_Cover(fl, propp, stmtsp, type, VAssertDirectiveType::COVER, name) {} string verilogKwd() const override { return "cover"; } + void dump(std::ostream& str) const override; + void dumpJson(std::ostream& str) const override; + bool isCoverSeq() const { return m_isCoverSeq; } + void isCoverSeq(bool flag) { m_isCoverSeq = flag; } }; class AstRestrict final : public AstNodeCoverOrAssert { public: diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 3bd3f66f6..453c45d21 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -630,6 +630,7 @@ void AstVar::combineType(const AstVar* otherp) { varType(otherp->varType()); direction(otherp->direction()); } + if (otherp->icoMaybeWritten()) icoMaybeWritten(true); } void AstVar::combineType(VVarType type) { // These flags get combined with the existing settings of the flags. @@ -1259,6 +1260,47 @@ uint32_t AstNodeDType::arrayUnpackedElements() const { return entries; } +bool AstNodeDType::isStreamableFixedAggregate() const { + const AstNodeDType* const dtypep = skipRefp(); + if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + return adtypep->subDTypep()->isStreamableFixedAggregate(); + } else if (const AstNodeUOrStructDType* const sdtypep = VN_CAST(dtypep, NodeUOrStructDType)) { + if (sdtypep->packed()) return true; + if (!VN_IS(sdtypep, StructDType)) return false; + for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + if (!itemp->dtypep()->isStreamableFixedAggregate()) return false; + } + return true; + } + return dtypep->isIntegralOrPacked() || dtypep->isDouble(); +} + +bool AstNodeDType::containsUnpackedStruct() const { + const AstNodeDType* const dtypep = skipRefp(); + if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + return adtypep->subDTypep()->containsUnpackedStruct(); + } + const AstStructDType* const sdtypep = VN_CAST(dtypep, StructDType); + return sdtypep && !sdtypep->packed(); +} + +int AstNodeDType::widthStream() const { + const AstNodeDType* const dtypep = skipRefp(); + if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + return adtypep->subDTypep()->widthStream() * adtypep->elementsConst(); + } else if (const AstNodeUOrStructDType* const sdtypep = VN_CAST(dtypep, NodeUOrStructDType)) { + if (!VN_IS(sdtypep, StructDType) || sdtypep->packed()) return width(); + int width = 0; + for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + width += itemp->dtypep()->widthStream(); + } + return width; + } + return dtypep->width(); +} + std::pair AstNodeDType::dimensions(bool includeBasic) const { // How many array dimensions (packed,unpacked) does this Var have? uint32_t packed = 0; @@ -1322,6 +1364,12 @@ AstNode* AstArraySel::baseFromp(AstNode* nodep, bool overMembers) { } else if (VN_IS(nodep, Sel)) { nodep = VN_AS(nodep, Sel)->fromp(); continue; + } else if (VN_IS(nodep, AssocSel)) { + nodep = VN_AS(nodep, AssocSel)->fromp(); + continue; + } else if (VN_IS(nodep, WildcardSel)) { + nodep = VN_AS(nodep, WildcardSel)->fromp(); + continue; } else if (overMembers && VN_IS(nodep, MemberSel)) { nodep = VN_AS(nodep, MemberSel)->fromp(); continue; @@ -2133,6 +2181,14 @@ void AstNodeCoverOrAssert::dumpJson(std::ostream& str) const { dumpJsonBoolFuncIf(str, immediate); dumpJsonBoolFuncIf(str, senFromAlways); } +void AstCover::dump(std::ostream& str) const { + this->AstNodeCoverOrAssert::dump(str); + if (isCoverSeq()) str << " [COVERSEQ]"; +} +void AstCover::dumpJson(std::ostream& str) const { + dumpJsonBoolFuncIf(str, isCoverSeq); + this->AstNodeCoverOrAssert::dumpJson(str); +} void AstClocking::dump(std::ostream& str) const { this->AstNode::dump(str); if (isDefault()) str << " [DEFAULT]"; @@ -2724,6 +2780,10 @@ void AstNodeArrayDType::dumpJson(std::ostream& str) const { dumpJsonStr(str, "declRange", cvtToStr(declRange())); dumpJsonGen(str); } +void AstNodeAssign::dump(std::ostream& str) const { + this->AstNode::dump(str); + if (timingControlp()) str << " [TIMING=" << nodeAddr(timingControlp()) << "]"; +} string AstPackArrayDType::prettyDTypeName(bool full) const { std::ostringstream os; if (const auto subp = subDTypep()) os << subp->prettyDTypeName(full); @@ -3158,7 +3218,6 @@ void AstVar::dump(std::ostream& str) const { if (noReset()) str << " [!RST]"; if (processQueue()) str << " [PROCQ]"; if (sampled()) str << " [SAMPLED]"; - if (attrIsolateAssign()) str << " [aISO]"; if (attrFsmState()) str << " [aFSMSTATE]"; if (attrFsmResetArc()) str << " [aFSMRESETARC]"; if (attrFsmArcInclCond()) str << " [aFSMARCCOND]"; @@ -3169,6 +3228,7 @@ void AstVar::dump(std::ostream& str) const { str << " [FUNC]"; } if (hasUserInit()) str << " [UINIT]"; + if (icoMaybeWritten()) str << " [ICOMAYBEWRITTEN]"; if (isDpiOpenArray()) str << " [DPIOPENA]"; if (ignorePostWrite()) str << " [IGNPWR]"; if (ignoreSchedWrite()) str << " [IGNWR]"; @@ -3193,11 +3253,11 @@ void AstVar::dumpJson(std::ostream& str) const { dumpJsonBoolFuncIf(str, noReset); dumpJsonBoolFuncIf(str, processQueue); dumpJsonBoolFuncIf(str, sampled); - dumpJsonBoolFuncIf(str, attrIsolateAssign); dumpJsonBoolFuncIf(str, attrFsmState); dumpJsonBoolFuncIf(str, attrFsmResetArc); dumpJsonBoolFuncIf(str, attrFsmArcInclCond); dumpJsonBoolFuncIf(str, attrFileDescr); + dumpJsonBoolFuncIf(str, icoMaybeWritten); dumpJsonBoolFuncIf(str, isDpiOpenArray); dumpJsonBoolFuncIf(str, isFuncReturn); dumpJsonBoolFuncIf(str, isFuncLocal); diff --git a/src/V3Begin.cpp b/src/V3Begin.cpp index 36e151053..2ac71c770 100644 --- a/src/V3Begin.cpp +++ b/src/V3Begin.cpp @@ -61,7 +61,6 @@ class BeginVisitor final : public VNVisitor { // NODE STATE // AstCase::user1 -> bool, if already purified - V3UniqueNames m_caseTempNames; // For generating unique temporary variable names used by cases // STATE - across all visitors BeginState* const m_statep; // Current global state @@ -75,7 +74,6 @@ class BeginVisitor final : public VNVisitor { string m_unnamedScope; // Name of begin blocks, including unnamed blocks int m_ifDepth = 0; // Current if depth bool m_keepBegins = false; // True if begins should not be inlined - VDouble0 m_statPurifiedCaseExpr; // Count of purified case expressions // METHODS @@ -132,7 +130,6 @@ class BeginVisitor final : public VNVisitor { } void visit(AstNodeModule* nodep) override { VL_RESTORER(m_modp); - VL_RESTORER(m_caseTempNames); m_modp = nodep; // Rename it (e.g. class under a generate) if (m_unnamedScope != "") { @@ -171,7 +168,6 @@ class BeginVisitor final : public VNVisitor { VL_RESTORER(m_liftedp); VL_RESTORER(m_namedScope); VL_RESTORER(m_unnamedScope); - VL_RESTORER(m_caseTempNames); m_displayScope = dot(m_displayScope, nodep->name()); m_namedScope = ""; m_unnamedScope = ""; @@ -415,29 +411,7 @@ class BeginVisitor final : public VNVisitor { // any BEGINs, but V3Coverage adds them all under the module itself. iterateChildren(nodep); } - void visit(AstCase* nodep) override { - // Introduce temporary variable for AstCase if needed - it is done here and not in V3Case - // because this phase is before V3Scope and V3Case is not. Doing it before V3Scope ensures - // that V3Scope will take care of a scope creation - if (!nodep->exprp()->isPure() && !nodep->user1SetOnce()) { - ++m_statPurifiedCaseExpr; - FileLine* const fl = nodep->exprp()->fileline(); - AstVar* const varp = new AstVar{fl, VVarType::XTEMP, m_caseTempNames.get(nodep), - nodep->exprp()->dtypep()}; - nodep->exprp(new AstExprStmt{fl, - new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, - nodep->exprp()->unlinkFrBack()}, - new AstVarRef{fl, varp, VAccess::READ}}); - if (m_ftaskp) { - varp->funcLocal(true); - varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); - m_ftaskp->stmtsp()->addHereThisAsNext(varp); - } else { - m_modp->stmtsp()->addHereThisAsNext(varp); - } - } - iterateChildren(nodep); - } + void visit(AstCase* nodep) override { iterateChildren(nodep); } // VISITORS - LINT CHECK void visit(AstIf* nodep) override { // not AstNodeIf; other types not covered VL_RESTORER(m_keepBegins); @@ -464,10 +438,8 @@ class BeginVisitor final : public VNVisitor { public: // CONSTRUCTORS BeginVisitor(AstNetlist* nodep, BeginState* statep) - : m_caseTempNames{"__VCase"} - , m_statep{statep} { + : m_statep{statep} { iterate(nodep); - V3Stats::addStatSum("Impure case expressions", m_statPurifiedCaseExpr); } ~BeginVisitor() override = default; }; diff --git a/src/V3Case.cpp b/src/V3Case.cpp index 4eca73e01..65386df2a 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -42,10 +42,6 @@ VL_DEFINE_DEBUG_FUNCTIONS; -#define CASE_OVERLAP_WIDTH 16 // Maximum width we can check for overlaps in -#define CASE_BARF 999999 // Magic width when non-constant -#define CASE_ENCODER_GROUP_DEPTH 8 // Levels of priority to be ORed together in top IF tree - //###################################################################### class CaseLintVisitor final : public VNVisitorConst { @@ -119,252 +115,302 @@ class CaseLintVisitor final : public VNVisitorConst { } void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } -public: // CONSTRUCTORS explicit CaseLintVisitor(AstCase* nodep) { iterateConst(nodep); } explicit CaseLintVisitor(AstGenCase* nodep) { iterateConst(nodep); } ~CaseLintVisitor() override = default; + +public: + static void apply(AstCase* nodep) { CaseLintVisitor{nodep}; } + static void apply(AstGenCase* nodep) { CaseLintVisitor{nodep}; } }; //###################################################################### // Case state, as a visitor of each AstNode class CaseVisitor final : public VNVisitor { - // NODE STATE - // Cleared each Case - // AstIf::user3() -> bool. Set true to indicate clone not needed - const VNUser3InUse m_inuser3; + // Maximum width for detailed analysis + constexpr static int CASE_DETAILS_MAX_WIDTH = 16; + // Levels of priority to be ORed together in top IF tree + constexpr static int CASE_ENCODER_GROUP_DEPTH = 8; + + // TYPES + // Record for each case value + struct CaseRecord final { + AstCaseItem* itemp; // Case item that covers value + AstConst* constp; // Expression within 'itemp' that covers value (nullptr for default) + AstNode* stmtsp; // Statements of 'itemp' (might be nullptr if case is empty) + }; // STATE - VDouble0 m_statCaseFast; // Statistic tracking - VDouble0 m_statCaseSlow; // Statistic tracking + // Statistics tracking, as a struct so can be passed to 'const' methods + struct Stats final { + VDouble0 caseFast; // Cases using fast bit tree method + VDouble0 caseGeneric; // Cases using generic if/else tree method + VDouble0 provenAssertions; // Assertions proven to hold + } m_stats; const AstNode* m_alwaysp = nullptr; // Always in which case is located - // Per-CASE - int m_caseWidth = 0; // Width of valueItems - int m_caseItems = 0; // Number of caseItem unique values - bool m_caseIncomplete = false; // Proven incomplete - bool m_caseNoOverlapsAllCovered = false; // Proven to be synopsys parallel_case compliant - // For each possible value, the case branch we need - std::array m_valueItem; - bool m_needToClearCache = false; // Whether cache needs to be cleared + // STATE - per AstCase. Update by 'analyzeCase', treat 'const' otherwise + bool m_caseOpaque = false; // Case statement is opaque (non-packed, or non-const conditions) + size_t m_caseNConditions = 0; // Number of conditions in the case statement + bool m_caseDetailsValid = false; // Indicates m_caseDetails is valid + struct final { + bool exhaustive = false; // Proven exhaustive + bool noOverlaps = false; // Proven no overlaps between cases + // Map from value (index) to the CaseRecord that covers this value + std::array records; + } m_caseDetails; // METHODS - //! Determine whether we should check case items are complete - //! @return Enum's dtype if should check, nullptr if shouldn't - const AstEnumDType* getEnumCompletionCheckDType(const AstCase* const nodep) { + + // Xs in case or casez are impossible due to two state simulations. + // Returns true if the item is never possible + static bool neverItem(const AstCase* casep, const AstNodeExpr* itemExprp) { + const AstConst* const constp = VN_CAST(itemExprp, Const); + if (!constp) return false; + if (casep->casex() || casep->caseInside()) return false; + if (casep->casez()) return constp->num().isAnyX(); + return constp->num().isFourState(); + } + + // Determine whether we should check case items are complete + // Returns enum's dtype if should check, nullptr if shouldn't + static const AstEnumDType* getEnumCompletionCheckDType(const AstCase* const nodep) { if (!nodep->uniquePragma() && !nodep->unique0Pragma()) return nullptr; const AstEnumDType* const enumDtp = VN_CAST(nodep->exprp()->dtypep()->skipRefToEnump(), EnumDType); if (!enumDtp) return nullptr; // Case isn't enum const AstBasicDType* const basicp = enumDtp->subDTypep()->basicp(); if (!basicp) return nullptr; // Not simple type (perhaps IEEE illegal) - if (basicp->width() > 32) return nullptr; return enumDtp; } - //! @return True if case items are complete, false if there are uncovered enums - bool checkCaseEnumComplete(const AstCase* const nodep, const AstEnumDType* const dtype) { - const uint32_t numCases = 1UL << m_caseWidth; - for (AstEnumItem* itemp = dtype->itemsp(); itemp; - itemp = VN_AS(itemp->nextp(), EnumItem)) { - AstConst* const econstp = VN_AS(itemp->valuep(), Const); - V3Number nummask{itemp, econstp->width()}; + + // Check and warn if case items are not complete over the given enum type. + // Returns true iff the case items cover all enum values/patterns. + bool checkExhaustiveEnum(const AstCase* const nodep, const AstEnumDType* const enump) { + const uint32_t numCases = 1UL << nodep->exprp()->width(); + for (AstEnumItem* eip = enump->itemsp(); eip; eip = VN_AS(eip->nextp(), EnumItem)) { + AstConst* const econstp = VN_AS(eip->valuep(), Const); + V3Number nummask{eip, econstp->width()}; nummask.opBitsNonX(econstp->num()); - const uint32_t mask = nummask.toUInt(); - V3Number numval{itemp, econstp->width()}; + V3Number numval{eip, econstp->width()}; numval.opBitsOne(econstp->num()); + + const uint32_t mask = nummask.toUInt(); const uint32_t val = numval.toUInt(); + // Check all cases to see if they cover this enum value/pattern for (uint32_t i = 0; i < numCases; ++i) { - if ((i & mask) == val) { - if (!m_valueItem[i]) { - if (!nodep->unique0Pragma()) - nodep->v3warn(CASEINCOMPLETE, "Enum item " - << itemp->prettyNameQ() - << " not covered by case\n"); - m_caseIncomplete = true; - return false; // enum has uncovered value by case items - } + if ((i & mask) != val) continue; // This case is not for this enum value + if (m_caseDetails.records[i].itemp) continue; // Covered case + // Warn unless unique0 case which allows no-match + if (!nodep->unique0Pragma()) { + nodep->v3warn(CASEINCOMPLETE, + "Enum item " << eip->prettyNameQ() << " not covered by case"); } + // TODO: warn for all uncovered enum values, not just the first + return false; // enum has uncovered value by case items } } return true; // enum is fully covered } - bool isCaseTreeFast(AstCase* nodep) { - int width = 0; - bool opaque = false; - m_caseItems = 0; - m_caseNoOverlapsAllCovered = true; - for (AstCaseItem* itemp = nodep->itemsp(); itemp; - itemp = VN_AS(itemp->nextp(), CaseItem)) { - for (AstNode* icondp = itemp->condsp(); icondp; icondp = icondp->nextp()) { - if (icondp->width() > width) width = icondp->width(); - if (icondp->isDouble()) opaque = true; - if (!VN_IS(icondp, Const)) width = CASE_BARF; // Can't parse; not a constant - m_caseItems++; + + // Check and warn if case items are not complete over all possible values. + // Returns true iff the case items cover all values of the case expression. + bool checkExhaustivePacked(AstCase* nodep) { + const uint32_t numCases = 1UL << nodep->exprp()->width(); + for (uint32_t i = 0; i < numCases; ++i) { + if (m_caseDetails.records[i].itemp) continue; // Covered case + if (!nodep->unique0Pragma()) { + nodep->v3warn(CASEINCOMPLETE, + "Case values incompletely covered (example pattern 0x" << std::hex + << i << ")"); } + // TODO: warn for more than one uncovered case, not just the first + return false; } - m_caseWidth = width; - if (width == 0 || width > CASE_OVERLAP_WIDTH || opaque) { - m_caseNoOverlapsAllCovered = false; - return false; // Too wide for analysis + + // It's an exhaustive case statement + return true; + } + + bool checkExhaustive(AstCase* nodep) { + if (const AstEnumDType* const enump = getEnumCompletionCheckDType(nodep)) { + return checkExhaustiveEnum(nodep, enump); + } + return checkExhaustivePacked(nodep); + } + + // Analyze each value in the case statement. Updates 'm_caseDetails' and issues warnings. + void analyzeCaseDetails(AstCase* nodep) { + const uint32_t numValues = 1UL << nodep->exprp()->width(); + // Clear case records + for (uint32_t i = 0; i < numValues; ++i) { + m_caseDetails.records[i].itemp = nullptr; + m_caseDetails.records[i].constp = nullptr; + m_caseDetails.records[i].stmtsp = nullptr; } - UINFO(8, "Simple case statement: " << nodep); - const uint32_t numCases = 1UL << m_caseWidth; - // Zero list of items for each value - for (uint32_t i = 0; i < numCases; ++i) m_valueItem[i] = nullptr; // Now pick up the values for each assignment // We can cheat and use uint32_t's because we only support narrow case's bool reportedOverlap = false; - bool reportedSubcase = false; - bool hasDefaultCase = false; - std::map caseItemMap; // case condition -> case item + bool hasDefault = false; + m_caseDetails.noOverlaps = true; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), CaseItem)) { - for (AstNode* icondp = itemp->condsp(); icondp; icondp = icondp->nextp()) { - // UINFOTREE(9, icondp, "", "caseitem"); - AstConst* const iconstp = VN_AS(icondp, Const); - UASSERT_OBJ(iconstp, nodep, "above 'can't parse' should have caught this"); - if (neverItem(nodep, iconstp)) { - // X in casez can't ever be executed + + // Default case + if (itemp->isDefault()) { + // Default was moved to be the last item by V3LinkDot. Fill remaining cases + for (uint32_t i = 0; i < numValues; ++i) { + CaseRecord& caseRecord = m_caseDetails.records[i]; + if (!caseRecord.itemp) { + caseRecord.itemp = itemp; + caseRecord.stmtsp = itemp->stmtsp(); + } + } + hasDefault = true; + continue; + } + + for (AstConst* iconstp = VN_AS(itemp->condsp(), Const); iconstp; + iconstp = VN_AS(iconstp->nextp(), Const)) { + // Some items can never match due to 2-state simulation + if (neverItem(nodep, iconstp)) continue; + + V3Number nummask{itemp, iconstp->width()}; + nummask.opBitsNonX(iconstp->num()); + V3Number numval{itemp, iconstp->width()}; + numval.opBitsOne(iconstp->num()); + + const uint32_t mask = nummask.toUInt(); + const uint32_t val = numval.toUInt(); + + bool foundNewCase = false; + const AstConst* firstOverlapConstp = nullptr; + uint32_t firstOverlapValue = 0; + for (uint32_t i = 0; i < numValues; ++i) { + if ((i & mask) != val) continue; + + CaseRecord& caseRecord = m_caseDetails.records[i]; + + // If this is the first case that covers this value, record it + if (!caseRecord.itemp) { + caseRecord.itemp = itemp; + caseRecord.constp = iconstp; + caseRecord.stmtsp = itemp->stmtsp(); + foundNewCase = true; + continue; + } + + // There is an overlap. If it's within the same CaseItem, it is legal + if (caseRecord.itemp == itemp) continue; + + // Otherwise record the first overlapping case + if (!firstOverlapConstp) { + firstOverlapConstp = caseRecord.constp; + firstOverlapValue = i; + m_caseDetails.noOverlaps = false; + } + } + + // Only report first overlap + if (reportedOverlap || !firstOverlapConstp) continue; + + // Report first overlap + if (nodep->priorityPragma()) { + // If this is a priority case, we only want to complain if every possible + // value for this item is already hit by some other item. This is true if + // 'foundNewCase' is false. 'firstOverlapConstp' is null when the only + // covering item is this item itself, which is legal overlap within one + // item. + if (!foundNewCase) { + iconstp->v3warn(CASEOVERLAP, + "Case item ignored: every matching value is covered " + "by an earlier condition\n" + << iconstp->warnContextPrimary() << '\n' + << firstOverlapConstp->warnOther() + << "... Location of previous condition\n" + << firstOverlapConstp->warnContextPrimary()); + reportedOverlap = true; + } } else { - const bool isCondWildcard = iconstp->num().isAnyXZ(); - V3Number nummask{itemp, iconstp->width()}; - nummask.opBitsNonX(iconstp->num()); - const uint32_t mask = nummask.toUInt(); - V3Number numval{itemp, iconstp->width()}; - numval.opBitsOne(iconstp->num()); - const uint32_t val = numval.toUInt(); - - uint32_t firstOverlap = 0; - const AstNode* overlappedCondp = nullptr; - bool foundHit = false; - for (uint32_t i = 0; i < numCases; ++i) { - if ((i & mask) == val) { - if (!m_valueItem[i]) { - m_valueItem[i] = icondp; - caseItemMap[icondp] = itemp; - foundHit = true; - } else if (!overlappedCondp) { - // Overlapping case item expressions in the - // same case item are legal - if (caseItemMap[m_valueItem[i]] != itemp) { - firstOverlap = i; - overlappedCondp = m_valueItem[i]; - m_caseNoOverlapsAllCovered = false; - } - } - } - } - if (!nodep->priorityPragma()) { - // If this case statement doesn't have the priority - // keyword, we want to warn on any overlap. - if (!reportedOverlap && overlappedCondp) { - std::ostringstream examplePattern; - if (isCondWildcard) { - examplePattern << " (example pattern 0x" << std::hex - << firstOverlap << ")"; - } - icondp->v3warn(CASEOVERLAP, - "Case conditions overlap" - << examplePattern.str() << "\n" - << icondp->warnContextPrimary() << '\n' - << overlappedCondp->warnOther() - << "... Location of overlapping condition\n" - << overlappedCondp->warnContextSecondary()); - reportedOverlap = true; - } - } else { - // If this is a priority case, we only want to complain - // if every possible value for this item is already hit - // by some other item. This is true if foundHit is - // false. - if (!reportedSubcase && !foundHit) { - icondp->v3warn(CASEOVERLAP, - "Case item ignored: every matching value is covered " - "by an earlier condition\n" - << icondp->warnContextPrimary() << '\n' - << overlappedCondp->warnOther() - << "... Location of previous condition\n" - << overlappedCondp->warnContextPrimary()); - reportedSubcase = true; - } - } - } - } - // Defaults were moved to last in the caseitem list by V3LinkDot - if (itemp->isDefault()) { // Case statement's default... Fill the table - for (uint32_t i = 0; i < numCases; ++i) { - if (!m_valueItem[i]) m_valueItem[i] = itemp; - } - caseItemMap[itemp] = itemp; - hasDefaultCase = true; - } - } - if (!hasDefaultCase) { - const AstEnumDType* const dtype = getEnumCompletionCheckDType(nodep); - if (dtype) { - if (!checkCaseEnumComplete(nodep, dtype)) { - // checkCaseEnumComplete has already warned of incompletion - m_caseNoOverlapsAllCovered = false; - return false; - } - } else { - for (uint32_t i = 0; i < numCases; ++i) { - if (!m_valueItem[i]) { // has uncovered case - if (!nodep->unique0Pragma()) - nodep->v3warn(CASEINCOMPLETE, "Case values incompletely covered " - "(example pattern 0x" - << std::hex << i << ")"); - m_caseIncomplete = true; - m_caseNoOverlapsAllCovered = false; - return false; + // If this case statement doesn't have the priority keyword, + // we want to warn on any overlap. + std::ostringstream examplePattern; + if (iconstp->num().isAnyXZ()) { + examplePattern << " (example pattern 0x" << std::hex << firstOverlapValue + << ")"; } + iconstp->v3warn(CASEOVERLAP, + "Case conditions overlap" + << examplePattern.str() << "\n" + << iconstp->warnContextPrimary() << '\n' + << firstOverlapConstp->warnOther() + << "... Location of overlapping condition\n" + << firstOverlapConstp->warnContextSecondary()); + reportedOverlap = true; } } } - if (m_caseItems <= 3 - // Avoid e.g. priority expanders from going crazy in expansion - || (m_caseWidth >= 8 && (m_caseItems <= (m_caseWidth + 1)))) { - return false; // Not worth simplifying - } - - // Convert valueItem from AstCaseItem* to the expression - // Not done earlier, as we may now have a nullptr because it's just a ";" NOP branch - for (uint32_t i = 0; i < numCases; ++i) { - if (AstNode* const condp = m_valueItem[i]) { - const AstCaseItem* const caseItemp = caseItemMap[condp]; - UASSERT_OBJ(caseItemp, condp, "caseItemp should exist"); - m_valueItem[i] = caseItemp->stmtsp(); - } - } - return true; // All is fine + // If there was no default, check exhaustiveness + m_caseDetails.exhaustive = hasDefault || checkExhaustive(nodep); + // Records now valid + m_caseDetailsValid = true; } - AstNode* replaceCaseFastRecurse(AstNodeExpr* cexprp, int msb, uint32_t upperValue) { - if (msb < 0) { - // There's no space for a IF. We know upperValue is thus down to a specific - // exact value, so just return the tree value - // Note can't clone here, as we're going to check for equivalence above - AstNode* const foundp = m_valueItem[upperValue]; - return foundp; - } else { - // Make left and right subtrees - // cexpr[msb:lsb] == 1 - AstNode* tree0p = replaceCaseFastRecurse(cexprp, msb - 1, upperValue); - AstNode* tree1p = replaceCaseFastRecurse( - cexprp, msb - 1, upperValue | (1UL << static_cast(msb))); + // Analyze case statement. Updates 'm_case*' members. Reports warnings. + void analyzeCase(AstCase* nodep) { + // Reset all analysis results + m_caseOpaque = false; + m_caseNConditions = 0; + m_caseDetailsValid = false; - if (tree0p == tree1p) { - // Same logic on both sides, so we can just return one of 'em - return tree0p; + AstNode* const caseExprp = nodep->exprp(); + + // Mark opaque if not a packed value - TODO: can this be a class? + if (caseExprp->isDouble() || caseExprp->isString()) m_caseOpaque = true; + + // Check each condition expression + for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) { + for (AstNode* condp = cip->condsp(); condp; condp = condp->nextp()) { + // Count conditions + ++m_caseNConditions; + // Mark opaque if non-constant condition + if (!VN_IS(condp, Const)) m_caseOpaque = true; } - // We could have a "checkerboard" with A B A B, we can use the same IF on both edges + } + + // Nothing else to do if not a packed type, or non-const conditions + if (m_caseOpaque) return; + + // If small enough, analyse details + if (caseExprp->width() <= CASE_DETAILS_MAX_WIDTH) analyzeCaseDetails(nodep); + } + + // TODO: should return AstNodeStmt after #6280 + AstNode* convertCaseFastRecurse(AstNodeExpr* cexprp, int msb, uint32_t upperValue) const { + // Base case: If reached the last bit, upperValue equals an exact value, just return + // the statements from that CaseItem. Note: Not cloning here as the caller will do + // an identity check. + if (msb < 0) return m_caseDetails.records[upperValue].stmtsp; + + // Recursive case: + // Make left and right subtrees assuming cexpr[msb] is 0 and 1 respectively + const uint32_t upperValue0 = upperValue; + const uint32_t upperValue1 = upperValue | (1UL << msb); + AstNode* tree0p = convertCaseFastRecurse(cexprp, msb - 1, upperValue0); + AstNode* tree1p = convertCaseFastRecurse(cexprp, msb - 1, upperValue1); + + // If same logic on both sides, we can just return one of them + if (tree0p == tree1p) return tree0p; + + // We could have a "checkerboard" with A B A B, we can use the same IF on both edges + { bool same = true; - for (uint32_t a = upperValue, b = (upperValue | (1UL << msb)); - a < (upperValue | (1UL << msb)); a++, b++) { - if (m_valueItem[a] != m_valueItem[b]) { + for (uint32_t a = upperValue0, b = upperValue1; a < upperValue1; ++a, ++b) { + if (m_caseDetails.records[a].stmtsp != m_caseDetails.records[b].stmtsp) { same = false; break; } @@ -373,161 +419,120 @@ class CaseVisitor final : public VNVisitor { VL_DO_DANGLING(tree1p->deleteTree(), tree1p); return tree0p; } - - // Must have differing logic, so make a selection - - // Case expressions can't be linked twice, so clone them - if (tree0p && !tree0p->user3()) tree0p = tree0p->cloneTree(true); - if (tree1p && !tree1p->user3()) tree1p = tree1p->cloneTree(true); - - // Alternate scheme if we ever do multiple bits at a time: - // V3Number nummask (cexprp, cexprp->width(), (1UL<fileline(), cexprp->cloneTreePure(false), - // new AstConst(cexprp->fileline(), nummask)); - AstNodeExpr* const and1p - = new AstSel{cexprp->fileline(), cexprp->cloneTreePure(false), msb, 1}; - AstNodeExpr* const eqp - = new AstNeq{cexprp->fileline(), new AstConst{cexprp->fileline(), 0}, and1p}; - AstIf* const ifp = new AstIf{cexprp->fileline(), eqp, tree1p, tree0p}; - ifp->user3(1); // So we don't bother to clone it - return ifp; } + + // Must have differing logic. Test the bit and convert to an If. + + // Clone if needed + if (tree0p && tree0p->backp()) tree0p = tree0p->cloneTree(true); + if (tree1p && tree1p->backp()) tree1p = tree1p->cloneTree(true); + // Create the If statement + FileLine* const flp = cexprp->fileline(); + AstNodeExpr* const condp = new AstSel{flp, cexprp->cloneTreePure(false), msb, 1}; + AstIf* const ifp = new AstIf{flp, condp, tree1p, tree0p}; + return ifp; } - void replaceCaseFast(AstCase* nodep) { - // CASEx(cexpr,.... - // -> tree of IF(msb, IF(msb-1, 11, 10) - // IF(msb-1, 01, 00)) - AstNodeExpr* cexprp; - AstExprStmt* cexprStmtp = nullptr; - if (nodep->exprp()->isPure()) { - cexprp = nodep->exprp()->unlinkFrBack(); - } else { - cexprStmtp = VN_AS(nodep->exprp()->unlinkFrBack(), ExprStmt); - cexprp = cexprStmtp->resultp()->cloneTreePure(false); - } - - if (debug() >= 9) { // LCOV_EXCL_START - for (uint32_t i = 0; i < (1UL << m_caseWidth); ++i) { - if (const AstNode* const itemp = m_valueItem[i]) { - UINFO(9, "Value " << std::hex << i << " " << itemp); - } - } - } // LCOV_EXCL_STOP - - // Handle any assertions - replaceCaseParallel(nodep, m_caseNoOverlapsAllCovered); - - AstNode::user3ClearTree(); - AstNode* ifrootp = replaceCaseFastRecurse(cexprp, m_caseWidth - 1, 0UL); - if (cexprStmtp) { - cexprStmtp->resultp()->unlinkFrBack()->deleteTree(); - AstIf* const ifp = VN_AS(ifrootp, If); - cexprStmtp->resultp(ifp->condp()->unlinkFrBack()); - ifp->condp(cexprStmtp); - m_needToClearCache = true; - } - - // Case expressions can't be linked twice, so clone them - if (ifrootp && !ifrootp->user3()) ifrootp = ifrootp->cloneTree(true); - - if (ifrootp) { - nodep->replaceWith(ifrootp); - } else { - nodep->unlinkFrBack(); - } - VL_DO_DANGLING(nodep->deleteTree(), nodep); - VL_DO_DANGLING(cexprp->deleteTree(), cexprp); - UINFOTREE(9, ifrootp, "", "_simp"); + // CASEx(cexpr,.... + // -> tree of IF(msb, IF(msb-1, 11, 10) + // IF(msb-1, 01, 00)) + // TODO: should return AstNodeStmt after #6280 + AstNode* convertCaseFast(AstCase* nodep) const { + const int caseWidth = nodep->exprp()->width(); + AstNode* const ifrootp = convertCaseFastRecurse(nodep->exprp(), caseWidth - 1, 0UL); + return ifrootp && ifrootp->backp() ? ifrootp->cloneTree(true) : ifrootp; } - void replaceCaseComplicated(AstCase* nodep) { - // CASEx(cexpr,ITEM(icond1,istmts1),ITEM(icond2,istmts2),ITEM(default,istmts3)) - // -> IF((cexpr==icond1),istmts1, - // IF((EQ (AND MASK cexpr) (AND MASK icond1) - // ,istmts2, istmts3 - AstNodeExpr* cexprp; - AstExprStmt* cexprStmtp = nullptr; - if (nodep->exprp()->isPure()) { - cexprp = nodep->exprp()->unlinkFrBack(); - } else { - cexprStmtp = VN_AS(nodep->exprp(), ExprStmt)->unlinkFrBack(); - cexprp = cexprStmtp->resultp()->cloneTreePure(false); - } - // We'll do this in two stages. First stage, convert the conditions to - // the appropriate IF AND terms. - UINFOTREE(9, nodep, "", "_comp_IN::"); - bool hadDefault = false; + // Convet case statement using generic if/else tree + // CASEx(cexpr,ITEM(icond1,istmts1),ITEM(icond2,istmts2),ITEM(default,istmts3)) + // -> IF((cexpr==icond1),istmts1, + // IF((EQ (AND MASK cexpr) (AND MASK icond1) + // ,istmts2, istmts3 + // TODO: should return AstNodeStmt after #6280 + AstNode* convertCaseGeneric(AstCase* nodep) const { + // We'll do this in two stages. + // First stage, convert the conditions to the appropriate IF AND terms. + bool hasDefault = false; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), CaseItem)) { - if (!itemp->condsp()) { - // Default clause. Just make true, we'll optimize it away later - itemp->addCondsp(new AstConst{itemp->fileline(), AstConst::BitTrue{}}); - hadDefault = true; - } else { - // Expressioned clause - AstNodeExpr* icondNextp = nullptr; - AstNodeExpr* ifexprp = nullptr; // If expression to test - for (AstNodeExpr* icondp = itemp->condsp(); icondp; icondp = icondNextp) { - icondNextp = VN_AS(icondp->nextp(), NodeExpr); - icondp->unlinkFrBack(); - AstNodeExpr* condp = nullptr; // Default is to use and1p/and2p - AstConst* const iconstp = VN_CAST(icondp, Const); - if (iconstp && neverItem(nodep, iconstp)) { - // X in casez can't ever be executed - VL_DO_DANGLING(icondp->deleteTree(), icondp); - VL_DANGLING(iconstp); - // For simplicity, make expression that is not equal, and let later - // optimizations remove it - condp = new AstConst{itemp->fileline(), AstConst::BitFalse{}}; - } else if (AstInsideRange* const irangep = VN_CAST(icondp, InsideRange)) { - // Similar logic in V3Width::visit(AstInside) - condp = irangep->newAndFromInside(cexprp->cloneTreePure(true), - irangep->lhsp()->unlinkFrBack(), - irangep->rhsp()->unlinkFrBack()); - VL_DO_DANGLING2(icondp->deleteTree(), icondp, irangep); - } else if (iconstp && iconstp->num().isFourState() - && (nodep->casex() || nodep->casez() || nodep->caseInside())) { - V3Number nummask{itemp, iconstp->width()}; - nummask.opBitsNonX(iconstp->num()); - V3Number numval{itemp, iconstp->width()}; - numval.opBitsOne(iconstp->num()); - AstNodeExpr* const and1p - = new AstAnd{itemp->fileline(), cexprp->cloneTreePure(false), - new AstConst{itemp->fileline(), nummask}}; - AstNodeExpr* const and2p = new AstAnd{ - itemp->fileline(), new AstConst{itemp->fileline(), numval}, - new AstConst{itemp->fileline(), nummask}}; - VL_DO_DANGLING(icondp->deleteTree(), icondp); - VL_DANGLING(iconstp); - condp = AstEq::newTyped(itemp->fileline(), and1p, and2p); - } else { - // Not a caseX mask, we can build CASEEQ(cexpr icond) - AstNodeExpr* const and1p = cexprp->cloneTreePure(false); - AstNodeExpr* const and2p = icondp; - condp = AstEq::newTyped(itemp->fileline(), and1p, and2p); - } - if (!ifexprp) { - ifexprp = condp; - } else { - ifexprp = new AstLogOr{itemp->fileline(), ifexprp, condp}; - } - } - // Replace expression in tree - itemp->addCondsp(ifexprp); + FileLine* const flp = itemp->fileline(); + + // Default clause. Just make true, we'll optimize it away later + if (itemp->isDefault()) { + itemp->addCondsp(new AstConst{flp, AstConst::BitTrue{}}); + hasDefault = true; + continue; } + + // Regular clause. Construct the condition expression. + AstNodeExpr* newCondp = nullptr; + for (AstNodeExpr *itemExprp = itemp->condsp(), *nextp; itemExprp; itemExprp = nextp) { + nextp = VN_AS(itemExprp->nextp(), NodeExpr); + itemExprp->unlinkFrBack(); + + // If case never matches, ignore it + if (neverItem(nodep, itemExprp)) { + VL_DO_DANGLING(itemExprp->deleteTree(), itemExprp); + continue; + } + + // Compute the term to add to the condition expression + AstNodeExpr* const termp = [&]() -> AstNodeExpr* { + // Will need a copy of the caseExpr regardless + AstNodeExpr* const caseExprp = nodep->exprp()->cloneTreePure(false); + + // InsideRange: Similar logic in V3Width::visit(AstInside) + if (AstInsideRange* const itemRangep = VN_CAST(itemExprp, InsideRange)) { + AstNodeExpr* const resultp = itemRangep->newAndFromInside( // + caseExprp, // + itemRangep->lhsp()->unlinkFrBack(), + itemRangep->rhsp()->unlinkFrBack()); + VL_DO_DANGLING2(itemExprp->deleteTree(), itemExprp, itemRangep); + return resultp; + } + + // Check if we need to perform a wildcard match, this needs masking + if (AstConst* const itemConstp = VN_CAST(itemExprp, Const)) { + // TODO: 4-state will need to fix this + if (itemConstp->num().isFourState() + && (nodep->casex() || nodep->casez() || nodep->caseInside())) { + // Wildcard match, make 'caseExpr' & 'mask' == 'itemExpr' & 'mask' + V3Number numMask{itemp, itemConstp->width()}; + numMask.opBitsNonX(itemConstp->num()); + V3Number numOne{itemp, itemConstp->width()}; + numOne.opBitsOne(itemConstp->num()); + V3Number numRhs{itemp, itemConstp->width()}; + numRhs.opAnd(numOne, numMask); + VL_DO_DANGLING2(itemExprp->deleteTree(), itemExprp, itemConstp); + return AstEq::newTyped( + flp, // + new AstConst{flp, numRhs}, + new AstAnd{flp, caseExprp, new AstConst{flp, numMask}}); + } + } + + // Regular case, use simple equality comparison + return AstEq::newTyped(flp, caseExprp, itemExprp); + }(); + + // 'Or' new term with previous terms + newCondp = newCondp ? new AstLogOr{flp, newCondp, termp} : termp; + } + // Replace expression in tree. Needs to be non-null, so add a constant false if needed + if (!newCondp) newCondp = new AstConst{flp, AstConst::BitFalse{}}; + itemp->addCondsp(newCondp); } - VL_DO_DANGLING(cexprp->deleteTree(), cexprp); - if (!hadDefault) { - // If there was no default, add a empty one, this greatly simplifies below code - // and constant propagation will just eliminate it for us later. + + // If there was no default, add a empty one, this greatly simplifies below code + // and constant propagation will just eliminate it for us later. + if (!hasDefault) { nodep->addItemsp(new AstCaseItem{ nodep->fileline(), new AstConst{nodep->fileline(), AstConst::BitTrue{}}, nullptr}); } - UINFOTREE(9, nodep, "", "_comp_COND"); + // Now build the IF statement tree - // The tree can be quite huge. Pull ever group of 8 out, and make a OR tree. + // The tree can be quite huge. Pull every group of 8 out, and make a OR tree. // This reduces the depth for the bottom elements, at the cost of // some of the top elements. If we ever have profiling data, we // should pull out the most common item from here and instead make @@ -538,8 +543,11 @@ class CaseVisitor final : public VNVisitor { AstIf* itemnextp = nullptr; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), CaseItem)) { - AstNode* const istmtsp = itemp->stmtsp(); // Maybe null -- no action. + + // Grab the statements from this item. May be empty. + AstNode* const istmtsp = itemp->stmtsp(); if (istmtsp) istmtsp->unlinkFrBackWithNext(); + // Expressioned clause AstNodeExpr* const ifexprp = itemp->condsp()->unlinkFrBack(); { // Prepare for next group @@ -575,65 +583,76 @@ class CaseVisitor final : public VNVisitor { itemnextp = newp; } } - UINFOTREE(9, nodep, "", "_comp_TREE"); - // Handle any assertions - replaceCaseParallel(nodep, false); - // Replace the CASE... with IF... - if (grouprootp) { - UINFOTREE(9, grouprootp, "", "_new"); - nodep->replaceWith(grouprootp); - if (cexprStmtp) { - pushDeletep(cexprStmtp->resultp()->unlinkFrBack()); - cexprStmtp->resultp(grouprootp->condp()->unlinkFrBack()); - grouprootp->condp(cexprStmtp); - m_needToClearCache = true; - } - } else { - nodep->unlinkFrBack(); - } - VL_DO_DANGLING(nodep->deleteTree(), nodep); + return grouprootp; } - void replaceCaseParallel(AstCase* nodep, bool noOverlapsAllCovered) { - // Take the notParallelp tree under the case statement created by V3Assert - // If the statement was proven to have no overlaps and all cases - // covered, we're done with it. - // Else, convert to a normal statement parallel with the case statement. - if (nodep->notParallelp() && !noOverlapsAllCovered) { - AstNode* const parp = nodep->notParallelp()->unlinkFrBackWithNext(); - nodep->addNextHere(parp); + // Convert the given case statement to a representation not using AstCase + // TODO: should return AstNodeStmt after #6280 + AstNode* convertCase(AstCase* nodep, Stats& stats) const { + // Determine if we should use the fast bitwise branching tree method + const bool useFastBitTree = [&]() { + // Not if disabled + if (!v3Global.opt.fCase()) return false; + // Can't do it without the detailed analysis + if (!m_caseDetailsValid) return false; + // Can't do it if not exhaustive + if (!m_caseDetails.exhaustive) return false; + // Not worth doing if there are few conditions + if (m_caseNConditions <= 3) return false; + // Avoid e.g. priority expanders from going crazy in expansion + const size_t caseWidth = nodep->exprp()->width(); + if (caseWidth >= 8 && m_caseNConditions <= (caseWidth + 1)) return false; + // Otherwise use the bit tree + return true; + }(); + if (useFastBitTree) { + ++stats.caseFast; + return convertCaseFast(nodep); } - } - bool neverItem(const AstCase* casep, const AstConst* itemp) { - // Xs in case or casez are impossible due to two state simulations - if (casep->casex()) { - } else if (casep->casez() || casep->caseInside()) { - if (itemp->num().isAnyX()) return true; - } else { - if (itemp->num().isFourState()) return true; + // Convert using the generic if/else tree method + ++stats.caseGeneric; + // If a case statement is exhaustive, presume signals involved aren't forming a latch + // TODO: this is broken, but it is as was before + if (m_alwaysp && (!m_caseDetailsValid || m_caseDetails.exhaustive)) { + m_alwaysp->fileline()->warnOff(V3ErrorCode::LATCH, true); } - return false; + return convertCaseGeneric(nodep); } // VISITORS void visit(AstCase* nodep) override { - VL_RESTORER(m_caseIncomplete); - { CaseLintVisitor{nodep}; } + UASSERT_OBJ(nodep->exprp()->isPure(), nodep, + "Impure case expression should have been removed by V3LiftExpr"); + + CaseLintVisitor::apply(nodep); + + // Convert any children first iterateChildren(nodep); - UINFOTREE(9, nodep, "", "case_old"); - if (isCaseTreeFast(nodep) && v3Global.opt.fCase()) { - // It's a simple priority encoder or complete statement - // we can make a tree of statements to avoid extra comparisons - ++m_statCaseFast; - VL_DO_DANGLING(replaceCaseFast(nodep), nodep); - } else { - // If a case statement is whole, presume signals involved aren't forming a latch - if (m_alwaysp && !m_caseIncomplete) - m_alwaysp->fileline()->warnOff(V3ErrorCode::LATCH, true); - ++m_statCaseSlow; - VL_DO_DANGLING(replaceCaseComplicated(nodep), nodep); + + // Analyze this case statement + analyzeCase(nodep); + + // Take the 'notParallelp' statements under the case statement created by V3Assert. + // If the statement was proven to have no overlaps and all cases covered, + // it can be removed. Otherwise insert the assertion after the case statement. + if (AstNode* const stmtp = nodep->notParallelp()) { + stmtp->unlinkFrBackWithNext(); + if (m_caseDetailsValid && m_caseDetails.exhaustive && m_caseDetails.noOverlaps) { + ++m_stats.provenAssertions; + VL_DO_DANGLING(stmtp->deleteTree(), stmtp); + } else { + nodep->addNextHere(stmtp); + } } + + // Convert the case statement and replace the original + if (AstNode* const replacementp = convertCase(nodep, m_stats)) { + nodep->replaceWith(replacementp); + } else { + nodep->unlinkFrBack(); + } + VL_DO_DANGLING(nodep->deleteTree(), nodep); } //-------------------- void visit(AstAlways* nodep) override { @@ -645,14 +664,11 @@ class CaseVisitor final : public VNVisitor { public: // CONSTRUCTORS - explicit CaseVisitor(AstNetlist* nodep) { - for (auto& itr : m_valueItem) itr = nullptr; - iterate(nodep); - if (m_needToClearCache) VIsCached::clearCacheTree(); - } + explicit CaseVisitor(AstNetlist* nodep) { iterate(nodep); } ~CaseVisitor() override { - V3Stats::addStat("Optimizations, Cases parallelized", m_statCaseFast); - V3Stats::addStat("Optimizations, Cases complex", m_statCaseSlow); + V3Stats::addStat("Optimizations, Cases parallelized", m_stats.caseFast); + V3Stats::addStat("Optimizations, Cases complex", m_stats.caseGeneric); + V3Stats::addStat("Optimizations, Cases proven assertions", m_stats.provenAssertions); } }; @@ -666,5 +682,5 @@ void V3Case::caseAll(AstNetlist* nodep) { } void V3Case::caseLint(AstGenCase* nodep) { UINFO(4, __FUNCTION__ << ": "); - { CaseLintVisitor{nodep}; } + CaseLintVisitor::apply(nodep); } diff --git a/src/V3CfgLiveVariables.cpp b/src/V3CfgLiveVariables.cpp index 79033af9d..162e0837d 100644 --- a/src/V3CfgLiveVariables.cpp +++ b/src/V3CfgLiveVariables.cpp @@ -51,28 +51,18 @@ class CfgLiveVariables final : VNVisitorConst { // METHODS - // This is to match DFG, but can be extended independently - eventually should handle all - static bool isSupportedPackedDType(const AstNodeDType* dtypep) { - dtypep = dtypep->skipRefp(); + static bool isPacked(const AstVarScope* vscp) { + AstNodeDType* const dtypep = vscp->dtypep()->skipRefp(); if (const AstBasicDType* const typep = VN_CAST(dtypep, BasicDType)) { return typep->keyword().isIntNumeric(); } - if (const AstPackArrayDType* const typep = VN_CAST(dtypep, PackArrayDType)) { - return isSupportedPackedDType(typep->subDTypep()); - } + if (VN_IS(dtypep, PackArrayDType)) return true; if (const AstNodeUOrStructDType* const typep = VN_CAST(dtypep, NodeUOrStructDType)) { return typep->packed(); } return false; } - // Check and return if variable is incompatible - bool incompatible(AstVarScope* vscp) { - if (!isSupportedPackedDType(vscp->dtypep())) return true; - if (vscp->varp()->ignoreSchedWrite()) return true; - return false; - } - void updateGen(AstNode* nodep) { UASSERT_OBJ(!m_abort, nodep, "Doing useless work"); m_abort = nodep->exists([&](const AstVarRef* refp) { @@ -80,8 +70,6 @@ class CfgLiveVariables final : VNVisitorConst { if (refp->access().isWriteOnly()) return false; // Grab referenced variable AstVarScope* const vscp = refp->varScopep(); - // Bail if not a compatible type - if (incompatible(vscp)) return true; // Add to gen set, if not killed - assume whole variable read if (m_currp->m_kill.count(vscp)) return false; m_currp->m_gen.emplace(vscp); @@ -96,11 +84,13 @@ class CfgLiveVariables final : VNVisitorConst { if (refp->access().isReadOnly()) return false; // Grab referenced variable AstVarScope* const vscp = refp->varScopep(); - // Bail if not a compatible type - if (incompatible(vscp)) return true; - // If whole written, add to kill set - if (refp->nextp()) return false; - if (VN_IS(refp->abovep(), Sel)) return false; + // Safety case: bail on variables with unusual semantics + if (vscp->varp()->ignoreSchedWrite()) return true; + // If not a packed variable, assume it's not a whole update, don't add to kill set + if (!isPacked(vscp)) return false; + // If packed, and updated partially, don't add to kill set + if (refp->backp()->nextp() != refp && VN_IS(refp->abovep(), Sel)) return false; + // Whole variable is writen, add to kill set m_currp->m_kill.emplace(vscp); return false; }); @@ -198,7 +188,7 @@ public: CfgLiveVariables analysis{cfg}; // If failed, return nullptr if (analysis.m_abort) return nullptr; - // Gather variables live in to the entry blockstd::unique_ptr> + // Gather variables live in to the entry block const std::unordered_set& lin = analysis.m_blockState[cfg.enter()].m_liveIn; std::vector* const resultp = new std::vector{lin.begin(), lin.end()}; diff --git a/src/V3Const.cpp b/src/V3Const.cpp index de2e1d64f..2ff6a0a80 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -973,6 +973,96 @@ class ConstVisitor final : public VNVisitor { return !numv.isNumber() ? numv : V3Number{nodep, nodep->width(), numv}; } + static bool lowerAsFixedAggregate(const AstNodeDType* const dtypep) { + return dtypep->isStreamableFixedAggregate() && dtypep->containsUnpackedStruct(); + } + + AstStructSel* newStructSel(AstNodeExpr* const fromp, const AstMemberDType* const itemp) { + AstStructSel* const selp = new AstStructSel{fromp->fileline(), fromp, itemp->name()}; + selp->dtypeFrom(itemp->dtypep()); + return selp; + } + + void collectFixedAggregateTerms(AstNodeExpr* const fromp, std::vector& 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 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 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(dstWidth - srcWidth)}, + dstWidth}; + srcWidth = dstWidth; + } + AstNodeAssign* newp = nullptr; + int offset = 0; + for (size_t i = 0; i < termps.size(); ++i) { + AstNodeExpr* const termp = termps[i]; + const int width = termp->dtypep()->widthStream(); + const int lsb = srcWidth - offset - width; + offset += width; + AstNodeExpr* rhsp + = new AstSel{srcp->fileline(), srcp->cloneTreePure(false), lsb, width}; + if (termp->dtypep()->skipRefp()->isDouble()) { + rhsp = new AstBitsToRealD{rhsp->fileline(), rhsp}; + } + AstNodeAssign* const assignp = nodep->cloneType(termp, rhsp); + assignp->dtypeFrom(termp); + newp = AstNode::addNext(newp, assignp); + } + nodep->addNextHere(newp); + VL_DO_DANGLING(pushDeletep(srcp), srcp); + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + } + bool operandConst(const AstNode* nodep) { return VN_IS(nodep, Const); } bool operandAsvConst(const AstNode* nodep) { // BIASV(CONST, BIASV(CONST,...)) -> BIASV( BIASV_CONSTED(a,b), ...) @@ -2393,6 +2483,25 @@ class ConstVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(conp), conp); // Further reduce, either node may have more reductions. return true; + } else if (m_doV && VN_IS(nodep->rhsp(), CvtPackedToArray) + && lowerAsFixedAggregate(nodep->lhsp()->dtypep())) { + AstCvtPackedToArray* const cvtp + = VN_AS(nodep->rhsp(), CvtPackedToArray)->unlinkFrBack(); + AstNodeExpr* srcp = cvtp->fromp()->unlinkFrBack(); + if (lowerAsFixedAggregate(srcp->dtypep())) { + srcp = packFixedAggregate(srcp); + } else if (AstNodeStream* const streamp = VN_CAST(srcp, NodeStream)) { + AstNodeExpr* const streamSrcp = streamp->lhsp(); + if (lowerAsFixedAggregate(streamSrcp->dtypep())) { + AstNodeExpr* const packedp = packFixedAggregate(streamSrcp->unlinkFrBack()); + streamp->lhsp(packedp); + streamp->dtypeSetLogicUnsized(packedp->width(), packedp->widthMin(), + VSigning::UNSIGNED); + } + } + VL_DO_DANGLING(pushDeletep(cvtp), cvtp); + replaceAssignToFixedAggregate(nodep, nodep->lhsp()->unlinkFrBack(), srcp); + return true; } else if (m_doV && VN_IS(nodep->rhsp(), StreamR) && !VN_IS(nodep->lhsp()->dtypep()->skipRefp(), QueueDType)) { // The right-streaming operator on rhs of assignment does not @@ -2402,7 +2511,9 @@ class ConstVisitor final : public VNVisitor { AstNodeExpr* srcp = streamp->lhsp()->unlinkFrBack(); AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp(); const AstNodeDType* const dstDTypep = nodep->lhsp()->dtypep()->skipRefp(); - if (VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)) { + if (lowerAsFixedAggregate(srcDTypep)) { + srcp = packFixedAggregate(srcp); + } else if (VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)) { if (VN_IS(dstDTypep, QueueDType) || VN_IS(dstDTypep, DynArrayDType)) { int srcElementBits = 0; if (const AstNodeDType* const elemDtp = srcDTypep->subDTypep()) { @@ -2429,6 +2540,19 @@ class ConstVisitor final : public VNVisitor { srcp = new AstShiftL{srcp->fileline(), srcp, new AstConst{srcp->fileline(), offset}, packedBits}; } + if (!VN_IS(dstDTypep, UnpackArrayDType) && !VN_IS(dstDTypep, QueueDType) + && !VN_IS(dstDTypep, DynArrayDType)) { + const int sWidth = srcp->width(); + const int dWidth = nodep->lhsp()->width(); + if (sWidth < dWidth) { + AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp}; + extendp->dtypeSetLogicSized(dWidth, VSigning::UNSIGNED); + srcp = new AstShiftL{ + srcp->fileline(), extendp, + new AstConst{srcp->fileline(), static_cast(dWidth - sWidth)}, + dWidth}; + } + } nodep->rhsp(srcp); VL_DO_DANGLING(pushDeletep(streamp), streamp); // Further reduce, any of the nodes may have more reductions. @@ -2438,7 +2562,7 @@ class ConstVisitor final : public VNVisitor { AstNodeExpr* streamp = nodep->lhsp()->unlinkFrBack(); AstNodeExpr* const dstp = VN_AS(streamp, StreamL)->lhsp()->unlinkFrBack(); AstNodeDType* const dstDTypep = dstp->dtypep()->skipRefp(); - AstNodeExpr* const srcp = nodep->rhsp()->unlinkFrBack(); + AstNodeExpr* srcp = nodep->rhsp()->unlinkFrBack(); const AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp(); // Handle unpacked/queue/dynarray source -> queue/dynarray dest via // CvtArrayToArray (StreamL reverses, so reverse=true) @@ -2466,6 +2590,7 @@ class ConstVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(streamp), streamp); return true; } + if (lowerAsFixedAggregate(srcDTypep)) srcp = packFixedAggregate(srcp); const int sWidth = srcp->width(); const int dWidth = dstp->width(); // Connect the rhs to the stream operator and update its width @@ -2556,7 +2681,7 @@ class ConstVisitor final : public VNVisitor { } else { // Source narrower than destination: left-justify by shifting left. // The right stream operator packs left-to-right, so remaining - // LSBs are zero-filled (IEEE 1800-2023 11.4.14.2). + // LSBs are zero-filled (IEEE 1800-2023 11.4.14.3). if (!VN_IS(srcp->dtypep()->skipRefp(), QueueDType)) { AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp}; extendp->dtypeSetLogicSized(dWidth, VSigning::UNSIGNED); @@ -2580,6 +2705,13 @@ class ConstVisitor final : public VNVisitor { AstNodeExpr* srcp = streamp->lhsp(); const AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp(); AstNodeDType* const dstDTypep = nodep->lhsp()->dtypep()->skipRefp(); + if (lowerAsFixedAggregate(srcDTypep)) { + AstNodeExpr* const packedp = packFixedAggregate(srcp->unlinkFrBack()); + streamp->lhsp(packedp); + streamp->dtypeSetLogicUnsized(packedp->width(), packedp->widthMin(), + VSigning::UNSIGNED); + srcp = packedp; + } if ((VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType) || VN_IS(srcDTypep, UnpackArrayDType))) { if (VN_IS(dstDTypep, QueueDType) || VN_IS(dstDTypep, DynArrayDType)) { diff --git a/src/V3Control.cpp b/src/V3Control.cpp index e95e3c086..3ac7aa727 100644 --- a/src/V3Control.cpp +++ b/src/V3Control.cpp @@ -182,7 +182,6 @@ class V3ControlFTask final { V3ControlVarResolver m_params; // Parameters in function/task V3ControlVarResolver m_ports; // Ports in function/task V3ControlVarResolver m_vars; // Variables in function/task - bool m_isolate = false; // Isolate function return bool m_noinline = false; // Don't inline function/task bool m_public = false; // Public function/task @@ -190,7 +189,6 @@ public: V3ControlFTask() = default; void update(const V3ControlFTask& f) { // Don't overwrite true with false - if (f.m_isolate) m_isolate = true; if (f.m_noinline) m_noinline = true; if (f.m_public) m_public = true; m_params.update(f.m_params); @@ -203,7 +201,6 @@ public: V3ControlVarResolver& ports() { return m_ports; } V3ControlVarResolver& vars() { return m_vars; } - void setIsolate(bool set) { m_isolate = set; } void setNoInline(bool set) { m_noinline = set; } void setPublic(bool set) { m_public = set; } @@ -212,8 +209,6 @@ public: ftaskp->addStmtsp(new AstPragma{ftaskp->fileline(), VPragmaType::NO_INLINE_TASK}); if (m_public) ftaskp->addStmtsp(new AstPragma{ftaskp->fileline(), VPragmaType::PUBLIC_TASK}); - // Only functions can have isolate (return value) - if (VN_IS(ftaskp, Func)) ftaskp->attrIsolateAssign(m_isolate); } }; @@ -981,13 +976,7 @@ void V3Control::addVarAttr(FileLine* fl, const string& module, const string& fta // Semantics: Most of the attributes operate on signals if (pattern.empty()) { - if (attr == VAttrType::VAR_ISOLATE_ASSIGNMENTS) { - if (ftask.empty()) { - fl->v3error("isolate_assignments only applies to signals or functions/tasks"); - } else { - V3ControlResolver::s().modules().at(module).ftasks().at(ftask).setIsolate(true); - } - } else if (attr == VAttrType::VAR_PUBLIC) { + if (attr == VAttrType::VAR_PUBLIC) { if (ftask.empty()) { // public module, this is the only exception from var here V3ControlResolver::s().modules().at(module).addModulePragma( diff --git a/src/V3Coverage.cpp b/src/V3Coverage.cpp index 63cb65b8a..12bb973dc 100644 --- a/src/V3Coverage.cpp +++ b/src/V3Coverage.cpp @@ -166,10 +166,13 @@ class CoverageVisitor final : public VNVisitor { // METHODS + // Return non-nullptr reason if this variable shouldn't have toggle coverage const char* varIgnoreToggle(const AstVar* nodep) { - // Return true if this shouldn't be traced - // See also similar rule in V3TraceDecl::varIgnoreTrace - if (!nodep->isToggleCoverable()) return "Not relevant signal type"; + const bool cover = nodep->isIO() || (nodep->isSignal() && nodep->isBitLogic()); + if (!cover) return "Not relevant signal"; + if (nodep->isConst()) return "Signal is constant"; + if (nodep->isDouble()) return "Signal is double"; + if (nodep->isString()) return "Signal is string"; if (!v3Global.opt.coverageUnderscore()) { const string prettyName = nodep->prettyName(); if (prettyName[0] == '_') return "Leading underscore"; @@ -530,8 +533,10 @@ class CoverageVisitor final : public VNVisitor { newent.cleanup(); } } - } else if (VN_IS(dtypep, QueueDType)) { + } else if (VN_IS(dtypep, QueueDType) || VN_IS(dtypep, AssocArrayDType) + || VN_IS(dtypep, WildcardArrayDType)) { // Not covered + varp->v3warn(COVERIGN, "Coverage ignored for type " << dtypep->prettyTypeName()); } else { dtypep->v3fatalSrc("Unexpected node data type in toggle coverage generation: " << dtypep->prettyTypeName()); diff --git a/src/V3Covergroup.cpp b/src/V3Covergroup.cpp index 727b5689c..0d43e9f60 100644 --- a/src/V3Covergroup.cpp +++ b/src/V3Covergroup.cpp @@ -68,6 +68,9 @@ class FunctionalCoverageVisitor final : public VNVisitor { , crossBins{cb} {} }; std::vector m_binInfos; // All bins in current covergroup + std::set m_crossedCpNames; // Coverpoints referenced by a cross (kept legacy) + std::vector m_convCpVars; // VlCoverpoint members of converted coverpoints + AstCDType* m_vlCoverpointDTypep = nullptr; // Shared "VlCoverpoint" C++ member type VMemberMap m_memberMap; // Member names cached for fast lookup @@ -88,6 +91,17 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Clear bin info for this covergroup (deleting any orphaned cross pseudo-bins) clearBinInfos(); + // Coverpoints referenced by a cross keep the legacy per-bin-member path (the cross + // reads those members); collect their names before they are consumed by the cross. + m_crossedCpNames.clear(); + m_convCpVars.clear(); + for (AstCoverCross* crossp : m_coverCrosses) { + for (AstNode* itemp = crossp->itemsp(); itemp; itemp = itemp->nextp()) { + if (const AstCoverpointRef* const refp = VN_CAST(itemp, CoverpointRef)) + m_crossedCpNames.insert(refp->name()); + } + } + // For each coverpoint, generate sampling code for (AstCoverpoint* cpp : m_coverpoints) generateCoverpointCode(cpp); @@ -504,6 +518,13 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Create implicit automatic bins if no regular bins exist createImplicitAutoBins(coverpointp, exprp, autoBinMax); + // Eligible coverpoints route through the VlCoverpoint runtime; the rest (cross-fed or + // transition-bearing) keep the legacy per-bin-member path below. + if (coverpointConvertible(coverpointp)) { + generateConvertedCoverpoint(coverpointp, exprp, atLeastValue); + return; + } + // Generate member variables and matching code for each bin // Process in two passes: first non-default bins, then default bins std::vector defaultBins; @@ -595,47 +616,179 @@ class FunctionalCoverageVisitor final : public VNVisitor { UINFO(4, " Successfully added if statement for bin: " << binp->name()); } + // Build the condition under which a default bin matches: NOT(OR of all normal bins). + AstNodeExpr* buildDefaultCondition(AstCoverpoint* coverpointp, AstNodeExpr* exprp, + FileLine* fl) { + AstNodeExpr* anyBinMatchp = nullptr; + for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { + AstCoverBin* const cbinp = VN_AS(binp, CoverBin); + if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT + || cbinp->binsType() == VCoverBinsType::BINS_IGNORE + || cbinp->binsType() == VCoverBinsType::BINS_ILLEGAL) + continue; + AstNodeExpr* const binCondp = buildBinCondition(cbinp, exprp); + UASSERT_OBJ(binCondp, cbinp, + "buildBinCondition returned nullptr for non-ignore/non-illegal bin"); + anyBinMatchp = anyBinMatchp ? new AstOr{fl, anyBinMatchp, binCondp} : binCondp; + } + return anyBinMatchp ? static_cast(new AstNot{fl, anyBinMatchp}) + : static_cast(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 extractArrayValues(AstCoverBin* arrayBinp, AstNodeExpr* exprp) { + std::vector 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(exprp->width()), + static_cast(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 namerStmts; + std::vector 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 values = extractArrayValues(cbinp, exprp); + namerStmts.push_back(makeNamer(cpVarp, cbinp, static_cast(values.size()))); + for (AstNodeExpr* valuep : values) { + emitConvHitIf(coverpointp, cbinp, cpVarp, idx++, + new AstEq{cbinp->fileline(), exprp->cloneTree(false), valuep}); + } + } else { + namerStmts.push_back(makeNamer(cpVarp, cbinp, -1)); + // buildBinCondition is null for 'ignore_bins = default' (no ranges); the bin + // still gets a reserved slot (recorded, never incremented). + if (AstNodeExpr* const condp = buildBinCondition(cbinp, exprp)) + emitConvHitIf(coverpointp, cbinp, cpVarp, idx, condp); + ++idx; + } + } + for (AstCoverBin* const defBinp : defaultBins) { + namerStmts.push_back(makeNamer(cpVarp, defBinp, -1)); + emitConvHitIf(coverpointp, defBinp, cpVarp, idx++, + buildDefaultCondition(coverpointp, exprp, defBinp->fileline())); + } + + // Constructor: init (allocates), namers, then registration (under --coverage) + const std::string hier = m_covergroupp->name() + "." + coverpointp->name(); + AstCStmt* const initp = new AstCStmt{fl}; + initp->add(memberRef(fl, cpVarp)); + initp->add(".init(\"" + hier + "\", " + std::to_string(atLeastValue) + ", " + + std::to_string(idx) + ");"); + m_constructorp->addStmtsp(initp); + for (AstCStmt* const ns : namerStmts) m_constructorp->addStmtsp(ns); + if (v3Global.opt.coverage()) { + AstCStmt* const regp = new AstCStmt{fl}; + regp->add(memberRef(fl, cpVarp)); + regp->add(".registerBins(vlSymsp->_vm_contextp__->coveragep(), \"v_covergroup/" + + m_covergroupp->name() + "\");"); + m_constructorp->addStmtsp(regp); + } + } + // Generate matching code for default bins // Default bins match when value doesn't match any other explicit bin void generateDefaultBinMatchCode(AstCoverpoint* coverpointp, AstCoverBin* defBinp, AstNodeExpr* exprp, AstVar* hitVarp) { UINFO(4, " Generating default bin match for: " << defBinp->name()); - // Build OR of all non-default, non-ignore bins - AstNodeExpr* anyBinMatchp = nullptr; - - for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { - AstCoverBin* const cbinp = VN_AS(binp, CoverBin); - - // Skip default, ignore, and illegal bins - if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT - || cbinp->binsType() == VCoverBinsType::BINS_IGNORE - || cbinp->binsType() == VCoverBinsType::BINS_ILLEGAL) { - continue; - } - - // Build condition for this bin - AstNodeExpr* const binCondp = buildBinCondition(cbinp, exprp); - UASSERT_OBJ(binCondp, cbinp, - "buildBinCondition returned nullptr for non-ignore/non-illegal bin"); - - // OR with previous conditions - if (anyBinMatchp) { - anyBinMatchp = new AstOr{defBinp->fileline(), anyBinMatchp, binCondp}; - } else { - anyBinMatchp = binCondp; - } - } - - // Default matches when NO explicit bin matches - AstNodeExpr* defaultCondp = nullptr; - if (anyBinMatchp) { - // NOT (bin1 OR bin2 OR ... OR binN) - defaultCondp = new AstNot{defBinp->fileline(), anyBinMatchp}; - } else { - // No other bins - default always matches (shouldn't happen in practice) - defaultCondp = new AstConst{defBinp->fileline(), AstConst::BitTrue{}}; - } + AstNodeExpr* defaultCondp = buildDefaultCondition(coverpointp, exprp, defBinp->fileline()); // Apply iff condition if present if (AstNodeExpr* iffp = coverpointp->iffp()) { @@ -1320,15 +1473,50 @@ class FunctionalCoverageVisitor final : public VNVisitor { void generateCoverageMethodBody(AstFunc* funcp) { FileLine* const fl = funcp->fileline(); + AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var); - // Count total bins (excluding ignore_bins and illegal_bins) + // Converted coverpoints hold their bins in VlCoverpoint. Combine their contributions + // (via coverageParts) with any remaining legacy cross/cross-fed bins as the same flat + // covered/total ratio the all-legacy path below computes. Normal bins only: ignore, + // illegal, and default are excluded (LRM 19.5). + if (!m_convCpVars.empty()) { + AstCStmt* const headp = new AstCStmt{fl}; + headp->add("double __Vcov = 0.0; double __Vtot = 0.0;"); + funcp->addStmtsp(headp); + for (AstVar* const cpVarp : m_convCpVars) { + AstCStmt* const cs = new AstCStmt{fl}; + cs->add("{ double __Vc = 0.0; double __Vt = 0.0; "); + cs->add(memberRef(fl, cpVarp)); + cs->add(".coverageParts(__Vc, __Vt); __Vcov += __Vc; __Vtot += __Vt; }"); + funcp->addStmtsp(cs); + } + int legacyRegular = 0; + for (const BinInfo& bi : m_binInfos) { + if (!bi.binp->binsType().binIsNormal()) continue; + ++legacyRegular; + AstCStmt* const cs = new AstCStmt{fl}; + cs->add("if ("); + cs->add(memberRef(fl, bi.varp)); + cs->add(" >= " + std::to_string(bi.atLeast) + ") __Vcov += 1.0;"); + funcp->addStmtsp(cs); + } + if (legacyRegular) { + AstCStmt* const cs = new AstCStmt{fl}; + cs->add("__Vtot += " + std::to_string(legacyRegular) + ".0;"); + funcp->addStmtsp(cs); + } + AstCStmt* const retp = new AstCStmt{fl}; + retp->add(new AstVarRef{fl, returnVarp, VAccess::WRITE}); + retp->add(" = (__Vtot != 0.0) ? (100.0 * __Vcov / __Vtot) : 100.0;"); + funcp->addStmtsp(retp); + return; + } + + // Count total bins (Normal only: excludes ignore/illegal/default) int totalBins = 0; for (const BinInfo& bi : m_binInfos) { UINFO(6, " Bin: " << bi.binp->name() << " type=" << bi.binp->binsType().ascii()); - if (bi.binp->binsType() != VCoverBinsType::BINS_IGNORE - && bi.binp->binsType() != VCoverBinsType::BINS_ILLEGAL) { - totalBins++; - } + if (bi.binp->binsType().binIsNormal()) totalBins++; } UINFO(4, " Total regular bins: " << totalBins << " of " << m_binInfos.size()); @@ -1337,7 +1525,6 @@ class FunctionalCoverageVisitor final : public VNVisitor { // No coverage to compute - return 100%. // Any parser-generated initialization of returnVar is overridden by our assignment. UINFO(4, " Empty covergroup, returning 100.0"); - AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var); funcp->addStmtsp(new AstAssign{fl, new AstVarRef{fl, returnVarp, VAccess::WRITE}, new AstConst{fl, AstConst::RealDouble{}, 100.0}}); UINFO(4, " Added assignment to return 100.0"); @@ -1356,11 +1543,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { // For each regular bin, if count > 0, increment covered_count for (const BinInfo& bi : m_binInfos) { - // Skip ignore_bins and illegal_bins in coverage calculation - if (bi.binp->binsType() == VCoverBinsType::BINS_IGNORE - || bi.binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { - continue; - } + // Skip ignore/illegal/default bins in coverage calculation + if (!bi.binp->binsType().binIsNormal()) continue; // if (bin_count >= at_least) covered_count++; AstIf* ifp = new AstIf{ @@ -1375,9 +1559,6 @@ class FunctionalCoverageVisitor final : public VNVisitor { funcp->addStmtsp(ifp); } - // Find the return variable - AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var); - // Calculate coverage: (covered_count / total_bins) * 100.0 // return_var = (double)covered_count / (double)total_bins * 100.0 diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index 273d1dcff..637cf184f 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -906,17 +906,20 @@ AstScope* DfgVertex::scopep(ScopeCache& cache, bool tryResultVar) VL_MT_DISABLED } } + AstScope* const rootp = v3Global.rootp()->topScopep()->scopep(); + AstScope* const constPoolp = v3Global.rootp()->constPoolp()->scopep(); + // Note: the recursive invocation can cause a re-hash but that will not invalidate references AstScope*& resultr = cache[this]; if (!resultr) { // Mark to prevent infinite recursion on circular graphs - should never be called on such resultr = reinterpret_cast(1); - // Find scope based on sources, falling back on the root scope - AstScope* const rootp = v3Global.rootp()->topScopep()->scopep(); + // Find scope based on sources, falling back on the root scope, + // also make sure it's not the constant pool scope, which is special. AstScope* foundp = nullptr; foreachSource([&](DfgVertex& src) { AstScope* const scp = src.scopep(cache, true); - if (scp != rootp) { + if (scp != rootp && scp != constPoolp) { foundp = scp; return true; } diff --git a/src/V3DfgContext.h b/src/V3DfgContext.h index dc1b54666..21e7af31a 100644 --- a/src/V3DfgContext.h +++ b/src/V3DfgContext.h @@ -234,6 +234,26 @@ private: addStat("temporaries introduced", m_temporariesIntroduced); } }; +class V3DfgRemoveSelectsContext final : public V3DfgSubContext { + // Only V3DfgContext can create an instance + friend class V3DfgContext; + +public: + // STATE + VDouble0 m_removedFullWidth; // Number of full width selects removed + VDouble0 m_replacedWithSelFromFull; // Number of selects replaced with sel from full driver + VDouble0 m_replacedWithSelFromPart; // Number of selects replaced with sel from partial driver + VDouble0 m_replacedWithPart; // Number of selects replaced with part of driver +private: + V3DfgRemoveSelectsContext() + : V3DfgSubContext{"RemoveSelects"} {} + ~V3DfgRemoveSelectsContext() { + addStat("full width selects removed", m_removedFullWidth); + addStat("replaced with sel from full driver", m_replacedWithSelFromFull); + addStat("replaced with sel from partial driver", m_replacedWithSelFromPart); + addStat("replaced with partial driver", m_replacedWithPart); + } +}; class V3DfgRemoveUnobservableContext final : public V3DfgSubContext { // Only V3DfgContext can create an instance friend class V3DfgContext; @@ -399,6 +419,7 @@ public: V3DfgPeepholeContext m_peepholeContext; V3DfgPushDownSelsContext m_pushDownSelsContext; V3DfgRegularizeContext m_regularizeContext; + V3DfgRemoveSelectsContext m_removeSelectsContext; V3DfgRemoveUnobservableContext m_removeUnobservableContext; V3DfgSynthesisContext m_synthContext; diff --git a/src/V3DfgDataType.h b/src/V3DfgDataType.h index 5af232b84..3011d254f 100644 --- a/src/V3DfgDataType.h +++ b/src/V3DfgDataType.h @@ -139,6 +139,7 @@ public: // Returns a Packed type of the given width static const DfgDataType& packed(uint32_t width) { + UASSERT(width > 0, "Width must be positive"); // Find or create the right sized packed type const DfgDataType*& entryr = s_packedTypes[width]; if (!entryr) entryr = new DfgDataType{width}; diff --git a/src/V3DfgOptimizer.cpp b/src/V3DfgOptimizer.cpp index b2fa0f9e8..0ad3b8aa9 100644 --- a/src/V3DfgOptimizer.cpp +++ b/src/V3DfgOptimizer.cpp @@ -139,6 +139,13 @@ class DataflowOptimize final { dfg.mergeGraphs(std::move(madeAcyclicComponents)); endOfStage("breakCycles", dfg, cyclicComps); + // Remove redundant selects + V3DfgPasses::removeSelects(dfg, m_ctx.m_removeSelectsContext); + for (std::unique_ptr& compp : cyclicComps) { + V3DfgPasses::removeSelects(*compp, m_ctx.m_removeSelectsContext); + } + endOfStage("removeSelects", dfg, cyclicComps); + // Split the acyclic DFG into [weakly] connected components std::vector> acyclicComps = dfg.splitIntoComponents("acyclic"); UASSERT(dfg.size() == 0, "DfgGraph should have become empty"); diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index 3e14397c6..afca00589 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -108,6 +108,78 @@ void V3DfgPasses::removeUnobservable(DfgGraph& dfg, V3DfgContext& dfgCtx) { } } +void V3DfgPasses::removeSelects(DfgGraph& dfg, V3DfgRemoveSelectsContext& ctx) { + + std::vector selps; + for (DfgVertex& vtx : dfg.opVertices()) { + DfgSel* const selp = vtx.cast(); + 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(); + if (!varp || !varp->tmpForp()) continue; + DfgVertex* const srcp = varp->srcp(); + if (!srcp) continue; + // Don't inline CReset + if (srcp->is()) 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()) { + ++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(); + DfgVertex* driverp = nullptr; + uint32_t driverLsb = 0; + splicep->foreachDriver([&](DfgVertex& src, const uint32_t dLsb) { + const uint32_t dMsb = dLsb + src.width() - 1; + // If it does not cover the whole searched bit range, move on + if (lsb < dLsb || dMsb < msb) return false; + // Save the driver + driverp = &src; + driverLsb = dLsb; + return true; + }); + if (!driverp) continue; + + // If partial driver is the whole thing we are looking for, just replace with the driver + if (driverp->dtype() == dtype) { + ++ctx.m_replacedWithPart; + selp->replaceWith(driverp); + continue; + } + + // Otherwise create a new select from the partial driver + ++ctx.m_replacedWithSelFromPart; + DfgSel* const newSelp = new DfgSel{dfg, flp, dtype}; + newSelp->lsb(lsb - driverLsb); + newSelp->fromp(driverp); + selp->replaceWith(newSelp); + } +} + void V3DfgPasses::inlineVars(DfgGraph& dfg) { for (DfgVertexVar& vtx : dfg.varVertices()) { // Nothing to inline it into diff --git a/src/V3DfgPasses.h b/src/V3DfgPasses.h index a7d2b04fb..2984c6bd7 100644 --- a/src/V3DfgPasses.h +++ b/src/V3DfgPasses.h @@ -41,6 +41,8 @@ void removeUnobservable(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; // Synthesize DfgLogic vertices into primitive operations. // Removes all DfgLogic (even those that were not synthesized). void synthesize(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; +// Remove redundant selects +void removeSelects(DfgGraph& dfg, V3DfgRemoveSelectsContext& ctx) VL_MT_DISABLED; // Attempt to make the given cyclic graph into an acyclic, or "less cyclic" // equivalent. If the returned pointer is null, then no improvement was // possible on the input graph. Otherwise the returned graph is an improvement diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index f89285eb7..3f08f490f 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -1188,6 +1188,102 @@ class V3DfgPeephole final : public DfgVisitor { return {fromp, lsb}; } + // Given a pair of vertices, returns a vertex representing the common LSBs of the two, + // and the number of common LSBs. Returns {nullptr, 0} if no common LSBs are found. + std::pair commonLSBs(DfgVertex* ap, DfgVertex* bp) { + if (ap == bp) return {ap, ap->width()}; + + // If both constants, check LSBs + if (DfgConst* const aConstp = ap->cast()) { + if (DfgConst* const bConstp = bp->cast()) { + 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()) return commonLSBs(catp->rhsp(), bp); + if (DfgConcat* const catp = bp->cast()) return commonLSBs(ap, catp->rhsp()); + + // If selecting the LSBs, check against the source of the Sel + if (DfgSel* const selp = ap->cast()) { + if (selp->lsb() == 0) { + DfgVertex* const fromp = selp->fromp(); + const std::pair common = commonLSBs(fromp, bp); + return {common.first, std::min(common.second, selp->width())}; + } + } + if (DfgSel* const selp = bp->cast()) { + if (selp->lsb() == 0) { + DfgVertex* const fromp = selp->fromp(); + const std::pair 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 commonMSBs(DfgVertex* ap, DfgVertex* bp) { + if (ap == bp) return {ap, ap->width()}; + + // If both constants, check MSBs + if (DfgConst* const aConstp = ap->cast()) { + if (DfgConst* const bConstp = bp->cast()) { + 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()) return commonMSBs(catp->lhsp(), bp); + if (DfgConcat* const catp = bp->cast()) return commonMSBs(ap, catp->lhsp()); + + // If selecting the MSBs, check against the source of the Sel + if (DfgSel* const selp = ap->cast()) { + DfgVertex* const fromp = selp->fromp(); + if (selp->msb() == fromp->width() - 1) { + const std::pair common = commonMSBs(fromp, bp); + return {common.first, std::min(common.second, selp->width())}; + } + } + if (DfgSel* const selp = bp->cast()) { + DfgVertex* const fromp = selp->fromp(); + if (selp->msb() == fromp->width() - 1) { + const std::pair common = commonMSBs(ap, fromp); + return {common.first, std::min(common.second, selp->width())}; + } + } + + // Otherwise no common MSBs + return {nullptr, 0}; + } + // VISIT methods void visit(DfgVertex*) override {} @@ -1789,10 +1885,24 @@ class V3DfgPeephole final : public DfgVisitor { if (!idxp) return; DfgVarArray* const varp = vtxp->fromp()->cast(); if (!varp) return; - if (varp->vscp()->varp()->isForced()) return; - if (varp->vscp()->varp()->isSigUserRWPublic()) return; + AstVar* const astVarp = varp->vscp()->varp(); + if (astVarp->isForced()) return; + if (astVarp->isSigUserRWPublic()) return; DfgVertex* const srcp = varp->srcp(); - if (!srcp) return; + if (!srcp) { + if (vtxp->isPacked()) { + if (AstInitArray* const iap = VN_CAST(astVarp->valuep(), InitArray)) { + if (AstConst* const valp + = VN_CAST(iap->getIndexDefaultedValuep(idxp->toSizeT()), Const)) { + APPLYING(FOLD_ARRAYSEL_TABLE) { + replace(new DfgConst{m_dfg, valp->fileline(), valp->num()}); + return; + } + } + } + } + return; + } if (DfgSpliceArray* const splicep = srcp->cast()) { DfgVertex* const driverp = splicep->driverAt(idxp->toSizeT()); @@ -2834,56 +2944,62 @@ class V3DfgPeephole final : public DfgVisitor { } } - if (DfgConcat* const tConcatp = thenp->cast()) { - if (DfgConcat* const eConcatp = elsep->cast()) { - DfgVertex* const tRhsp = tConcatp->rhsp(); - DfgVertex* const tLhsp = tConcatp->lhsp(); - DfgVertex* const eRhsp = eConcatp->rhsp(); - DfgVertex* const eLhsp = eConcatp->lhsp(); - - if (isSame(tRhsp, eRhsp)) { - APPLYING(REPLACE_COND_SAME_CAT_RHS) { - DfgCond* const newCondp - = make(flp, tLhsp->dtype(), condp, tLhsp, eLhsp); - replace(make(vtxp, newCondp, tRhsp)); - return; - } - } - - if (isSame(tLhsp, eLhsp)) { - APPLYING(REPLACE_COND_SAME_CAT_LHS) { - DfgCond* const newCondp - = make(flp, tRhsp->dtype(), condp, tRhsp, eRhsp); - replace(make(vtxp, tLhsp, newCondp)); + if (!thenp->is() && !elsep->is()) { + const std::pair cLSBs = commonLSBs(thenp, elsep); + if (cLSBs.first) { + APPLYING(REPLACE_COND_COMMON_LSBS) { + // Create new RHS + const uint32_t rWidth = cLSBs.second; + DfgVertex* rhsp = cLSBs.first; + if (rWidth != rhsp->width()) { + const DfgDataType& rDtype = DfgDataType::packed(rWidth); + rhsp = make(flp, rDtype, rhsp, 0U); + } + // If it's all the same, just replace + if (rhsp->dtype() == vtxp->dtype()) { + // Note this branch can only be hit if rules run in the right order, + // it might have missing code coverage after a refactor. + replace(rhsp); return; } + // Create new LHS + const uint32_t lWidth = vtxp->width() - rWidth; + const DfgDataType& lDtype = DfgDataType::packed(lWidth); + DfgVertex* const lThenp = make(flp, lDtype, thenp, rWidth); + DfgVertex* const lElsep = make(flp, lDtype, elsep, rWidth); + DfgVertex* const lhsp = make(flp, lDtype, condp, lThenp, lElsep); + // Replace with concat + replace(make(vtxp, lhsp, rhsp)); + return; } } - if (!tConcatp->hasMultipleSinks()) { - if (DfgConcat* const tRCatp = tConcatp->rhsp()->cast()) { - if (!tRCatp->hasMultipleSinks()) { - if (DfgSel* const tLSelp = tConcatp->lhsp()->cast()) { - if (DfgSel* const tRRSelp = tRCatp->rhsp()->cast()) { - if (tLSelp->lsb() == tRCatp->width() // - && tRRSelp->lsb() == 0 // - && isSame(tLSelp->fromp(), elsep) // - && isSame(tRRSelp->fromp(), elsep)) { - APPLYING(REPLACE_COND_INSERT) { - DfgVertex* const newTp = tRCatp->lhsp(); - DfgVertex* const newEp = make( - flp, newTp->dtype(), elsep, tRRSelp->width()); - DfgCond* const newCp = make(flp, newTp->dtype(), - condp, newTp, newEp); - replace(make( - vtxp, tLSelp, - make(tRCatp, newCp, tRRSelp))); - return; - } - } - } - } + const std::pair cMSBs = commonMSBs(thenp, elsep); + if (cMSBs.first) { + APPLYING(REPLACE_COND_COMMON_MSBS) { + // Create new LHS + const uint32_t lWidth = cMSBs.second; + DfgVertex* lhsp = cMSBs.first; + if (lWidth != lhsp->width()) { + const DfgDataType& lDtype = DfgDataType::packed(lWidth); + lhsp = make(flp, lDtype, lhsp, lhsp->width() - lWidth); } + // If it's all the same, just replace + if (lhsp->dtype() == vtxp->dtype()) { + // Note this branch can only be hit if rules run in the right order, + // it might have missing code coverage after a refactor. + replace(lhsp); + return; + } + // Create new RHS + const uint32_t rWidth = vtxp->width() - lWidth; + const DfgDataType& rDtype = DfgDataType::packed(rWidth); + DfgVertex* const rThenp = make(flp, rDtype, thenp, 0U); + DfgVertex* const rElsep = make(flp, rDtype, elsep, 0U); + DfgVertex* const rhsp = make(flp, rDtype, condp, rThenp, rElsep); + // Replace with concat + replace(make(vtxp, lhsp, rhsp)); + return; } } } diff --git a/src/V3DfgPeepholePatterns.h b/src/V3DfgPeepholePatterns.h index d8d295b2d..52d5717fe 100644 --- a/src/V3DfgPeepholePatterns.h +++ b/src/V3DfgPeepholePatterns.h @@ -27,6 +27,7 @@ // Enumeration of each peephole optimization. Must be kept in sorted order (enforced by tests). // clang-format off #define FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(macro) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ARRAYSEL_TABLE) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY_LHS_OF_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY_RHS_OF_LHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_BINARY) \ @@ -112,17 +113,16 @@ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_SAME_REP_ON_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_COMMON_LSBS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_COMMON_MSBS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ONES_ZERO) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ONE_ZERO) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ZERO_ONE) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ZERO_ONES) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_DEC) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_INC) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_INSERT) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_OR_THEN_COND_LHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_OR_THEN_COND_RHS) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_CAT_LHS) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_CAT_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_COND_ELSE) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_COND_THEN) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_THEN_OR_LHS) \ diff --git a/src/V3DfgVertices.h b/src/V3DfgVertices.h index a3d3adf8e..f8ca8e9c8 100644 --- a/src/V3DfgVertices.h +++ b/src/V3DfgVertices.h @@ -299,6 +299,7 @@ public: void fromp(DfgVertex* vtxp) { srcp(vtxp); } uint32_t lsb() const { return m_lsb; } void lsb(uint32_t value) { m_lsb = value; } + uint32_t msb() const { return m_lsb + width() - 1; } }; class DfgUnitArray final : public DfgVertexUnary { diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index 8eb82b31f..11f2ac14a 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -18,8 +18,6 @@ #include "V3EmitCFunc.h" -#include "V3TSP.h" - #include #include diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index c05c46afe..969e2fd0b 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -573,8 +573,14 @@ public: + "\", " + std::to_string(nodep->fileline()->lineno()) + ")->"), memberVarp, resetp->constructing()); } else { - AstVar* const varp = VN_AS(fromp, NodeVarRef)->varp(); - emitVarReset("", varp, resetp->constructing()); + AstNodeVarRef* const fromVarRefp = VN_AS(fromp, NodeVarRef); + AstVar* const varp = fromVarRefp->varp(); + const string prefix + = fromVarRefp->selfPointer().isEmpty() + ? "" + : dereferenceString( + VN_AS(fromp, NodeVarRef)->selfPointerProtect(m_useSelfForThis)); + emitVarReset(prefix, varp, resetp->constructing()); } return; } @@ -1128,7 +1134,7 @@ public: } void visit(AstFFlush* nodep) override { if (!nodep->filep()) { - putns(nodep, "Verilated::runFlushCallbacks();\n"); + putns(nodep, "VL_FFLUSH_MT();"); } else { putns(nodep, "VL_FFLUSH_I("); iterateAndNextConstNull(nodep->filep()); diff --git a/src/V3EmitCHeaders.cpp b/src/V3EmitCHeaders.cpp index 1746139b8..524a39222 100644 --- a/src/V3EmitCHeaders.cpp +++ b/src/V3EmitCHeaders.cpp @@ -689,6 +689,7 @@ class EmitCHeader final : public EmitCConstInit { if (v3Global.opt.mtasks()) puts("#include \"verilated_threads.h\"\n"); if (v3Global.opt.savable()) puts("#include \"verilated_save.h\"\n"); if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n"); + if (v3Global.opt.coverage()) puts("#include \"verilated_covergroup.h\"\n"); if (v3Global.usesTiming()) puts("#include \"verilated_timing.h\"\n"); if (v3Global.useRandomizeMethods()) puts("#include \"verilated_random.h\"\n"); if (v3Global.usesForce()) puts("#include \"verilated_force.h\"\n"); diff --git a/src/V3EmitCModel.cpp b/src/V3EmitCModel.cpp index 211eea99f..6ddd53e77 100644 --- a/src/V3EmitCModel.cpp +++ b/src/V3EmitCModel.cpp @@ -70,6 +70,7 @@ class EmitCModel final : public EmitCFunc { if (v3Global.opt.mtasks()) puts("#include \"verilated_threads.h\"\n"); if (v3Global.opt.savable()) puts("#include \"verilated_save.h\"\n"); if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n"); + if (v3Global.opt.coverage()) puts("#include \"verilated_covergroup.h\"\n"); if (v3Global.dpi()) puts("#include \"svdpi.h\"\n"); // Declare foreign instances up front to make C++ happy diff --git a/src/V3FsmDetect.cpp b/src/V3FsmDetect.cpp index 4a2ccf8af..5ffee9e4c 100644 --- a/src/V3FsmDetect.cpp +++ b/src/V3FsmDetect.cpp @@ -30,6 +30,7 @@ #include "V3Ast.h" #include "V3Control.h" #include "V3Graph.h" +#include "V3UniqueNames.h" #include #include @@ -987,14 +988,23 @@ class FsmDetectVisitor final : public VNVisitor { return assp; } + static AstVarRef* tryExtractVarRef(AstNodeExpr* const exprp) { + AstVarRef* const varp = VN_CAST(AstArraySel::baseFromp(exprp, true), VarRef); + if (!varp) { + exprp->v3warn(COVERIGN, + "Ignoring unsupported: FSM coverage with " << exprp->prettyTypeName()); + return nullptr; + } + return varp; + } + static AstNodeAssign* nodeStateVarAssign(AstNode* nodep, AstVarScope*& stateVscp, AstVarScope*& fromVscp) { AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign); if (!assp) return nullptr; - AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef); - UASSERT_OBJ(lhsp, assp, "register commit lhs should be normalized to a VarRef"); + AstVarRef* const lhsp = tryExtractVarRef(assp->lhsp()); AstVarRef* const rhsp = VN_CAST(assp->rhsp(), VarRef); - if (!rhsp) return nullptr; + if (!rhsp || !lhsp) return nullptr; stateVscp = lhsp->varScopep(); fromVscp = rhsp->varScopep(); return assp; @@ -1006,11 +1016,9 @@ class FsmDetectVisitor final : public VNVisitor { FsmStateValue& resetValue) { AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign); if (!assp) return nullptr; - AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef); - UASSERT_OBJ(lhsp, assp, - "conditional register commit lhs should be normalized to a VarRef"); + AstVarRef* const lhsp = tryExtractVarRef(assp->lhsp()); AstCond* const rhsp = VN_CAST(assp->rhsp(), Cond); - if (!rhsp) return nullptr; + if (!rhsp || !lhsp) return nullptr; if (AstVarRef* const elsep = VN_CAST(rhsp->elsep(), VarRef)) { if (constValueStatus(rhsp->thenp(), resetValue) != ConstValueStatus::OK) return nullptr; @@ -1033,7 +1041,7 @@ class FsmDetectVisitor final : public VNVisitor { FsmStateValue& value) { AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign); if (!assp) return nullptr; - AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef); + AstVarRef* const lhsp = VN_CAST(AstArraySel::baseFromp(assp->lhsp(), true), VarRef); UASSERT_OBJ(lhsp, assp, "direct constant state assignment lhs should be normalized to a VarRef"); if (constValueStatus(assp->rhsp(), value) != ConstValueStatus::OK) return nullptr; @@ -1220,7 +1228,8 @@ class FsmDetectVisitor final : public VNVisitor { AstVarRef* vrefp = VN_CAST(eqp->lhsp(), VarRef); AstNodeExpr* valuep = eqp->rhsp(); if (!vrefp) { - vrefp = VN_AS(eqp->rhsp(), VarRef); + vrefp = tryExtractVarRef(eqp->rhsp()); + if (!vrefp) { return false; } valuep = eqp->lhsp(); } @@ -2082,6 +2091,7 @@ public: class FsmLowerVisitor final { // STATE - across all visitors const FsmState& m_state; + V3UniqueNames m_fsmBuildNames; // METHODS // Rebuild a state-typed constant using the tracked state variable @@ -2133,8 +2143,8 @@ class FsmLowerVisitor final { AstNodeModule* const modp = scopep->modp(); AstNodeDType* const prevDTypep = scopep->findLogicDType( sampleVscp->width(), sampleVscp->width(), sampleVscp->dtypep()->numeric()); - AstVarScope* const prevVscp - = scopep->createTemp("__Vfsmcov_prev__" + stateVscp->varp()->shortName(), prevDTypep); + const std::string tmpName = m_fsmBuildNames.get(stateVscp->varp()->shortName()); + AstVarScope* const prevVscp = scopep->createTemp(tmpName, prevDTypep); // The saved previous-state temp crosses the scheduler's pre/post split // in the same way as Verilator's built-in NBA shadow variables, so keep // both vars marked as post-life participants for stable MT ordering. @@ -2283,7 +2293,8 @@ public: // concrete coverage instrumentation while the saved scoped pointers are // still valid in the same pass. explicit FsmLowerVisitor(const FsmState& state) - : m_state{state} { + : m_state{state} + , m_fsmBuildNames{"__Vfsmcov_prev"} { for (const DetectedFsm& fsm : m_state.fsms()) { buildOne(*fsm.graphp); } } }; diff --git a/src/V3Gate.cpp b/src/V3Gate.cpp index 4875ac75c..b96c892f2 100644 --- a/src/V3Gate.cpp +++ b/src/V3Gate.cpp @@ -543,89 +543,62 @@ class GateInline final { // Logic block with pending substitutions are stored in this map, together with their ordinal std::unordered_map m_hasPending; size_t m_statInlined = 0; // Statistic tracking - signals inlined - size_t m_statRefs = 0; // Statistic tracking - size_t m_statExcluded = 0; // Statistic tracking + size_t m_statNotInlined = 0; // Statistic tracking - signals not inlined due to cost + size_t m_statRefs = 0; // Statistic tracking - number of input variable references replaced // METHODS - static bool isCheapWide(const AstNodeExpr* exprp) { + static bool isCheap(const AstNodeExpr* exprp) { + // Constant is cheap + if (VN_IS(exprp, Const)) return true; + // Variable reference is cheap + if (VN_IS(exprp, NodeVarRef)) return true; + // AstSel is cheap if the fromp is cheap, and not a wide needing bit swizzling if (const AstSel* const selp = VN_CAST(exprp, Sel)) { + if (!isCheap(selp->fromp())) return false; + if (!selp->isWide()) return true; + if (!VN_IS(selp->lsbp(), Const)) return false; if (selp->lsbConst() % VL_EDATASIZE != 0) return false; - exprp = selp->fromp(); + return true; } - if (const AstArraySel* const aselp = VN_CAST(exprp, ArraySel)) exprp = aselp->fromp(); - return VN_IS(exprp, Const) || VN_IS(exprp, NodeVarRef); - } - static bool excludedWide(GateVarVertex* const vVtxp, const AstNodeExpr* const rhsp) { - // Handle wides with logic drivers that are too wide for V3Expand. - if (!vVtxp->varScp()->isWide() // - || vVtxp->varScp()->widthWords() <= v3Global.opt.expandLimit() // - || vVtxp->inEmpty() // - || isCheapWide(rhsp)) - return false; - - const GateLogicVertex* const lVtxp - = vVtxp->inEdges().frontp()->fromp()->as(); - - // 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(); - if (lvp->activep() != primaryActivep) continue; - - reads += edge.weight(); - if (reads > 1) return true; - } + // AstArraySel is cheap if the fromp is cheap + if (const AstArraySel* const aselp = VN_CAST(exprp, ArraySel)) { + return isCheap(aselp->fromp()); } + // Otherwise it is not cheap return false; } bool shouldInline(GateVarVertex* vVtxp, GateLogicVertex* lVtxp, size_t nReads, AstNodeExpr* substp, bool allowMultiIn) { - AstVarScope* const vscp = vVtxp->varScp(); - // Always inline constants if (VN_IS(substp, Const)) return true; - // Don't inline non-constant static initializers + // Don't inline non-constant static initializers - these are scheduled differently if (lVtxp->staticInit()) return false; // Inline simple variable references if (VN_IS(substp, VarRef)) return true; // Only inline arrays if a simple variable or constant - if (VN_IS(vscp->dtypep()->skipRefp(), UnpackArrayDType)) return false; - // Inline constant array selects - if (VN_IS(substp, ArraySel) && nReads <= 1) return true; - - // Don't inline expensive wide operations - if (excludedWide(vVtxp, substp)) { - ++m_statExcluded; - UINFO(9, "Gate inline exclude '" << vVtxp->name() << "'"); - vVtxp->clearReducible("Excluded wide"); // Check once. - return false; - } - - if (nReads == 0) { - // Reads no variables, likely unfolded constant expression - return true; - } else if (nReads == 1) { - // Reads one variable - return true; - } else { - // Reads more two or more variables - if (!allowMultiIn) return false; - // Do it if not used, or used only once, ignoring slow code - int n = 0; - for (V3GraphEdge& edge : vVtxp->outEdges()) { - const GateLogicVertex* const dstVtxp = edge.top()->as(); - // Ignore slow code, or if the destination is not used - if (dstVtxp->slow()) continue; - if (dstVtxp->outEmpty() && !dstVtxp->consumed()) continue; - n += edge.weight(); - if (n > 1) return false; + if (VN_IS(vVtxp->varScp()->dtypep()->skipRefp(), UnpackArrayDType)) return false; + // Inline if reads no variables - unfolded constant expression, nullary builtin e.g.: $time + if (nReads == 0) return true; + // If it reads one variable, inline if not wide, or if cheap + if (nReads == 1 && (!substp->isWide() || isCheap(substp))) return true; + // Don't inline on first round if reads more than one variable + if (nReads > 1 && !allowMultiIn) return false; + // Reads multiple variables, or is expensive to compute. + // Inline if used only once, ignoring slow code, or dead code that can be deleted. + int n = 0; + for (V3GraphEdge& edge : vVtxp->outEdges()) { + const GateLogicVertex* const dstVtxp = edge.top()->as(); + // Ignore slow code, or if the destination is not used + if (dstVtxp->slow()) continue; + if (dstVtxp->outEmpty() && !dstVtxp->consumed()) continue; + n += edge.weight(); + if (n > 1) { + ++m_statNotInlined; + return false; } - return true; } + return true; } void recordSubstitution(AstVarScope* vscp, AstNodeExpr* substp, AstNode* logicp) { @@ -724,7 +697,7 @@ class GateInline final { if (!okVisitor.varAssigned(vVtxp->varScp())) continue; // Expression we are considering to substitute with - AstNodeExpr* const substp = okVisitor.substitutionp(); + AstNodeExpr* const substp = V3Const::constifyEdit(okVisitor.substitutionp()); // Number of variables read by the substitution const size_t nReads = okVisitor.readVscps().size(); @@ -832,9 +805,9 @@ class GateInline final { } ~GateInline() { - V3Stats::addStat("Optimizations, Gate sigs deleted", m_statInlined); - V3Stats::addStat("Optimizations, Gate inputs replaced", m_statRefs); - V3Stats::addStat("Optimizations, Gate excluded wide expressions", m_statExcluded); + V3Stats::addStat("Optimizations, Gate signals inlined", m_statInlined); + V3Stats::addStat("Optimizations, Gate signals not inlined due to cost", m_statNotInlined); + V3Stats::addStat("Optimizations, Gate reads replaced", m_statRefs); } public: diff --git a/src/V3Global.cpp b/src/V3Global.cpp index e0b6f42ea..2e7c25fc7 100644 --- a/src/V3Global.cpp +++ b/src/V3Global.cpp @@ -245,6 +245,7 @@ std::vector V3Global::verilatedCppFiles() { if (v3Global.opt.vpi()) result.emplace_back("verilated_vpi.cpp"); if (v3Global.opt.savable()) result.emplace_back("verilated_save.cpp"); if (v3Global.opt.coverage()) result.emplace_back("verilated_cov.cpp"); + if (v3Global.opt.coverage()) result.emplace_back("verilated_covergroup.cpp"); for (const string& base : v3Global.opt.traceSourceBases()) result.emplace_back(base + "_c.cpp"); if (v3Global.usesProbDist()) result.emplace_back("verilated_probdist.cpp"); diff --git a/src/V3Life.cpp b/src/V3Life.cpp index 77ac85c87..7b4d5ab1a 100644 --- a/src/V3Life.cpp +++ b/src/V3Life.cpp @@ -60,7 +60,7 @@ public: class LifeVarEntry final { // Last assignment to this varscope, nullptr if no longer relevant - AstNodeStmt* m_assignp = nullptr; + AstNodeStmt* m_assignp = nullptr; // Last simple assignment AstConst* m_constp = nullptr; // Known constant value bool m_isNew = true; // Is just created // First access was a set (and thus block above may have a set that can be deleted @@ -77,6 +77,17 @@ public: m_isNew = false; m_setBeforeUse = setBeforeUse; } + string ascii() const { // LCOV_EXCL_START + std::ostringstream os; + os << "[Life "; + if (m_isNew) os << " isNew"; + if (m_setBeforeUse) os << " setBeforeUse"; + if (m_everSet) os << " everSet"; + if (m_assignp) os << " assignp=" << AstNode::nodeAddr(m_assignp); + if (m_constp) os << " constp=" << AstNode::nodeAddr(m_constp); + os << "]"; + return os.str(); + } // LCOV_EXCL_STOP void simpleAssign(AstNodeStmt* nodep) { // New simple A=.... assignment UASSERT_OBJ(!m_isNew, nodep, "Uninitialized new entry"); @@ -84,7 +95,10 @@ public: m_constp = nullptr; m_everSet = true; if (AstNodeAssign* const assp = VN_CAST(nodep, Assign)) { - if (VN_IS(assp->rhsp(), Const)) m_constp = VN_AS(assp->rhsp(), Const); + if (VN_IS(assp->rhsp(), Const)) { + m_constp = VN_AS(assp->rhsp(), Const); + UINFO(9, "assign-const " << assp->rhsp() << " = " << m_constp); + } } } void complexAssign() { // A[x]=... or some complicated assignment @@ -179,6 +193,7 @@ public: // Aha, variable is constant; substitute in. // We'll later constant propagate UINFO(4, " replaceconst: " << varrefp); + UINFO(9, " replaceval: " << constp); varrefp->replaceWith(constp->cloneTree(false)); m_replacedVref = true; VL_DO_DANGLING(varrefp->deleteTree(), varrefp); @@ -264,7 +279,9 @@ class LifeVisitor final : public VNVisitor { LifeBlock* m_lifep = nullptr; // Current active lifetime map for current scope // METHODS - void setNoopt() { + void setNoopt(const char* reasonp) { + (void)reasonp; + // UINFO(9, "setNoopt " << reasonp); m_noopt = true; m_lifep->clear(); } @@ -310,7 +327,7 @@ class LifeVisitor final : public VNVisitor { void visit(AstNodeAssign* nodep) override { if (nodep->isTimingControl() || VN_IS(nodep, AssignForce)) { // V3Life doesn't understand time sense nor force assigns - don't optimize - setNoopt(); + setNoopt("timing|force"); if (nodep->isTimingControl()) m_containsTiming = true; iterateChildren(nodep); return; @@ -325,7 +342,7 @@ class LifeVisitor final : public VNVisitor { // V3Life doesn't understand time sense if (nodep->isTimingControl()) { // Don't optimize - setNoopt(); + setNoopt("assigndly"); m_containsTiming = true; } // Don't treat as normal assign @@ -337,19 +354,19 @@ class LifeVisitor final : public VNVisitor { UINFO(4, " IF " << nodep); // Condition is part of PREVIOUS block iterateAndNextNull(nodep->condp()); - LifeBlock* const prevLifep = m_lifep; - LifeBlock* const ifLifep = new LifeBlock{prevLifep, m_statep}; - LifeBlock* const elseLifep = new LifeBlock{prevLifep, m_statep}; + LifeBlock* const ifLifep = new LifeBlock{m_lifep, m_statep}; + LifeBlock* const elseLifep = new LifeBlock{m_lifep, m_statep}; { + VL_RESTORER(m_lifep); m_lifep = ifLifep; iterateAndNextNull(nodep->thensp()); } { + VL_RESTORER(m_lifep); m_lifep = elseLifep; iterateAndNextNull(nodep->elsesp()); } - m_lifep = prevLifep; - UINFO(4, " join "); + UINFO(4, " join " << nodep); // Find sets on both flows m_lifep->dualBranch(ifLifep, elseLifep); // For the next assignments, clear any variables that were read or written in the block @@ -357,6 +374,7 @@ class LifeVisitor final : public VNVisitor { elseLifep->lifeToAbove(); VL_DO_DANGLING(delete ifLifep, ifLifep); VL_DO_DANGLING(delete elseLifep, elseLifep); + UINFO(4, " if-done " << nodep); } void visit(AstLoop* nodep) override { // Similar problem to AstJumpBlock, don't optimize loop bodies - most are unrolled @@ -366,14 +384,14 @@ class LifeVisitor final : public VNVisitor { VL_RESTORER(m_noopt); VL_RESTORER(m_lifep); m_lifep = new LifeBlock{m_lifep, m_statep}; - setNoopt(); + setNoopt("loop"); iterateAndNextNull(nodep->stmtsp()); UINFO(4, " joinloop"); // For the next assignments, clear any variables that were read or written in the block m_lifep->lifeToAbove(); VL_DO_DANGLING(delete m_lifep, m_lifep); } - if (m_containsTiming) setNoopt(); + if (m_containsTiming) setNoopt("timing"); } void visit(AstJumpBlock* nodep) override { // As with Loop's we can't predict if a JumpGo will kill us or not @@ -384,14 +402,14 @@ class LifeVisitor final : public VNVisitor { VL_RESTORER(m_noopt); VL_RESTORER(m_lifep); m_lifep = new LifeBlock{m_lifep, m_statep}; - setNoopt(); + setNoopt("jumpblock"); iterateAndNextNull(nodep->stmtsp()); UINFO(4, " joinjump"); // For the next assignments, clear any variables that were read or written in the block m_lifep->lifeToAbove(); VL_DO_DANGLING(delete m_lifep, m_lifep); } - if (m_containsTiming) setNoopt(); + if (m_containsTiming) setNoopt("timing"); } void visit(AstNodeCCall* nodep) override { // UINFO(4, " CCALL " << nodep); @@ -399,7 +417,7 @@ class LifeVisitor final : public VNVisitor { // Enter the function and trace it // else is non-inline or public function we optimize separately if (nodep->funcp()->entryPoint()) { - setNoopt(); + setNoopt("ccall"); } else { m_tracingCall = true; iterate(nodep->funcp()); @@ -409,8 +427,8 @@ class LifeVisitor final : public VNVisitor { // UINFO(4, " CFUNC " << nodep); if (!m_tracingCall && !nodep->entryPoint()) return; m_tracingCall = false; - if (nodep->recursive()) setNoopt(); - if (nodep->noLife()) setNoopt(); + if (nodep->recursive()) setNoopt("recursive"); + if (nodep->noLife()) setNoopt("nolife"); if (nodep->dpiImportPrototype() && !nodep->dpiPure()) { m_sideEffect = true; // If appears on assign RHS, don't ever delete the assignment } @@ -429,7 +447,7 @@ class LifeVisitor final : public VNVisitor { void visit(AstNode* nodep) override { if (nodep->isTimingControl()) { // V3Life doesn't understand time sense - don't optimize - setNoopt(); + setNoopt("timing"); m_containsTiming = true; } iterateChildren(nodep); diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index a04b7bbdf..1ada078ba 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -1727,7 +1727,6 @@ class LinkDotFindVisitor final : public VNVisitor { newvarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); newvarp->funcReturn(true); newvarp->trace(false); // Not user visible - newvarp->attrIsolateAssign(nodep->attrIsolateAssign()); nodep->fvarp(newvarp); // Explicit insert required, as the var name shadows the upper level's task name m_statep->insertSym(m_curSymp, newvarp->name(), newvarp, nullptr /*classOrPackagep*/); diff --git a/src/V3LinkJump.cpp b/src/V3LinkJump.cpp index 21e3cdad9..70115033f 100644 --- a/src/V3LinkJump.cpp +++ b/src/V3LinkJump.cpp @@ -262,6 +262,9 @@ class LinkJumpVisitor final : public VNVisitor { if (it != m_beginDisableBegins.end()) return it->second; AstBegin* const beginBodyp = new AstBegin{fl, "", nullptr, false}; + // Disable-by-name rewrites kill this detached block-body process, so mark it as process + // backed to ensure fork/join kill-accounting hooks are always emitted. + beginBodyp->setNeedProcess(); if (beginp->stmtsp()) beginBodyp->addStmtsp(beginp->stmtsp()->unlinkFrBackWithNext()); AstFork* const forkp = new AstFork{fl, VJoinType::JOIN}; diff --git a/src/V3LinkLevel.cpp b/src/V3LinkLevel.cpp index 012c87166..066097239 100644 --- a/src/V3LinkLevel.cpp +++ b/src/V3LinkLevel.cpp @@ -291,6 +291,7 @@ void V3LinkLevel::wrapTopCell(AstNetlist* rootp) { varp->sigPublic(true); // User needs to be able to get to it... oldvarp->primaryIO(false); varp->primaryIO(true); + varp->icoMaybeWritten(oldvarp->icoMaybeWritten()); if (varp->isRef() || varp->isConstRef()) { varp->v3warn(E_UNSUPPORTED, "Unsupported: ref/const ref as primary input/output: " diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index e6beb5760..5403d77c2 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -606,10 +606,6 @@ class LinkParseVisitor final : public VNVisitor { UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); m_varp->sigUserRWPublic(true); VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - } else if (nodep->attrType() == VAttrType::VAR_ISOLATE_ASSIGNMENTS) { - UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); - m_varp->attrIsolateAssign(true); - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); } else if (nodep->attrType() == VAttrType::VAR_SFORMAT) { UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); m_varp->attrSFormat(true); diff --git a/src/V3Options.cpp b/src/V3Options.cpp index b8d7a0a29..dd26e7e06 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1077,6 +1077,9 @@ void V3Options::notify() VL_MT_DISABLED { // Preprocessor defines based on options used if (timing().isSetTrue()) V3PreShell::defineCmdLine("VERILATOR_TIMING", "1"); + // If VPI is used, and no explicit ico change detect option was passed, disable it by default + if (m_vpi && m_fIcoChangeDetect.isDefault()) m_fIcoChangeDetect.setTrueOrFalse(false); + // === Leave last // Mark options as available m_available = true; @@ -1483,6 +1486,9 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, DECL_OPTION("-ffunc-opt-balance-cat", FOnOff, &m_fFuncBalanceCat); DECL_OPTION("-ffunc-opt-split-cat", FOnOff, &m_fFuncSplitCat); DECL_OPTION("-fgate", FOnOff, &m_fGate); + DECL_OPTION("-fico-change-detect", CbFOnOff, [this](bool flag) { // + m_fIcoChangeDetect.setTrueOrFalse(flag); + }); DECL_OPTION("-finline", FOnOff, &m_fInline); DECL_OPTION("-finline-funcs", FOnOff, &m_fInlineFuncs); DECL_OPTION("-finline-funcs-eager", FOnOff, &m_fInlineFuncsEager); diff --git a/src/V3Options.h b/src/V3Options.h index 729c68f47..56ba51f79 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -410,6 +410,8 @@ private: bool m_fFuncBalanceCat = true; // main switch: -fno-func-balance-cat: expansion of C macros bool m_fFuncSplitCat = true; // main switch: -fno-func-split-cat: expansion of C macros bool m_fGate; // main switch: -fno-gate: gate wire elimination + // main switch: -fno-ico-change-detect: input change detection optimization + VOptionBool m_fIcoChangeDetect{VOptionBool::OPT_DEFAULT_TRUE}; bool m_fInline; // main switch: -fno-inline: module inlining bool m_fInlineFuncs = true; // main switch: -fno-inline-funcs: function inlining bool m_fInlineFuncsEager = true; // main switch: -fno-inline-funcs-eager: don't inline eagerly @@ -745,6 +747,7 @@ public: bool fFuncSplitCat() const { return m_fFuncSplitCat; } bool fFunc() const { return fFuncSplitCat() || fFuncBalanceCat(); } bool fGate() const { return m_fGate; } + VOptionBool fIcoChangeDetect() const { return m_fIcoChangeDetect; } bool fInline() const { return m_fInline; } bool fInlineFuncs() const { return m_fInlineFuncs; } bool fInlineFuncsEager() const { return m_fInlineFuncsEager; } diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 3fcaf5535..929bbf115 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -1509,6 +1509,41 @@ class ConstraintExprVisitor final : public VNVisitor { VL_DO_DANGLING(nodep->deleteTree(), nodep); iterate(sump); } + void visit(AstRedXor* nodep) override { + if (editFormat(nodep)) return; + + // Build popcount expansion: (extract x 1 1) ^ (extract x 2 2) ^ ... + FileLine* const fl = nodep->fileline(); + AstNodeExpr* const argp = nodep->lhsp()->unlinkFrBack(); + + AstNodeExpr* redxorp = new AstSel{fl, argp, 0, 1}; + redxorp->user1(true); + for (int i = 1; i < argp->width(); i++) { + AstSel* const selp = new AstSel{fl, argp->cloneTreePure(false), i, 1}; + selp->user1(true); + + redxorp = new AstXor{fl, redxorp, selp}; + redxorp->user1(true); + } + + nodep->replaceWith(redxorp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + iterate(redxorp); + } + void visit(AstRedAnd* nodep) override { + if (editFormat(nodep)) return; + // Convert to (~x == 0) + FileLine* const fl = nodep->fileline(); + AstNodeExpr* const argp = nodep->lhsp()->unlinkFrBack(); + const V3Number numZero{fl, argp->width(), 0}; + AstNodeExpr* const negp = new AstNot{fl, argp}; + negp->user1(true); + AstNodeExpr* const eqp = new AstEq{fl, negp, new AstConst{fl, numZero}}; + eqp->user1(true); + nodep->replaceWith(eqp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + iterate(eqp); + } void visit(AstRedOr* nodep) override { if (editFormat(nodep)) return; // Convert to (x != 0) diff --git a/src/V3Sampled.cpp b/src/V3Sampled.cpp index 4fb45e772..841b32d55 100644 --- a/src/V3Sampled.cpp +++ b/src/V3Sampled.cpp @@ -51,8 +51,6 @@ class SampledVisitor final : public VNVisitor { AstVar* const newvarp = new AstVar{flp, VVarType::MODULETEMP, newvarname, varp->dtypep()}; m_scopep->modp()->addStmtsp(newvarp); AstVarScope* const newvscp = new AstVarScope{flp, m_scopep, newvarp}; - newvarp->direction(VDirection::INPUT); // Inform V3Sched that it will be driven later - newvarp->primaryIO(true); newvarp->sampled(true); vscp->user1p(newvscp); m_scopep->addVarsp(newvscp); diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index 8adc0a52e..6078fbbf0 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -529,8 +529,46 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, + entry.m_memberp->name()); } + // Create the input change detect SenTrees. + // If there is a lot of combinationallogic hanging of the top level inputs, we can save + // a lot of work by only evaluating it if an input has actually changed. This in + // paticular helps hierarchical models partitioned across combinaitonal boundaries. + // The change detect itself should be fairly cheap otherwise so alway do it. + // For correctness, don't create a change detect for top level inputs also written + // by the design, as the change detect 'previous value' would get out of sync. + // Also omit a SenTree for types that don't have the required '!=' operator. + // Any signal that does not have an explicit change detect trigger will fall back to + // using the 'first iteration' trigger, same as if this optimization was disabled. + std::unordered_map inp2changedp; + std::vector icoChangeSenTreeps; + if (v3Global.opt.fIcoChangeDetect().isTrue()) { + FileLine* const flp = netlistp->fileline(); + AstScope* const scopep = netlistp->topScopep()->scopep(); + for (AstVarScope* vscp = scopep->varsp(); vscp; vscp = VN_AS(vscp->nextp(), VarScope)) { + // Only for top level ports, assume outputs don't change externally + if (!vscp->varp()->isPrimaryInish()) continue; + // Don't do if written by the design - wouldn't update the change detect 'prev' value + if (vscp->varp()->icoMaybeWritten()) continue; + // Don't do if forceable, as we can't see the actual value - this is belt and braces + if (vscp->varp()->isForced()) continue; + // Can't handle unpacked arrays (they have special types when primary input) + if (VN_IS(vscp->dtypep()->skipRefp(), UnpackArrayDType)) continue; + // Similarly to arrays, can't handle SystemC types + if (vscp->varp()->isSc()) continue; + // Create a sen tree triggered when this input changes + AstSenTree*& senTreepr = inp2changedp[vscp]; + UASSERT_OBJ(!senTreepr, vscp, "Duplicate input change detect trigger"); + AstVarRef* const refp = new AstVarRef{flp, vscp, VAccess::READ}; + AstSenItem* const senItemp = new AstSenItem{flp, VEdgeType::ET_CHANGED, refp}; + senTreepr = new AstSenTree{flp, senItemp}; + icoChangeSenTreeps.push_back(senTreepr); + } + } + V3Stats::addStat("Scheduling, 'ico' change detect triggers", icoChangeSenTreeps.size()); + // Gather the relevant sensitivity expressions and create the trigger kit - const auto& senTreeps = getSenTreesUsedBy({&logic}); + std::vector senTreeps = getSenTreesUsedBy({&logic}); + senTreeps.insert(senTreeps.end(), icoChangeSenTreeps.begin(), icoChangeSenTreeps.end()); const TriggerKit trigKit = TriggerKit::create(netlistp, initFuncp, senExprBuilder, {}, senTreeps, "ico", extraTriggers, false, false); std::ignore = senExprBuilder.getAndClearResults(); @@ -543,14 +581,14 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, // Remap sensitivities remapSensitivities(logic, trigKit.mapVec()); + for (auto& pair : inp2changedp) pair.second = trigKit.mapVec().at(pair.second); // Create the inverse map from trigger ref AstSenTree to original AstSenTree V3Order::TrigToSenMap trigToSen; invertAndMergeSenTreeMap(trigToSen, trigKit.mapVec()); - // The trigger top level inputs (first iteration) - AstSenTree* const inputChanged - = trigKit.newExtraTriggerSenTree(trigKit.vscp(), firstIterationTrigger); + // The 'first iteration' trigger for top level inputs - lazy constructed only if needed + AstSenTree* firstIterTriggerp = nullptr; // The DPI Export trigger AstSenTree* const dpiExportTriggered @@ -565,9 +603,19 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, netlistp, {&logic}, trigToSen, "ico", false, false, [&](const AstVarScope* vscp, std::vector& out) { AstVar* const varp = vscp->varp(); - if (varp->isPrimaryInish() || varp->isSigUserRWPublic()) { - out.push_back(inputChanged); + // If it has an explicit change detect trigger, use that, + // otherwise fall back to using the 'first iteration' trigger + auto it = inp2changedp.find(vscp); + if (it != inp2changedp.end()) { + out.push_back(it->second); + } else if (varp->isPrimaryInish() || varp->isSigUserRWPublic() || varp->sampled()) { + if (!firstIterTriggerp) { + firstIterTriggerp + = trigKit.newExtraTriggerSenTree(trigKit.vscp(), firstIterationTrigger); + } + out.push_back(firstIterTriggerp); } + // Add other triggers if (varp->isWrittenByDpi()) out.push_back(dpiExportTriggered); if (vscp->varp()->sensIfacep() || vscp->varp()->isVirtIface()) { const auto& ifaceTriggered @@ -593,8 +641,14 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, // Work statements: Invoke the 'ico' function util::callVoidFunc(icoFuncp)); - // Add the first iteration trigger to the trigger computation function - trigKit.addExtraTriggerAssignment(icoLoop.firstIterp, firstIterationTrigger, false); + // Add the first iteration trigger to the trigger computation function - if used + if (firstIterTriggerp) { + trigKit.addExtraTriggerAssignment(icoLoop.firstIterp, firstIterationTrigger, false); + } + + // Release temporary input change detect SenTrees + for (AstSenTree* const senTreep : icoChangeSenTreeps) senTreep->deleteTree(); + icoChangeSenTreeps.clear(); return icoLoop.stmtsp; } diff --git a/src/V3SchedAcyclic.cpp b/src/V3SchedAcyclic.cpp index 36a9cd096..ad8ba454e 100644 --- a/src/V3SchedAcyclic.cpp +++ b/src/V3SchedAcyclic.cpp @@ -351,8 +351,7 @@ std::string reportLoopVars(FileLine* /*warnFl*/, Graph* graphp, SchedAcyclicVarV if (splittable) { ss << V3Error::warnMore() - << "... Suggest add /*verilator split_var*/ or /*verilator " - "isolate_assignments*/ to appropriate variables above.\n"; + << "... Suggest add /*verilator split_var*/ to appropriate variables above.\n"; } V3Stats::addStat("Scheduling, split_var, candidates", splittable); return ss.str(); diff --git a/src/V3SchedReplicate.cpp b/src/V3SchedReplicate.cpp index 35718b98a..3aa404018 100644 --- a/src/V3SchedReplicate.cpp +++ b/src/V3SchedReplicate.cpp @@ -150,8 +150,8 @@ public: : SchedReplicateVertex{graphp} , m_vscp{vscp} { // Top level inputs are - if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->isWrittenByDpi() - || varp()->sensIfacep() || varp()->isVirtIface()) { + if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->sampled() + || varp()->isWrittenByDpi() || varp()->sensIfacep() || varp()->isVirtIface()) { addDrivingRegions(INPUT); } // Currently we always execute suspendable processes at the beginning of diff --git a/src/V3SplitAs.cpp b/src/V3SplitAs.cpp deleted file mode 100644 index 74b72a4dd..000000000 --- a/src/V3SplitAs.cpp +++ /dev/null @@ -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); -} diff --git a/src/V3SplitAs.h b/src/V3SplitAs.h deleted file mode 100644 index dc5c4cce6..000000000 --- a/src/V3SplitAs.h +++ /dev/null @@ -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 diff --git a/src/V3TSP.cpp b/src/V3TSP.cpp deleted file mode 100644 index 4244a5676..000000000 --- a/src/V3TSP.cpp +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include -#include - -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 -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 -class TspGraphTmpl final : public V3Graph { -public: - // TYPES - using Vertex = TspVertexTmpl; - - enum VertexState : uint32_t { CLEAR = 0, MST_VISITED = 1, UNMATCHED_ODD = 2 }; - - // MEMBERS - std::unordered_map 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(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(edgep->user()); - } - - // cppcheck-suppress duplInheritedMember - bool empty() const { return m_vertices.empty(); } - - const std::list keysToVertexList(const std::vector& odds) { - std::list 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; - - 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(vxp); } - static const Vertex* castVertexp(const V3GraphVertex* vxp) { - return static_cast(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 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 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& oddKeys, TspGraphTmpl* outp) { - UASSERT(outp->empty(), "Output graph must start empty"); - - const std::list& 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 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 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* markedEdgesp, Vertex* startp, - std::vector* sortedOutp) { - Vertex* cur_vertexp = startp; - - // Go on a random tour. Fun! - std::vector 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 logp{V3File::new_ofstream(filename)}; - if (logp->fail()) v3fatal("Can't write file: " << filename); - dumpGraph(*logp, nameComment); - } - } - - void findEulerTour(std::vector* sortedOutp) { - UASSERT(sortedOutp->empty(), "Output graph must start empty"); - if (::dumpGraphLevel() >= 6) dumpDotFilePrefixed("findEulerTour"); - std::unordered_set markedEdges; - // Pick a start node - Vertex* const start_vertexp = castVertexp(vertices().frontp()); - findEulerTourRecurse(&markedEdges, start_vertexp, sortedOutp); - } - - std::vector getOddDegreeKeys() const { - std::vector 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; - 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 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 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(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(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(*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(*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; - 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 oddDegree = minGraph.getOddDegreeKeys(); - Graph matching; - graph.perfectMatching(oddDegree, &matching); - if (dumpGraphLevel() >= 6) matching.dumpGraphFilePrefixed("matching"); - - minGraph.combineGraph(matching); - - std::vector result; - minGraph.findEulerTour(&result); - - std::vector 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(); -} diff --git a/src/V3TSP.h b/src/V3TSP.h deleted file mode 100644 index 840f5fa96..000000000 --- a/src/V3TSP.h +++ /dev/null @@ -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 - -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; - -// 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 diff --git a/src/V3Task.cpp b/src/V3Task.cpp index 5bae5d0aa..0db8d04d3 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -248,9 +248,9 @@ private: UASSERT_OBJ(nodep->taskp(), nodep, "Unlinked task"); TaskFTaskVertex* const taskVtxp = getFTaskVertex(nodep->taskp()); new TaskEdge{&m_callGraph, m_curVxp, taskVtxp}; - if (isVirtualIfaceMethodCall(nodep) && isIfaceFTaskScope(getScope(nodep->taskp()))) { - taskVtxp->needsNonInlineCFunc(true); - } + // Virtual-interface method calls dispatch through a runtime handle and + // must not be inlined. + if (isVirtualIfaceMethodCall(nodep)) taskVtxp->needsNonInlineCFunc(true); // Do we have to disable inlining the function? const V3TaskConnects tconnects = V3Task::taskConnects(nodep, nodep->taskp()->stmtsp()); if (!taskVtxp->noInline()) { // Else short-circuit below @@ -1638,6 +1638,7 @@ class TaskVisitor final : public VNVisitor { // Create cloned statements AstNode* beginp; AstCNew* cnewp = nullptr; + // getScope() is safe here: TaskStateVisitor stamped all FTask scopes before this pass. const bool virtualIfaceCall = TaskStateVisitor::isVirtualIfaceMethodCall(nodep) && TaskStateVisitor::isIfaceFTaskScope(m_statep->getScope(nodep->taskp())); diff --git a/src/V3Unknown.cpp b/src/V3Unknown.cpp index 727e97840..c8851280d 100644 --- a/src/V3Unknown.cpp +++ b/src/V3Unknown.cpp @@ -51,15 +51,14 @@ class UnknownVisitor final : public VNVisitor { static const std::string s_xrandPrefix; // STATE - across all visitors - VDouble0 m_statUnkVars; // Statistic tracking + VDouble0 m_statUnkVars; // Statistic of xrand variable created + VDouble0 m_statElses; // Statistic of else branches created for array selects V3UniqueNames m_lvboundNames; // For generating unique temporary variable names std::unique_ptr m_xrandNames; // For generating unique temporary variable names // STATE - for current visit position (use VL_RESTORER) AstNodeModule* m_modp = nullptr; // Current module AstNodeFTask* m_ftaskp = nullptr; // Current function/task - AstAssignDly* m_assigndlyp = nullptr; // Current assignment - AstNode* m_timingControlp = nullptr; // Current assignment's intra timing control bool m_constXCvt = false; // Convert X's bool m_allowXUnique = true; // Allow unique assignments @@ -69,38 +68,21 @@ class UnknownVisitor final : public VNVisitor { if (m_ftaskp) { varp->funcLocal(true); varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); - m_ftaskp->stmtsp()->addHereThisAsNext(varp); + if (m_ftaskp->stmtsp()) + m_ftaskp->stmtsp()->addHereThisAsNext(varp); + else + m_ftaskp->addStmtsp(varp); } else { - m_modp->stmtsp()->addHereThisAsNext(varp); + if (m_modp->stmtsp()) + m_modp->stmtsp()->addHereThisAsNext(varp); + else + m_modp->addStmtsp(varp); } } void replaceBoundLvalue(AstNodeExpr* nodep, AstNodeExpr* condp) { // Spec says a out-of-range LHS SEL results in a NOP. - // This is a PITA. We could: - // 1. IF(...) around an ASSIGN, - // but that would break a "foo[TOO_BIG]=$fopen(...)". - // 2. Hack to extend the size of the output structure - // by one bit, and write to that temporary, but never read it. - // That makes there be two widths() and is likely a bug farm. - // 3. Make a special SEL to choose between the real lvalue - // and a temporary NOP register. - // 4. Assign to a temp, then IF that assignment. - // This is suspected to be nicest to later optimizations. - // 4 seems best but breaks later optimizations. 3 was tried, - // but makes a mess in the emitter as lvalue switching is needed. So 4. - // SEL(...) -> temp - // if (COND(LTE(bit<=maxlsb))) ASSIGN(SEL(...)),temp) - const bool needDly = (m_assigndlyp != nullptr); - if (m_assigndlyp) { - // Delayed assignments become normal assignments, - // then the temp created becomes the delayed assignment - AstNode* const newp = new AstAssign{m_assigndlyp->fileline(), - m_assigndlyp->lhsp()->unlinkFrBackWithNext(), - m_assigndlyp->rhsp()->unlinkFrBackWithNext()}; - m_assigndlyp->replaceWith(newp); - VL_DO_CLEAR(pushDeletep(m_assigndlyp), m_assigndlyp = nullptr); - } + // We wrap the expression into IF AstNodeExpr* prep = nodep; // Scan back to put the condlvalue above all selects (IE top of the lvalue) @@ -121,32 +103,35 @@ class UnknownVisitor final : public VNVisitor { // Already exists; rather than IF(a,... IF(b... optimize to IF(a&&b, // Saves us teaching V3Const how to optimize, and it won't be needed again. if (const AstIf* const ifp = VN_AS(prep->user2p(), If)) { - UASSERT_OBJ(!needDly, prep, "Should have already converted to non-delay"); VNRelinker replaceHandle; AstNodeExpr* const earliercondp = ifp->condp()->unlinkFrBack(&replaceHandle); AstNodeExpr* const newp = new AstLogAnd{condp->fileline(), condp, earliercondp}; UINFO(4, "Edit BOUNDLVALUE " << newp); replaceHandle.relink(newp); } else { - AstVar* const varp - = new AstVar{fl, VVarType::MODULETEMP, m_lvboundNames.get(prep), prep->dtypep()}; - addVar(varp); AstNode* stmtp = prep->backp(); // Grab above point before we replace 'prep' while (!VN_IS(stmtp, NodeStmt)) stmtp = stmtp->backp(); - - prep->replaceWith(new AstVarRef{fl, varp, VAccess::WRITE}); - if (m_timingControlp) m_timingControlp->unlinkFrBack(); - AstIf* const newp = new AstIf{ - fl, condp, - (needDly - ? static_cast(new AstAssignDly{ - fl, prep, new AstVarRef{fl, varp, VAccess::READ}, m_timingControlp}) - : static_cast(new AstAssign{ - fl, prep, new AstVarRef{fl, varp, VAccess::READ}, m_timingControlp}))}; + VNRelinker replaceHandle; + AstNode* const origStmtp = stmtp->unlinkFrBack(&replaceHandle); + AstNode* elseStmtp = nullptr; + const bool hasSideEffects = origStmtp->exists( + [](AstNode* const np) { return !np->isPure() || np->isTimingControl(); }); + if (hasSideEffects) { + // Copy original statement and replace `prep` with reference to tmp var to make + // sure that side effect will take place + m_statElses++; + elseStmtp = origStmtp->cloneTree(false); + AstVar* const varp = new AstVar{fl, VVarType::MODULETEMP, m_lvboundNames.get(prep), + prep->dtypep()}; + addVar(varp); + AstNode* const prepCopyp = prep->clonep(); + prepCopyp->replaceWith(new AstVarRef{fl, varp, VAccess::WRITE}); + pushDeletep(prepCopyp); + } + AstIf* const newp = new AstIf{fl, condp, origStmtp, elseStmtp}; + replaceHandle.relink(newp); newp->branchPred(VBranchPred::BP_LIKELY); newp->isBoundsCheck(true); - UINFOTREE(9, newp, "", "_new"); - stmtp->addNextHere(newp); prep->user2p(newp); // Save so we may LogAnd it next time } } @@ -208,23 +193,6 @@ class UnknownVisitor final : public VNVisitor { m_ftaskp = nodep; iterateChildren(nodep); } - void visit(AstAssignDly* nodep) override { - VL_RESTORER(m_assigndlyp); - VL_RESTORER(m_timingControlp); - m_assigndlyp = nodep; - m_timingControlp = nodep->timingControlp(); - VL_DO_DANGLING(iterateChildren(nodep), nodep); // May delete nodep. - } - void visit(AstAssignW* nodep) override { - VL_RESTORER(m_timingControlp); - m_timingControlp = nodep->timingControlp(); - VL_DO_DANGLING(iterateChildren(nodep), nodep); // May delete nodep. - } - void visit(AstNodeAssign* nodep) override { - VL_RESTORER(m_timingControlp); - m_timingControlp = nodep->timingControlp(); - iterateChildren(nodep); - } void visit(AstCaseItem* nodep) override { VL_RESTORER(m_constXCvt); m_constXCvt = false; // Avoid losing the X's in casex @@ -584,6 +552,7 @@ public: } ~UnknownVisitor() override { // V3Stats::addStat("Unknowns, variables created", m_statUnkVars); + V3Stats::addStat("Unknowns, else branches created", m_statElses); } }; diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 5a24539b2..f5e55d2a7 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -255,13 +255,6 @@ class WidthVisitor final : public VNVisitor { EXTEND_OFF // No extension }; - int widthUnpacked(const AstNodeDType* const dtypep) { - if (const AstUnpackArrayDType* const arrDtypep = VN_CAST(dtypep, UnpackArrayDType)) { - return arrDtypep->subDTypep()->width() * arrDtypep->arrayUnpackedElements(); - } - return dtypep->width(); - } - static void packIfUnpacked(AstNodeExpr* const nodep) { if (AstUnpackArrayDType* const unpackDTypep = VN_CAST(nodep->dtypep(), UnpackArrayDType)) { const int elementsNum = unpackDTypep->arrayUnpackedElements(); @@ -274,6 +267,9 @@ class WidthVisitor final : public VNVisitor { nodep->findLogicDType(unpackBits, unpackMinBits, VSigning::UNSIGNED)}); } } + static bool lowerAsFixedAggregate(const AstNodeDType* const dtypep) { + return dtypep->isStreamableFixedAggregate() && dtypep->containsUnpackedStruct(); + } // When fromp() is a DType (e.g. unlinked RefDType), resolve through // the ref chain; when it's an expression, dtypep() is already resolved. static AstNodeDType* fromDTypep(AstNode* fromp) { @@ -979,7 +975,10 @@ class WidthVisitor final : public VNVisitor { } const AstNodeDType* const lhsDtypep = nodep->lhsp()->dtypep()->skipRefToEnump(); if (VN_IS(lhsDtypep, DynArrayDType) || VN_IS(lhsDtypep, QueueDType) - || VN_IS(lhsDtypep, UnpackArrayDType)) { + || (VN_IS(lhsDtypep, UnpackArrayDType) && lhsDtypep->isStreamableFixedAggregate()) + || (VN_IS(lhsDtypep, NodeUOrStructDType) + && !VN_AS(lhsDtypep, NodeUOrStructDType)->packed() + && lhsDtypep->isStreamableFixedAggregate())) { nodep->dtypeSetStream(); } else if (lhsDtypep->isCompound()) { nodep->v3warn(E_UNSUPPORTED, @@ -3074,6 +3073,7 @@ class WidthVisitor final : public VNVisitor { UASSERT_OBJ(nodep->dtypep(), nodep, "LHS var should be dtype completed"); } // UINFOTREE(9, nodep, "", "VRout"); + if (nodep->access().isWriteOrRW()) nodep->varp()->icoMaybeWritten(true); if (nodep->access().isWriteOrRW() && nodep->varp()->direction() == VDirection::CONSTREF) { nodep->v3error("Assigning to const ref variable: " << nodep->prettyNameQ()); } else if (nodep->access().isWriteOrRW() && nodep->varp()->isInput() @@ -4352,6 +4352,12 @@ class WidthVisitor final : public VNVisitor { } void methodCallAssoc(AstMethodCall* nodep, AstAssocArrayDType* adtypep) { AstCMethodHard* newp = nullptr; + if (nodep->withp() && adtypep->subDTypep()->isWide()) { + nodep->v3warn( + E_UNSUPPORTED, + "Unsupported: `with` clause on assoc arrays with wide value types in method '" + << nodep->prettyName() << "'"); + } if (nodep->name() == "num" // function int num() || nodep->name() == "size") { methodOkArguments(nodep, 0, 0); @@ -6289,14 +6295,14 @@ class WidthVisitor final : public VNVisitor { userIterateAndNext(nodep->rhsp(), WidthVP{nodep->dtypep(), PRELIM}.p()); // // UINFOTREE(1, nodep, "", "assign"); - AstNodeDType* const lhsDTypep + AstNodeDType* lhsDTypep = nodep->lhsp()->dtypep(); // Note we use rhsp for context determined // Check width of stream and wrap if needed if (AstNodeStream* const streamp = VN_CAST(nodep->rhsp(), NodeStream)) { AstNodeDType* const lhsDTypeSkippedRefp = lhsDTypep->skipRefp(); - const int lwidth = widthUnpacked(lhsDTypeSkippedRefp); - const int rwidth = widthUnpacked(streamp->lhsp()->dtypep()->skipRefp()); + const int lwidth = lhsDTypeSkippedRefp->widthStream(); + const int rwidth = streamp->lhsp()->dtypep()->skipRefp()->widthStream(); if (lwidth != 0 && lwidth < rwidth) { nodep->v3widthWarn(lwidth, rwidth, "Target fixed size variable (" @@ -6308,16 +6314,18 @@ class WidthVisitor final : public VNVisitor { const int queueElementSize = streamp->lhsp()->dtypep()->subDTypep()->width(); UASSERT_OBJ(queueElementSize <= lwidth, nodep, "LHS < RHS"); } - if (VN_IS(lhsDTypeSkippedRefp, UnpackArrayDType)) { + if (VN_IS(lhsDTypeSkippedRefp, UnpackArrayDType) + || lowerAsFixedAggregate(lhsDTypeSkippedRefp)) { streamp->unlinkFrBack(); nodep->rhsp(new AstCvtPackedToArray{streamp->fileline(), streamp, lhsDTypeSkippedRefp}); } } - if (const AstNodeStream* const streamp = VN_CAST(nodep->lhsp(), NodeStream)) { + if (AstNodeStream* const streamp = VN_CAST(nodep->lhsp(), NodeStream)) { const AstNodeDType* const rhsDTypep = nodep->rhsp()->dtypep()->skipRefp(); - const int lwidth = widthUnpacked(streamp->lhsp()->dtypep()->skipRefp()); - const int rwidth = widthUnpacked(rhsDTypep); + AstNodeDType* const lhsStreamDTypep = streamp->lhsp()->dtypep()->skipRefp(); + const int lwidth = lhsStreamDTypep->widthStream(); + const int rwidth = rhsDTypep->widthStream(); if (rwidth != 0 && rwidth < lwidth) { nodep->v3widthWarn(lwidth, rwidth, "Stream target requires " @@ -6325,7 +6333,27 @@ class WidthVisitor final : public VNVisitor { << " bits, but source expression only provides " << rwidth << " bits (IEEE 1800-2023 11.4.14.3)"); } - if (VN_IS(rhsDTypep, UnpackArrayDType)) { + if (lowerAsFixedAggregate(lhsStreamDTypep)) { + AstNodeExpr* const streamExprp = nodep->lhsp()->unlinkFrBack(); + AstNodeExpr* const dstp = streamp->lhsp()->unlinkFrBack(); + AstNodeExpr* srcp = nodep->rhsp()->unlinkFrBack(); + if (VN_IS(streamp, StreamL)) { + streamp->lhsp(srcp); + streamp->dtypeSetLogicUnsized(srcp->width(), srcp->widthMin(), + VSigning::UNSIGNED); + srcp = streamExprp; + } else { + if (srcp->width() > lwidth) { + srcp = new AstSel{streamp->fileline(), srcp, srcp->width() - lwidth, + lwidth}; + } + VL_DO_DANGLING(pushDeletep(streamExprp), streamExprp); + } + nodep->lhsp(dstp); + nodep->rhsp(new AstCvtPackedToArray{srcp->fileline(), srcp, lhsStreamDTypep}); + nodep->dtypeFrom(dstp); + lhsDTypep = nodep->lhsp()->dtypep(); + } else if (VN_IS(rhsDTypep, UnpackArrayDType)) { AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack(); nodep->rhsp( new AstCvtArrayToPacked{rhsp->fileline(), rhsp, streamp->dtypep()}); diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 13bb95c6f..b37336a52 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -98,12 +98,10 @@ #include "V3Scoreboard.h" #include "V3Slice.h" #include "V3Split.h" -#include "V3SplitAs.h" #include "V3SplitVar.h" #include "V3Stats.h" #include "V3String.h" #include "V3Subst.h" -#include "V3TSP.h" #include "V3Table.h" #include "V3Task.h" #include "V3ThreadPool.h" @@ -261,6 +259,7 @@ static void process() { // Assertion insertion // After we've added block coverage, but before other nasty transforms + V3AssertCommon::collectDefaultDisable(v3Global.rootp()); V3AssertNfa::assertNfaAll(v3Global.rootp()); // V3AssertProp removed: NFA subsumes multi-cycle property lowering. // Unsupported constructs fall through to V3AssertPre. @@ -421,7 +420,6 @@ static void process() { // Split single ALWAYS blocks into multiple blocks for better ordering chances if (v3Global.opt.fSplit()) V3Split::splitAll(v3Global.rootp()); - V3SplitAs::splitAsAll(v3Global.rootp()); // Create tracing sample points, before we start eliminating signals if (v3Global.opt.trace()) V3TraceDecl::traceDeclAll(v3Global.rootp()); @@ -734,7 +732,6 @@ static bool verilate(const string& argString) { VHashSha256::selfTest(); VSpellCheck::selfTest(); V3Graph::selfTest(); - V3TSP::selfTest(); V3ScoreboardBase::selfTest(); V3Order::selfTestParallel(); V3ExecGraph::selfTest(); diff --git a/src/bisonpre b/src/bisonpre index 9be647f39..5db061a5a 100755 --- a/src/bisonpre +++ b/src/bisonpre @@ -145,6 +145,26 @@ def clean_output(filename: str, outname: str, is_output: bool, is_c: bool) -> No out.append(line) lines = out out = [] + # Code updates for the .c + if outname.endswith(".c"): + for line in lines: + modify = "YYSTACK_FREE (yyss);" in line and "if (yyss != yyssa)" in out[-1] + ifdefined = "#if defined __GNUC__ && ! defined __ICC\n" + endif = "#endif\n" + if modify: + out.insert(-1, ifdefined) + out.insert(-1, " _Pragma (\"GCC diagnostic push\")\n") + out.insert( + -1, + " _Pragma (\"GCC diagnostic ignored \\\"-Wfree-nonheap-object\\\"\")\n") + out.insert(-1, endif) + out.append(line) + if modify: + out.append(ifdefined) + out.append(" _Pragma (\"GCC diagnostic pop\")\n") + out.append(endif) + lines = out + out = [] with open(outname, "w", encoding="utf-8") as fh: tmpy = re.escape(tmp_prefix() + ".y") diff --git a/src/cppcheck-suppressions.txt b/src/cppcheck-suppressions.txt index 754ef3c4b..6d9ab321d 100644 --- a/src/cppcheck-suppressions.txt +++ b/src/cppcheck-suppressions.txt @@ -137,8 +137,6 @@ constParameterPointer:src/V3Tristate.cpp constParameterReference:src/V3Tristate.cpp constVariablePointer:src/V3Tristate.cpp constVariableReference:src/V3Tristate.cpp -constVariablePointer:src/V3TSP.cpp -constVariableReference:src/V3TSP.cpp constParameterPointer:src/V3Undriven.cpp constVariablePointer:src/V3Undriven.cpp constParameterPointer:src/V3Unroll.cpp diff --git a/src/verilog.y b/src/verilog.y index 663e8a206..847a5f828 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -3121,7 +3121,7 @@ sigAttr: | yVL_PUBLIC_FLAT { $$ = new AstAttrOf{$1, VAttrType::VAR_PUBLIC_FLAT}; v3Global.dpi(true); } | yVL_PUBLIC_FLAT_RD { $$ = new AstAttrOf{$1, VAttrType::VAR_PUBLIC_FLAT_RD}; v3Global.dpi(true); } | yVL_PUBLIC_FLAT_RW attr_event_controlE { $$ = new AstAttrOf{$1, VAttrType::VAR_PUBLIC_FLAT_RW}; v3Global.dpi(true); DEL($2); } - | yVL_ISOLATE_ASSIGNMENTS { $$ = new AstAttrOf{$1, VAttrType::VAR_ISOLATE_ASSIGNMENTS}; } + | yVL_ISOLATE_ASSIGNMENTS { $$ = nullptr; /* Historical, now has no effect */ } | yVL_SC_BIGUINT { $$ = new AstAttrOf{$1, VAttrType::VAR_SC_BIGUINT}; } | yVL_SC_BV { $$ = new AstAttrOf{$1, VAttrType::VAR_SC_BV}; } | yVL_SFORMAT { $$ = new AstAttrOf{$1, VAttrType::VAR_SFORMAT}; } @@ -4657,12 +4657,12 @@ task_prototype: // ==IEEE: task_prototype function_declaration: // IEEE: function_declaration + function_body_declaration yFUNCTION dynamic_override_specifiersE lifetimeE funcId funcIsolateE tfGuts yENDFUNCTION endLabelE - { $$ = $4; $4->attrIsolateAssign($5); $$->addStmtsp($6); + { $$ = $4; $$->addStmtsp($6); $$->baseOverride($2); $$->lifetime($3); GRAMMARP->endLabel($8, $$, $8); } | yFUNCTION dynamic_override_specifiersE lifetimeE funcIdNew funcIsolateE tfNewGuts yENDFUNCTION endLabelE - { $$ = $4; $4->attrIsolateAssign($5); $$->addStmtsp($6); + { $$ = $4; $$->addStmtsp($6); $$->baseOverride($2); $$->lifetime($3); GRAMMARP->endLabel($8, $$, $8); } @@ -6497,14 +6497,34 @@ concurrent_assertion_statement: // ==IEEE: concurrent_assertion_stat | yCOVER yPROPERTY '(' property_spec ')' stmt { $$ = new AstCover{$1, $4, $6, VAssertType::CONCURRENT}; } // // IEEE: cover_sequence_statement + // // Reuses AstCover + AstPropSpec (same wrapper as + // // cover_property_statement above) and the isCoverSeq + // // flag drives V3AssertNfa to fire stmt per end-of-match + // // (IEEE 1800-2023 16.14.3), not per property success. | yCOVER ySEQUENCE '(' sexpr ')' stmt - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover sequence"); DEL($4, $6); } - // // IEEE: yCOVER ySEQUENCE '(' clocking_event sexpr ')' stmt - // // sexpr already includes "clocking_event sexpr" + { AstCover* const coverp = new AstCover{$1, + new AstPropSpec{$4->fileline(), nullptr, nullptr, $4}, + $6, VAssertType::CONCURRENT}; + coverp->isCoverSeq(true); + $$ = coverp; } + | yCOVER ySEQUENCE '(' clocking_event sexpr ')' stmt + { AstCover* const coverp = new AstCover{$1, + new AstPropSpec{$4->fileline(), $4, nullptr, $5}, + $7, VAssertType::CONCURRENT}; + coverp->isCoverSeq(true); + $$ = coverp; } | yCOVER ySEQUENCE '(' clocking_event yDISABLE yIFF '(' expr/*expression_or_dist*/ ')' sexpr ')' stmt - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover sequence"); DEL($4, $8, $10, $12);} + { AstCover* const coverp = new AstCover{$1, + new AstPropSpec{$4->fileline(), $4, $8, $10}, + $12, VAssertType::CONCURRENT}; + coverp->isCoverSeq(true); + $$ = coverp; } | yCOVER ySEQUENCE '(' yDISABLE yIFF '(' expr/*expression_or_dist*/ ')' sexpr ')' stmt - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover sequence"); DEL($7, $9, $11); } + { AstCover* const coverp = new AstCover{$1, + new AstPropSpec{$7->fileline(), nullptr, $7, $9}, + $11, VAssertType::CONCURRENT}; + coverp->isCoverSeq(true); + $$ = coverp; } // // IEEE: restrict_property_statement | yRESTRICT yPROPERTY '(' property_spec ')' ';' { $$ = new AstRestrict{$1, $4}; } @@ -6664,14 +6684,8 @@ property_spec: // IEEE: property_spec { $$ = new AstPropSpec{$1, $3, nullptr, $5}; } | '@' senitemVar pexpr { $$ = new AstPropSpec{$1, $2, nullptr, $3}; } - // // Disable applied after the event occurs, - // // so no existing AST can represent this - | yDISABLE yIFF '(' expr ')' '@' '(' senitemEdge ')' pexpr - { $$ = new AstPropSpec{$1, $8, nullptr, new AstLogOr{$1, $4, $10}}; - BBUNSUP($1, "Unsupported: property '(disable iff (...) @ (...)'\n" - + $1->warnMore() - + "... Suggest use property '(@(...) disable iff (...))'"); } - //UNSUP remove above + | yDISABLE yIFF '(' expr ')' '@' '(' senitem ')' pexpr + { $$ = new AstPropSpec{$1, $8, $4, $10}; } | yDISABLE yIFF '(' expr ')' pexpr { $$ = new AstPropSpec{$4->fileline(), nullptr, $4, $6}; } | pexpr { $$ = new AstPropSpec{$1->fileline(), nullptr, nullptr, $1}; } ; @@ -8559,8 +8573,7 @@ vltVarAttrSpecE: ; vltVarAttrFront: - yVLT_ISOLATE_ASSIGNMENTS { $$ = VAttrType::VAR_ISOLATE_ASSIGNMENTS; } - | yVLT_FORCEABLE { $$ = VAttrType::VAR_FORCEABLE; } + yVLT_FORCEABLE { $$ = VAttrType::VAR_FORCEABLE; } | yVLT_PUBLIC { $$ = VAttrType::VAR_PUBLIC; v3Global.dpi(true); } | yVLT_PUBLIC_FLAT { $$ = VAttrType::VAR_PUBLIC_FLAT; v3Global.dpi(true); } | yVLT_PUBLIC_FLAT_RD { $$ = VAttrType::VAR_PUBLIC_FLAT_RD; v3Global.dpi(true); } @@ -8574,6 +8587,7 @@ vltVarAttrFront: vltVarAttrFrontDeprecated: yVLT_CLOCK_ENABLE { } | yVLT_CLOCKER { } + | yVLT_ISOLATE_ASSIGNMENTS { } | yVLT_NO_CLOCKER { } ; diff --git a/test_regress/AGENTS.md b/test_regress/AGENTS.md new file mode 100644 index 000000000..7d6b1b708 --- /dev/null +++ b/test_regress/AGENTS.md @@ -0,0 +1,134 @@ + + +# 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/.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_.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. diff --git a/test_regress/t/t_assert_ctl_immediate.out b/test_regress/t/t_assert_ctl_immediate.out index a10fd21c4..58b76b5f2 100644 --- a/test_regress/t/t_assert_ctl_immediate.out +++ b/test_regress/t/t_assert_ctl_immediate.out @@ -1,6 +1,15 @@ -[0] %Error: t_assert_ctl_immediate.v:51: Assertion failed in top.t.module_with_assertctl: 'assert' failed. --Info: t/t_assert_ctl_immediate.v:51: Verilog $stop, ignored due to +verilator+error+limit -[0] %Error: t_assert_ctl_immediate.v:57: Assertion failed in top.t.module_with_assertctl: 'assert' failed. -[0] %Error: t_assert_ctl_immediate.v:45: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed. -[0] %Error: t_assert_ctl_immediate.v:45: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:52: Assertion failed in top.t.module_with_assertctl: 'assert' failed. +-Info: t/t_assert_ctl_immediate.v:52: Verilog $stop, ignored due to +verilator+error+limit +[0] %Error: t_assert_ctl_immediate.v:58: Assertion failed in top.t.module_with_assertctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:46: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:46: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:159: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:132: Assertion failed in top.t.module_with_method_ctl.Ctl.check_positive: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:169: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:93: Assertion failed in top.t.module_with_method_ctl.iface.check_positive: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:180: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:186: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:195: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:199: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:203: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. *-* All Finished *-* diff --git a/test_regress/t/t_assert_ctl_immediate.v b/test_regress/t/t_assert_ctl_immediate.v index 6c5848aaf..cc9d22b1b 100644 --- a/test_regress/t/t_assert_ctl_immediate.v +++ b/test_regress/t/t_assert_ctl_immediate.v @@ -10,6 +10,7 @@ module t ( module_with_assert module_with_assert (clk); module_with_assertctl module_with_assertctl (clk); + module_with_method_ctl module_with_method_ctl (); always @(posedge clk) begin assert (0); @@ -63,3 +64,146 @@ module module_with_assertctl ( f_assert(); end endmodule + +// Assertion control invoked from class methods and interface functions, in a +// single sub-module with a single initial block so the golden output order is +// stable across --fno-inline (otherwise parallel sub-module initials are +// interleaved differently by the inliner). +// +// Covers: +// - class task $assertoff / $asserton +// - class static method $assertcontrol(Off=4 / On=3), IEEE 1800-2023 Table 20-5 +// - $assertkill from a class method +// - assert that lives inside a class method (obeys context-global gating) +// - interface function $assertoff / $asserton +// - assert inside an interface function +// - virtual interface dispatch to an assertion-control function +// - interface class (AstClass with isInterfaceClass) via a concrete impl +// - multiple instances of each thing: OFF via one instance, ON via another +// (assertion control is global per-context, IEEE 1800-2023 20.11) + +interface AssertCtlIf; + function void suppress(); + $assertoff; + endfunction + function void enable(); + $asserton; + endfunction + function void check_positive(int v); + assert (v > 0); + endfunction +endinterface + +// verilog_format: off (verible-verilog-format mangles `pure virtual function`) +interface class IAssertCtl; + pure virtual function void suppress(); + pure virtual function void enable(); +endclass +// verilog_format: on + +class IAssertCtlImpl implements IAssertCtl; + virtual function void suppress(); + $assertoff; + endfunction + virtual function void enable(); + $asserton; + endfunction +endclass + +module module_with_method_ctl; + class Ctl; + virtual AssertCtlIf vif; + static function void off_all(); + $assertcontrol(4); + endfunction + static function void on_all(); + $asserton; + endfunction + function void kill_all(); + $assertkill; + endfunction + function void inst_off(); + $assertoff; + endfunction + function void inst_on(); + $asserton; + endfunction + function void check_positive(int v); + assert (v > 0); + endfunction + function void vif_suppress(); + vif.suppress(); + endfunction + function void vif_enable(); + vif.enable(); + endfunction + endclass + + Ctl c; + Ctl c2; + AssertCtlIf iface (); + AssertCtlIf iface2 (); + IAssertCtlImpl impl; + IAssertCtlImpl impl2; + + initial begin + c = new; + c2 = new; + impl = new; + impl2 = new; + + // --- class method coverage --- + Ctl::off_all(); + assert (0); // gated via class static -> no fire + Ctl::on_all(); + assert (0); // fires + Ctl::off_all(); + c.check_positive(-1); // assert inside class method, gated -> no fire + Ctl::on_all(); + c.check_positive(-2); // assert inside class method, fires + + // --- interface function coverage --- + iface.suppress(); + assert (0); // gated via iface fn -> no fire + iface.enable(); + assert (0); // fires + iface.suppress(); + iface.check_positive(-1); // assert inside iface fn, gated -> no fire + iface.enable(); + iface.check_positive(-2); // assert inside iface fn, fires + + // --- virtual interface dispatch coverage --- + c.vif = iface; + c.vif_suppress(); + assert (0); // gated via virtual interface dispatch -> no fire + c.vif_enable(); + assert (0); // fires + + // --- interface class via concrete impl --- + impl.suppress(); + assert (0); // gated via interface-class impl -> no fire + impl.enable(); + assert (0); // fires + + // --- multiple instances: OFF via one instance, ON via another --- + // Assertion control is global per-context (IEEE 1800-2023 20.11, no scope + // list), so OFF issued via one instance gates every assertion and ON issued + // via a different instance re-enables them. + c.inst_off(); // class: OFF via c + assert (0); // gated -> no fire + c2.inst_on(); // class: ON via c2 + assert (0); // fires + iface.suppress(); // interface: OFF via iface + assert (0); // gated -> no fire + iface2.enable(); // interface: ON via iface2 + assert (0); // fires + impl.suppress(); // interface class: OFF via impl + assert (0); // gated -> no fire + impl2.enable(); // interface class: ON via impl2 + assert (0); // fires + + // --- $assertkill (last: terminal per IEEE 1800-2023 Table 20-5) --- + c.kill_all(); + assert (0); // killed -> no fire + end +endmodule diff --git a/test_regress/t/t_assert_ctl_unsup.out b/test_regress/t/t_assert_ctl_unsup.out index 26e32ff0d..b00a2a58f 100644 --- a/test_regress/t/t_assert_ctl_unsup.out +++ b/test_regress/t/t_assert_ctl_unsup.out @@ -1,123 +1,103 @@ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:25:5: Unsupported: non-constant assert assertion-type expression +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:26:5: Unsupported: non-constant assert assertion-type expression : ... note: In instance 't.unsupported_ctl_type' - 25 | $assertcontrol(Lock, a); + 26 | $assertcontrol(Lock, a); | ^~~~~~~~~~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:27:5: Unsupported: $assertcontrol control_type '2' - 27 | $assertcontrol(Unlock); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:28:5: Unsupported: $assertcontrol control_type '2' + 28 | $assertcontrol(Unlock); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:29:5: Unsupported: $assertcontrol control_type '6' - 29 | $assertcontrol(PassOn); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:30:5: Unsupported: $assertcontrol control_type '6' + 30 | $assertcontrol(PassOn); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:30:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 30 | $assertpasson; - | ^~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:31:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 31 | $assertpasson(a); + 31 | $assertpasson; | ^~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:32:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 32 | $assertpasson(a, t); + 32 | $assertpasson(a); | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:34:5: Unsupported: $assertcontrol control_type '7' - 34 | $assertcontrol(PassOff); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:35:5: Unsupported: assert control assertion_type +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:33:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 35 | $assertpassoff; + 33 | $assertpasson(a, t); + | ^~~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:35:5: Unsupported: $assertcontrol control_type '7' + 35 | $assertcontrol(PassOff); | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:36:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 36 | $assertpassoff(a); + 36 | $assertpassoff; | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:37:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 37 | $assertpassoff(a, t); + 37 | $assertpassoff(a); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:39:5: Unsupported: $assertcontrol control_type '8' - 39 | $assertcontrol(FailOn); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:40:5: Unsupported: assert control assertion_type +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:38:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 40 | $assertfailon; - | ^~~~~~~~~~~~~ + 38 | $assertpassoff(a, t); + | ^~~~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:40:5: Unsupported: $assertcontrol control_type '8' + 40 | $assertcontrol(FailOn); + | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:41:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 41 | $assertfailon(a); + 41 | $assertfailon; | ^~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:42:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 42 | $assertfailon(a, t); + 42 | $assertfailon(a); | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:44:5: Unsupported: $assertcontrol control_type '9' - 44 | $assertcontrol(FailOff); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:45:5: Unsupported: assert control assertion_type +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:43:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 45 | $assertfailoff; + 43 | $assertfailon(a, t); + | ^~~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:45:5: Unsupported: $assertcontrol control_type '9' + 45 | $assertcontrol(FailOff); | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:46:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 46 | $assertfailoff(a); + 46 | $assertfailoff; | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:47:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 47 | $assertfailoff(a, t); + 47 | $assertfailoff(a); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:49:5: Unsupported: $assertcontrol control_type '10' - 49 | $assertcontrol(NonvacuousOn); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:50:5: Unsupported: assert control assertion_type +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:48:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 50 | $assertnonvacuouson; - | ^~~~~~~~~~~~~~~~~~~ + 48 | $assertfailoff(a, t); + | ^~~~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:50:5: Unsupported: $assertcontrol control_type '10' + 50 | $assertcontrol(NonvacuousOn); + | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:51:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 51 | $assertnonvacuouson(a); + 51 | $assertnonvacuouson; | ^~~~~~~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:52:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 52 | $assertnonvacuouson(a, t); + 52 | $assertnonvacuouson(a); | ^~~~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:54:5: Unsupported: $assertcontrol control_type '11' - 54 | $assertcontrol(VacuousOff); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:55:5: Unsupported: assert control assertion_type +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:53:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 55 | $assertvacuousoff; - | ^~~~~~~~~~~~~~~~~ + 53 | $assertnonvacuouson(a, t); + | ^~~~~~~~~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:55:5: Unsupported: $assertcontrol control_type '11' + 55 | $assertcontrol(VacuousOff); + | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:56:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 56 | $assertvacuousoff(a); + 56 | $assertvacuousoff; | ^~~~~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:57:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 57 | $assertvacuousoff(a, t); + 57 | $assertvacuousoff(a); | ^~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:64:5: Unsupported: non-const assert control type expression +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:58:5: Unsupported: assert control assertion_type + : ... note: In instance 't.unsupported_ctl_type' + 58 | $assertvacuousoff(a, t); + | ^~~~~~~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:65:5: Unsupported: non-const assert control type expression : ... note: In instance 't.unsupported_ctl_type_expr' - 64 | $assertcontrol(ctl_type); + 65 | $assertcontrol(ctl_type); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:93:7: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_class' - 93 | $asserton; - | ^~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:99:7: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_class' - 99 | $assertoff; - | ^~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:172:5: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_iface' - 172 | $assertoff; - | ^~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:138:5: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_iface_class' - 138 | $assertoff; - | ^~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:145:5: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_iface_class' - 145 | $asserton; - | ^~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_assert_ctl_unsup.v b/test_regress/t/t_assert_ctl_unsup.v index 98afcad10..90d63e86c 100644 --- a/test_regress/t/t_assert_ctl_unsup.v +++ b/test_regress/t/t_assert_ctl_unsup.v @@ -4,15 +4,16 @@ // SPDX-FileCopyrightText: 2024 Antmicro // SPDX-License-Identifier: CC0-1.0 -module t(input logic clk); - unsupported_ctl_type unsupported_ctl_type(clk ? 1 : 2); - unsupported_ctl_type_expr unsupported_ctl_type_expr(); - assert_class assert_class(); - assert_iface assert_iface(); - assert_iface_class assert_iface_class(); +module t ( + input logic clk +); + unsupported_ctl_type unsupported_ctl_type (clk ? 1 : 2); + unsupported_ctl_type_expr unsupported_ctl_type_expr (); endmodule -module unsupported_ctl_type(input int a); +module unsupported_ctl_type ( + input int a +); initial begin let Lock = 1; let Unlock = 2; @@ -64,122 +65,3 @@ module unsupported_ctl_type_expr; $assertcontrol(ctl_type); end endmodule - -module assert_class; - virtual class AssertCtl; - pure virtual function void virtual_assert_ctl(); - endclass - - class AssertCls; - static function void static_function(); - assert(0); - endfunction - static task static_task(); - assert(0); - endtask - function void assert_function(); - assert(0); - endfunction - task assert_task(); - assert(0); - endtask - virtual function void virtual_assert(); - assert(0); - endfunction - endclass - - class AssertOn extends AssertCtl; - virtual function void virtual_assert_ctl(); - $asserton; - endfunction - endclass - - class AssertOff extends AssertCtl; - virtual function void virtual_assert_ctl(); - $assertoff; - endfunction - endclass - - AssertCls assertCls; - AssertOn assertOn; - AssertOff assertOff; - initial begin - $assertoff; - AssertCls::static_function(); - AssertCls::static_task(); - $asserton; - AssertCls::static_function(); - AssertCls::static_task(); - - assertCls = new; - assertOn = new; - assertOff = new; - - assertOff.virtual_assert_ctl(); - assertCls.assert_function(); - assertCls.assert_task(); - assertCls.virtual_assert(); - - assertOn.virtual_assert_ctl(); - assertCls.assert_function(); - assertCls.assert_task(); - assertCls.virtual_assert(); - assertOff.virtual_assert_ctl(); - assertCls.assert_function(); - end -endmodule - -interface Iface; - function void assert_func(); - assert(0); - endfunction - - function void assertoff_func(); - $assertoff; - endfunction - - initial begin - assertoff_func(); - assert(0); - assert_func(); - $asserton; - assert(0); - assert_func(); - end -endinterface - -module assert_iface; - Iface iface(); - virtual Iface vIface = iface; - initial begin - vIface.assert_func(); - vIface.assertoff_func(); - vIface.assert_func(); - - iface.assert_func(); - iface.assertoff_func(); - iface.assert_func(); - end -endmodule - -interface class IfaceClass; - pure virtual function void assertoff_func(); - pure virtual function void assert_func(); -endclass - -class IfaceClassImpl implements IfaceClass; - virtual function void assertoff_func(); - $assertoff; - endfunction - virtual function void assert_func(); - assert(0); - endfunction -endclass - -module assert_iface_class; - IfaceClassImpl ifaceClassImpl = new; - initial begin - ifaceClassImpl.assertoff_func(); - ifaceClassImpl.assert_func(); - end -endmodule diff --git a/test_regress/t/t_assert_iff_clk.py b/test_regress/t/t_assert_iff_clk.py new file mode 100755 index 000000000..fd4b2dc44 --- /dev/null +++ b/test_regress/t/t_assert_iff_clk.py @@ -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() diff --git a/test_regress/t/t_assert_iff_clk.v b/test_regress/t/t_assert_iff_clk.v new file mode 100644 index 000000000..3dfb3458b --- /dev/null +++ b/test_regress/t/t_assert_iff_clk.v @@ -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 diff --git a/test_regress/t/t_assert_iff_clk_unsup.out b/test_regress/t/t_assert_iff_clk_unsup.out deleted file mode 100644 index 3ff98273e..000000000 --- a/test_regress/t/t_assert_iff_clk_unsup.out +++ /dev/null @@ -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 diff --git a/test_regress/t/t_assert_iff_clk_unsup.v b/test_regress/t/t_assert_iff_clk_unsup.v deleted file mode 100644 index efe917a04..000000000 --- a/test_regress/t/t_assert_iff_clk_unsup.v +++ /dev/null @@ -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 diff --git a/test_regress/t/t_assert_opt_check.py b/test_regress/t/t_assert_opt_check.py index b7652f1ab..884e2876c 100755 --- a/test_regress/t/t_assert_opt_check.py +++ b/test_regress/t/t_assert_opt_check.py @@ -15,7 +15,7 @@ test.compile(verilator_flags2=['--binary', '--stats']) test.execute(check_finished=True) -test.file_grep(test.stats, r'Assertions, assertOn checks combined\s+(\d+)', 2) -test.file_grep(test.stats, r'Assertions, assertOn checks hoisted\s+(\d+)', 11) +test.file_grep(test.stats, r'Assertions, assertOn checks combined\s+(\d+)', 3) +test.file_grep(test.stats, r'Assertions, assertOn checks hoisted\s+(\d+)', 15) test.passes() diff --git a/test_regress/t/t_assert_opt_check.v b/test_regress/t/t_assert_opt_check.v index ccfea327e..7d0f54537 100644 --- a/test_regress/t/t_assert_opt_check.v +++ b/test_regress/t/t_assert_opt_check.v @@ -58,4 +58,12 @@ module t; end end + // Should combine the 2 nested assertOn checks after hoisting + always @(posedge clk) begin + if (assertEnable) begin + // This is an 'assert' with another 'assert' in the fail branch + assert(cntB - 100 == cntA); else assert(cntB == cntA + 100); + end + end + endmodule diff --git a/test_regress/t/t_assoc_method.v b/test_regress/t/t_assoc_method.v index 3a7ecdfde..13ab0411f 100644 --- a/test_regress/t/t_assoc_method.v +++ b/test_regress/t/t_assoc_method.v @@ -22,15 +22,23 @@ module t; int qe[int]; // Empty int qv[$]; // Value returns int qi[$]; // Index returns + bit[229:0] qw[int]; // Wide values + bit[229:0] qwe[int]; // Wide values - empty + bit[229:0] qwv[$]; // Wide values - Value returns + int qwi[$]; // Wide values - Index returns point points_q[int]; point points_qe[int]; // Empty points point points_qv[$]; int i; bit b; + bit[229:0] w; q = '{10: 1, 11: 2, 12: 2, 13: 4, 14: 3}; `checkp(q, "'{'ha:'h1, 'hb:'h2, 'hc:'h2, 'hd:'h4, 'he:'h3}"); + qw = '{10: 1, 11: 2, 12: 2, 13: 4, 14: 3}; + `checkp(qw, "'{'ha:'h1, 'hb:'h2, 'hc:'h2, 'hd:'h4, 'he:'h3}"); + // NOT tested: with ... selectors //q.sort; // Not legal on assoc - see t_assoc_meth_bad @@ -43,16 +51,34 @@ module t; `checkp(qv, "'{'h1, 'h2, 'h4, 'h3}"); qv = qe.unique; `checkp(qv, "'{}"); + + qwv = qw.unique; + `checkp(qwv, "'{'h1, 'h2, 'h4, 'h3}"); + qwv = qwe.unique; + `checkp(qwv, "'{}"); + qi = q.unique_index; qi.sort; `checkp(qi, "'{'ha, 'hb, 'hd, 'he}"); qi = qe.unique_index; `checkp(qi, "'{}"); + qwi = qw.unique_index; + qwi.sort; + `checkp(qwi, "'{'ha, 'hb, 'hd, 'he}"); + qwi = qwe.unique_index; + `checkp(qwi, "'{}"); + points_q[0] = point'{1, 2}; points_q[1] = point'{2, 4}; points_q[5] = point'{1, 4}; + qi = points_qe.unique_index(); + `checkp(qi, "'{}"); + + qi = points_q.unique_index(); + `checkh(qi.size, 3); + points_qv = points_q.unique(p) with (p.x); `checkh(points_qv.size, 2); qi = points_q.unique_index (p) with (p.x + p.y); @@ -113,6 +139,10 @@ module t; qv = q.min; `checkp(qv, "'{'h1}"); + + qwv = qw.min; + `checkp(qwv, "'{'h1}"); + points_qv = points_q.min(p) with (p.x + p.y); if (points_qv[0].x != 1 || points_qv[0].y != 2) $stop; @@ -130,8 +160,13 @@ module t; qv = qe.max(x) with (x + 1); `checkp(qv, "'{}"); - // Reduction methods + // Wide + qwv = qwe.min; + `checkp(qwv, "'{}"); + qwv = qwe.max; + `checkp(qwv, "'{}"); + // Reduction methods i = q.sum; `checkh(i, 32'hc); i = q.sum with (item + 1); @@ -141,6 +176,12 @@ module t; i = q.product with (item + 1); `checkh(i, 32'h168); + // Wide + w = qw.sum; + `checkh(w, 230'hc); + w = qw.product; + `checkh(w, 230'h30); + i = qe.sum; `checkh(i, 32'h0); i = qe.sum with (item + 1); @@ -150,6 +191,12 @@ module t; i = qe.product with (item + 1); `checkh(i, 32'h0); + // Wide + w = qwe.sum; + `checkh(w, 230'h0); + w = qwe.product; + `checkh(w, 230'h0); + q = '{10: 32'b1100, 11: 32'b1010}; i = q.and; `checkh(i, 32'b1000); @@ -164,6 +211,14 @@ module t; i = q.xor with (item + 1); `checkh(i, 32'b0110); + qw = '{10: 230'b1100, 11: 230'b1010}; + w = qw.and; + `checkh(w, 230'b1000); + w = qw.or; + `checkh(w, 230'b1110); + w = qw.xor; + `checkh(w, 230'b0110); + i = qe.and; `checkh(i, 32'b0); i = qe.and with (item + 1); @@ -177,6 +232,14 @@ module t; i = qe.xor with (item + 1); `checkh(i, 32'b0); + // Wide + w = qwe.and; + `checkh(w, 230'b0); + w = qwe.or; + `checkh(w, 230'b0); + w = qwe.xor; + `checkh(w, 230'b0); + i = q.and(); `checkh(i, 32'b1000); i = q.and() with (item + 1); @@ -190,6 +253,14 @@ module t; i = q.xor() with (item + 1); `checkh(i, 32'b0110); + // Wide + w = qw.and(); + `checkh(w, 230'b1000); + w = qw.or(); + `checkh(w, 230'b1110); + w = qw.xor(); + `checkh(w, 230'b0110); + i = qe.and(); `checkh(i, 32'b0); i = qe.or(); @@ -197,6 +268,14 @@ module t; i = qe.xor(); `checkh(i, 32'b0); + // Wide + w = qwe.and(); + `checkh(w, 230'b0); + w = qwe.or(); + `checkh(w, 230'b0); + w = qwe.xor(); + `checkh(w, 230'b0); + q = '{10: 1, 11: 2}; qe = '{10: 1, 11: 2}; `checkh(q == qe, 1'b1); diff --git a/test_regress/t/t_assoc_unsup.out b/test_regress/t/t_assoc_unsup.out new file mode 100644 index 000000000..0895427a3 --- /dev/null +++ b/test_regress/t/t_assoc_unsup.out @@ -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 diff --git a/test_regress/t/t_assert_iff_clk_unsup.py b/test_regress/t/t_assoc_unsup.py similarity index 79% rename from test_regress/t/t_assert_iff_clk_unsup.py rename to test_regress/t/t_assoc_unsup.py index b5718946c..30986489e 100755 --- a/test_regress/t/t_assert_iff_clk_unsup.py +++ b/test_regress/t/t_assoc_unsup.py @@ -9,8 +9,8 @@ import vltest_bootstrap -test.scenarios('vlt') +test.scenarios("linter") -test.lint(expect_filename=test.golden_filename, verilator_flags2=['--assert'], fails=True) +test.lint(fails=True, expect_filename=test.golden_filename) test.passes() diff --git a/test_regress/t/t_assoc_unsup.v b/test_regress/t/t_assoc_unsup.v new file mode 100644 index 000000000..848bf0bf8 --- /dev/null +++ b/test_regress/t/t_assoc_unsup.v @@ -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 diff --git a/test_regress/t/t_case_call_count.py b/test_regress/t/t_case_call_count.py index 4116217e8..df91d0e3c 100755 --- a/test_regress/t/t_case_call_count.py +++ b/test_regress/t/t_case_call_count.py @@ -15,6 +15,6 @@ test.compile(verilator_flags2=['--stats']) test.execute() -test.file_grep(test.stats, r'Impure case expressions\s+(\d+)', 2) +test.file_grep(test.stats, r'LiftExpr, lifted calls\s+(\d+)', 3) test.passes() diff --git a/test_regress/t/t_case_huge.py b/test_regress/t/t_case_huge.py index 81fcffbad..0ac31a2f8 100755 --- a/test_regress/t/t_case_huge.py +++ b/test_regress/t/t_case_huge.py @@ -11,7 +11,8 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +# This tests combining CFuncs, but Dfg would inline the submodule, disabling +test.compile(verilator_flags2=["--stats", "-fno-dfg"]) test.execute() diff --git a/test_regress/t/t_case_inside_call_count.py b/test_regress/t/t_case_inside_call_count.py index 4116217e8..df91d0e3c 100755 --- a/test_regress/t/t_case_inside_call_count.py +++ b/test_regress/t/t_case_inside_call_count.py @@ -15,6 +15,6 @@ test.compile(verilator_flags2=['--stats']) test.execute() -test.file_grep(test.stats, r'Impure case expressions\s+(\d+)', 2) +test.file_grep(test.stats, r'LiftExpr, lifted calls\s+(\d+)', 3) test.passes() diff --git a/test_regress/t/t_case_inside_with_x.py b/test_regress/t/t_case_inside_with_x.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_case_inside_with_x.py @@ -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() diff --git a/test_regress/t/t_case_inside_with_x.v b/test_regress/t/t_case_inside_with_x.v new file mode 100644 index 000000000..935d779e8 --- /dev/null +++ b/test_regress/t/t_case_inside_with_x.v @@ -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 diff --git a/test_regress/t/t_case_priority_overlap.py b/test_regress/t/t_case_priority_overlap.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_case_priority_overlap.py @@ -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() diff --git a/test_regress/t/t_case_priority_overlap.v b/test_regress/t/t_case_priority_overlap.v new file mode 100644 index 000000000..9b7e794d6 --- /dev/null +++ b/test_regress/t/t_case_priority_overlap.v @@ -0,0 +1,57 @@ +// 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 + +// Regression: a `priority` case item whose later condition (INST_B) is fully +// subsumed by an earlier condition (INST_A) of the *same* item. Overlap within +// a single case item is legal, but this previously crashed V3Case with a null +// pointer dereference: the priority "case item ignored" CASEOVERLAP diagnostic +// dereferenced 'overlappedCondp', which is null when the only covering item is +// the item itself. Must compile cleanly and simulate correctly. + +// 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 t; + + logic clk = 1'b0; + always #5 clk = ~clk; + + logic [1:0] in; + logic [1:0] out; + + always_comb begin + priority casez (in) + 2'b1?, // fully subsumes 2'b11 below on the same case clause + 2'b11: + out = 2'b10; + 2'b0?: out = 2'b01; + endcase + end + + initial begin + in = 2'b00; + @(posedge clk); + `checkh(out, 2'b01); + + in = 2'b01; + @(posedge clk); + `checkh(out, 2'b01); + + in = 2'b10; + @(posedge clk); + `checkh(out, 2'b10); + + in = 2'b11; + @(posedge clk); + `checkh(out, 2'b10); + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_case_unique_overlap.py b/test_regress/t/t_case_unique_overlap.py index 8a938befd..f3945fcdd 100755 --- a/test_regress/t/t_case_unique_overlap.py +++ b/test_regress/t/t_case_unique_overlap.py @@ -11,8 +11,10 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile() +test.compile(verilator_flags2=['--stats']) test.execute() +test.file_grep(test.stats, r'Optimizations, Cases proven assertions\s+(\d+)', 1) + test.passes() diff --git a/test_regress/t/t_constraint_json_only.out b/test_regress/t/t_constraint_json_only.out index ce4b7e0d1..c18d8a989 100644 --- a/test_regress/t/t_constraint_json_only.out +++ b/test_regress/t/t_constraint_json_only.out @@ -29,7 +29,7 @@ {"type":"VAR","name":"state","addr":"(Z)","loc":"d,17:10,17:15","dtypep":"(M)","origName":"state","verilogName":"state","direction":"NONE","lifetime":"VAUTOMI","varType":"MEMBER","dtypeName":"string","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"FUNC","name":"strings_equal","addr":"(AB)","loc":"d,61:16,61:29","dtypep":"(U)","method":true,"cname":"strings_equal", "fvarp": [ - {"type":"VAR","name":"strings_equal","addr":"(BB)","loc":"d,61:16,61:29","dtypep":"(U)","origName":"strings_equal","verilogName":"strings_equal","direction":"OUTPUT","noCReset":true,"isFuncReturn":true,"isFuncLocal":true,"lifetime":"VAUTOM","varType":"VAR","dtypeName":"bit","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []} + {"type":"VAR","name":"strings_equal","addr":"(BB)","loc":"d,61:16,61:29","dtypep":"(U)","origName":"strings_equal","verilogName":"strings_equal","direction":"OUTPUT","noCReset":true,"icoMaybeWritten":true,"isFuncReturn":true,"isFuncLocal":true,"lifetime":"VAUTOM","varType":"VAR","dtypeName":"bit","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []} ],"classOrPackagep": [], "stmtsp": [ {"type":"VAR","name":"a","addr":"(CB)","loc":"d,61:37,61:38","dtypep":"(M)","origName":"a","verilogName":"a","direction":"INPUT","isFuncLocal":true,"lifetime":"VAUTOMI","varType":"PORT","dtypeName":"string","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, diff --git a/test_regress/t/t_constraint_redops.py b/test_regress/t/t_constraint_redops.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_redops.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +if not test.have_solver: + test.skip("No constraint solver installed") + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_constraint_redops.v b/test_regress/t/t_constraint_redops.v new file mode 100644 index 000000000..5eff12a63 --- /dev/null +++ b/test_regress/t/t_constraint_redops.v @@ -0,0 +1,123 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 by Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// Test case for reducing and in constraint +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`define check_rand(cl, field, gotv, expv, count) \ +begin \ + automatic longint prev_result; \ + automatic int ok; \ + if (!bit'(cl.randomize())) $stop; \ + prev_result = longint'(field); \ + `checkd(gotv, expv) \ + repeat(count) begin \ + longint result; \ + if (!bit'(cl.randomize())) $stop; \ + result = longint'(field); \ + `checkd(gotv, expv) \ + if (result != prev_result) ok = 1; \ + prev_result = result; \ + end \ + if (ok != 1) $stop; \ +end +// verilog_format: on + +class test_redops_bitfields #(RANDVAL_BITWIDTH=8); + rand bit [RANDVAL_BITWIDTH-1:0] rand_val; + rand bit redand; + rand bit redxor; + rand bit redor; + + constraint c { + redand == &rand_val; + } + + constraint d { + redxor == ^rand_val; + } + + constraint e { + redor == |rand_val; + } + + function bit calc_redand(); + bit result = 1'b1; + + foreach (rand_val[idx]) begin + result &= rand_val[idx]; + end + + return result; + endfunction + + function bit calc_redxor(); + bit result; + + foreach (rand_val[idx]) begin + result ^= rand_val[idx]; + end + + return result; + endfunction + + function bit calc_redor(); + bit result = 1'b0; + + foreach (rand_val[idx]) begin + result |= rand_val[idx]; + end + + return result; + endfunction + + function void verify(); + //`check_rand(this, this.rand_val, this.redand, this.calc_redand(), 20); + `check_rand(this, this.rand_val, this.redxor, this.calc_redxor(), 20); + //`check_rand(this, this.rand_val, this.redor, this.calc_redor(), 20); + endfunction +endclass + +module t; + test_redops_bitfields #(.RANDVAL_BITWIDTH(1)) redops_1bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(8)) redops_8bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(16)) redops_16bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(32)) redops_32bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(47)) redops_47bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(63)) redops_63bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(64)) redops_64bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(128)) redops_128bit; + + initial begin + redops_1bit = new(); + redops_1bit.verify(); + + redops_8bit = new(); + redops_8bit.verify(); + + redops_16bit = new(); + redops_16bit.verify(); + + redops_32bit = new(); + redops_32bit.verify(); + + redops_47bit = new(); + redops_47bit.verify(); + + redops_63bit = new(); + redops_63bit.verify(); + + redops_64bit = new(); + redops_64bit.verify(); + + redops_128bit = new(); + redops_128bit.verify(); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_constraint_unsup.v b/test_regress/t/t_constraint_unsup.v index 4b1c95031..84f67e5ce 100644 --- a/test_regress/t/t_constraint_unsup.v +++ b/test_regress/t/t_constraint_unsup.v @@ -1,7 +1,7 @@ // DESCRIPTION: Verilator: Verilog Test module // // This file ONLY is placed under the Creative Commons Public Domain. -// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-FileCopyrightText: 2025 Antmicro // SPDX-License-Identifier: CC0-1.0 class Packet; diff --git a/test_regress/t/t_cover_fsm_concat_unsup.out b/test_regress/t/t_cover_fsm_concat_unsup.out new file mode 100644 index 000000000..85da4d440 --- /dev/null +++ b/test_regress/t/t_cover_fsm_concat_unsup.out @@ -0,0 +1,6 @@ +%Warning-COVERIGN: t/t_cover_fsm_concat_unsup.v:12:17: Ignoring unsupported: FSM coverage with CONCAT + 12 | assign c = ({a, b} == 8'h00); + | ^ + ... For warning description see https://verilator.org/warn/COVERIGN?v=latest + ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Error: Exiting due to diff --git a/test_regress/t/t_cover_fsm_concat_unsup.py b/test_regress/t/t_cover_fsm_concat_unsup.py new file mode 100755 index 000000000..4a63bc600 --- /dev/null +++ b/test_regress/t/t_cover_fsm_concat_unsup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage concat as unsupported operation test +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(fails=True, expect_filename=test.golden_filename, verilator_flags2=['--coverage']) + +test.passes() diff --git a/test_regress/t/t_cover_fsm_concat_unsup.v b/test_regress/t/t_cover_fsm_concat_unsup.v new file mode 100644 index 000000000..a7d9a1f84 --- /dev/null +++ b/test_regress/t/t_cover_fsm_concat_unsup.v @@ -0,0 +1,18 @@ +// 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 ( + input logic [6:0] a, + input logic b, + output logic c +); + assign c = ({a, b} == 8'h00); + + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_cover_fsm_sel.out b/test_regress/t/t_cover_fsm_sel.out new file mode 100644 index 000000000..a87e0569d --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel.out @@ -0,0 +1,97 @@ +// // verilator_coverage annotation + // 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 + + package P; + typedef struct packed {logic [7:0] vs;} C; + typedef struct packed { + C a; + int b; + } B; + typedef struct packed {B a;} A; + endpackage + + module t ( +%000009 input clk + ); + typedef enum logic [1:0] { + S_IDLE = 2'd0, + S_RUN = 2'd1, + S_DONE = 2'd2, + S_ERR = 2'd3 + } state_t; + +%000001 logic rst; +%000001 logic start; + integer cyc; +%000001 state_t state /*verilator fsm_reset_arc*/; +%000002 P::A a; +%000001 logic done; + + logic [7:0] va[int]; + logic [7:0] va2d[int][int]; + +%000001 logic b; +%000001 logic c; +%000001 logic d; + + assign b = (a.a.a.vs == 8'h0); + assign c = (va[0] == 8'h0); + assign d = (va2d[0][0] == 8'h0); + +%000001 initial begin +%000001 rst = 1'b1; +%000001 start = 1'b0; +%000001 cyc = 0; + end + +%000009 always @(posedge clk) begin +%000009 cyc <= cyc + 1; +%000008 if (cyc == 1) rst <= 1'b0; +%000008 if (cyc == 2) start <= 1'b1; +%000008 if (cyc == 3) start <= 1'b0; +%000008 if (cyc == 8) begin +%000001 $write("*-* All Finished *-*\n"); +%000001 $finish; + end + end + +%000009 always_ff @(posedge clk) begin +%000007 if (rst) begin +%000002 state <= S_IDLE; + end +%000007 else begin +%000007 case (state) + // [FSM coverage] +%000001 // [fsm_arc t.state::ANY->S_IDLE[reset_include]] [reset arc, excluded from %] +%000002 // [fsm_arc t.state::S_DONE->S_DONE] +%000003 // [fsm_arc t.state::S_IDLE->S_IDLE] +%000001 // [fsm_arc t.state::S_IDLE->S_RUN] +%000001 // [fsm_state t.state::S_DONE] +%000000 // [fsm_state t.state::S_ERR] *** UNCOVERED *** +%000000 // [fsm_state t.state::S_IDLE] *** UNCOVERED *** +%000001 // [fsm_state t.state::S_RUN] +%000002 S_IDLE: +%000001 if (start) state <= S_RUN; +%000001 else state <= S_IDLE; +%000003 S_RUN: begin +%000003 a.a.a.vs <= a.a.a.vs + 1; +%000003 done <= (a.a.a.vs == 8'h1); +%000002 if (done) begin +%000001 state <= S_DONE; + end +%000002 else begin +%000002 state <= S_RUN; + end + end +%000002 S_DONE: state <= S_DONE; +%000000 default: state <= S_ERR; + endcase + end + end + + endmodule + diff --git a/test_regress/t/t_cover_fsm_sel.py b/test_regress/t/t_cover_fsm_sel.py new file mode 100755 index 000000000..9d066e89e --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage array sel test +# +# 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 os + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--coverage"]) + +test.execute() + +test.run(cmd=[ + os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage", + "--annotate", + test.obj_dir + "/annotated", + test.obj_dir + "/coverage.dat", +], + verilator_run=True) + +test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_cover_fsm_sel.v b/test_regress/t/t_cover_fsm_sel.v new file mode 100644 index 000000000..732a63553 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel.v @@ -0,0 +1,86 @@ +// 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 + +package P; + typedef struct packed {logic [7:0] vs;} C; + typedef struct packed { + C a; + int b; + } B; + typedef struct packed {B a;} A; +endpackage + +module t ( + input clk +); + typedef enum logic [1:0] { + S_IDLE = 2'd0, + S_RUN = 2'd1, + S_DONE = 2'd2, + S_ERR = 2'd3 + } state_t; + + logic rst; + logic start; + integer cyc; + state_t state /*verilator fsm_reset_arc*/; + P::A a; + logic done; + + logic [7:0] va[int]; + logic [7:0] va2d[int][int]; + + logic b; + logic c; + logic d; + + assign b = (a.a.a.vs == 8'h0); + assign c = (va[0] == 8'h0); + assign d = (va2d[0][0] == 8'h0); + + initial begin + rst = 1'b1; + start = 1'b0; + cyc = 0; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) rst <= 1'b0; + if (cyc == 2) start <= 1'b1; + if (cyc == 3) start <= 1'b0; + if (cyc == 8) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + always_ff @(posedge clk) begin + if (rst) begin + state <= S_IDLE; + end + else begin + case (state) + S_IDLE: + if (start) state <= S_RUN; + else state <= S_IDLE; + S_RUN: begin + a.a.a.vs <= a.a.a.vs + 1; + done <= (a.a.a.vs == 8'h1); + if (done) begin + state <= S_DONE; + end + else begin + state <= S_RUN; + end + end + S_DONE: state <= S_DONE; + default: state <= S_ERR; + endcase + end + end + +endmodule diff --git a/test_regress/t/t_cover_fsm_sel_assign.out b/test_regress/t/t_cover_fsm_sel_assign.out new file mode 100644 index 000000000..1f0c3983e --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_assign.out @@ -0,0 +1,90 @@ +// // verilator_coverage annotation + // 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 #( + parameter int unsigned W = 16, + parameter int unsigned D = 4, + parameter int unsigned BW = 2 + ) ( +%000009 input clk + ); + typedef enum logic [1:0] { + S_IDLE = 2'd0, + S_RUN = 2'd1, + S_DONE = 2'd2, + S_ERR = 2'd3 + } state_t; + +%000001 logic rst; +%000001 logic start; + integer cyc; +%000001 state_t state /*verilator fsm_reset_arc*/; +%000001 logic [1:0] done_arr; + +%000001 logic [W-1:0] a; +%000000 logic [BW-1:0] b; + begin +%000001 logic [D-1:0][W-1:0] s; + begin +%000009 always_ff @(posedge clk) s[b] <= a; + end + end + +%000001 initial begin +%000001 rst = 1'b1; +%000001 start = 1'b0; +%000001 cyc = 0; + end + +%000009 always @(posedge clk) begin +%000009 cyc <= cyc + 1; +%000008 if (cyc == 1) rst <= 1'b0; +%000008 if (cyc == 2) start <= 1'b1; +%000008 if (cyc == 3) start <= 1'b0; +%000008 if (cyc == 4) a[0] = 1'b1; +%000008 if (cyc == 8) begin +%000001 $write("*-* All Finished *-*\n"); +%000001 $finish; + end + end + +%000009 always_ff @(posedge clk) begin +%000007 if (rst) begin +%000002 state <= S_IDLE; + end +%000007 else begin +%000007 case (state) + // [FSM coverage] +%000001 // [fsm_arc t.state::ANY->S_IDLE[reset_include]] [reset arc, excluded from %] +%000002 // [fsm_arc t.state::S_DONE->S_DONE] +%000003 // [fsm_arc t.state::S_IDLE->S_IDLE] +%000001 // [fsm_arc t.state::S_IDLE->S_RUN] +%000001 // [fsm_state t.state::S_DONE] +%000000 // [fsm_state t.state::S_ERR] *** UNCOVERED *** +%000000 // [fsm_state t.state::S_IDLE] *** UNCOVERED *** +%000001 // [fsm_state t.state::S_RUN] +%000002 S_IDLE: +%000001 if (start) state <= S_RUN; +%000001 else state <= S_IDLE; +%000003 S_RUN: begin + ; +%000003 done_arr[0] <= (a[0] == 1'b1); +%000002 if (done_arr[0]) begin +%000001 state <= S_DONE; + end +%000002 else begin +%000002 state <= S_RUN; + end + end +%000002 S_DONE: state <= S_DONE; +%000000 default: state <= S_ERR; + endcase + end + end + + endmodule + diff --git a/test_regress/t/t_cover_fsm_sel_assign.py b/test_regress/t/t_cover_fsm_sel_assign.py new file mode 100755 index 000000000..728658c89 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_assign.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage assignment test +# +# 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 os + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--coverage"]) + +test.execute() + +test.run(cmd=[ + os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage", + "--annotate", + test.obj_dir + "/annotated", + test.obj_dir + "/coverage.dat", +], + verilator_run=True) + +test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_cover_fsm_sel_assign.v b/test_regress/t/t_cover_fsm_sel_assign.v new file mode 100644 index 000000000..9f3ecca24 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_assign.v @@ -0,0 +1,79 @@ +// 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 #( + parameter int unsigned W = 16, + parameter int unsigned D = 4, + parameter int unsigned BW = 2 +) ( + input clk +); + typedef enum logic [1:0] { + S_IDLE = 2'd0, + S_RUN = 2'd1, + S_DONE = 2'd2, + S_ERR = 2'd3 + } state_t; + + logic rst; + logic start; + integer cyc; + state_t state /*verilator fsm_reset_arc*/; + logic [1:0] done_arr; + + logic [W-1:0] a; + logic [BW-1:0] b; + begin + logic [D-1:0][W-1:0] s; + begin + always_ff @(posedge clk) s[b] <= a; + end + end + + initial begin + rst = 1'b1; + start = 1'b0; + cyc = 0; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) rst <= 1'b0; + if (cyc == 2) start <= 1'b1; + if (cyc == 3) start <= 1'b0; + if (cyc == 4) a[0] = 1'b1; + if (cyc == 8) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + always_ff @(posedge clk) begin + if (rst) begin + state <= S_IDLE; + end + else begin + case (state) + S_IDLE: + if (start) state <= S_RUN; + else state <= S_IDLE; + S_RUN: begin + ; + done_arr[0] <= (a[0] == 1'b1); + if (done_arr[0]) begin + state <= S_DONE; + end + else begin + state <= S_RUN; + end + end + S_DONE: state <= S_DONE; + default: state <= S_ERR; + endcase + end + end + +endmodule diff --git a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out new file mode 100644 index 000000000..4b76f7298 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out @@ -0,0 +1,11 @@ +%Warning-COVERIGN: t/t_cover_fsm_sel_togglevar_unsup.v:19:16: Coverage ignored for type ASSOCARRAYDTYPE + : ... note: In instance 't' + 19 | input P::A a, + | ^ + ... For warning description see https://verilator.org/warn/COVERIGN?v=latest + ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Warning-COVERIGN: t/t_cover_fsm_sel_togglevar_unsup.v:19:16: Coverage ignored for type WILDCARDARRAYDTYPE + : ... note: In instance 't' + 19 | input P::A a, + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.py b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.py new file mode 100755 index 000000000..59e566c59 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage ignores associative array selection operators +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(fails=True, expect_filename=test.golden_filename, verilator_flags2=['--coverage']) + +test.passes() diff --git a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v new file mode 100644 index 000000000..9be3e9a7a --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v @@ -0,0 +1,30 @@ +// 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 + +package P; + typedef struct { + logic [7:0] va[int]; + logic [7:0] vw[*]; + } C; + typedef struct { + C a; + int b; + } B; + typedef struct {B a;} A; +endpackage +module t ( + input P::A a, + output logic b, + output logic c +); + assign b = (a.a.a.va[0] == 8'h0); + assign c = (a.a.a.vw[0] == 8'h0); + + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_cover_sequence.py b/test_regress/t/t_cover_sequence.py new file mode 100755 index 000000000..23e13d99b --- /dev/null +++ b/test_regress/t/t_cover_sequence.py @@ -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(timing_loop=True, verilator_flags2=['--assert', '--timing', '--coverage-user']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_cover_sequence.v b/test_regress/t/t_cover_sequence.v new file mode 100644 index 000000000..719a3c1ba --- /dev/null +++ b/test_regress/t/t_cover_sequence.v @@ -0,0 +1,90 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define 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); +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + + logic [63:0] crc = 64'h5aef0c8dd70a4497; + logic rst_n = 1'b0; + logic a, b, c, d, e; + int cyc = 0; + + int hit_simple = 0; + int hit_clocked = 0; + int hit_clocked_disable = 0; + int hit_default_disable = 0; + int hit_consrep_range = 0; + int hit_consrep_2 = 0; + int hit_consrep_3 = 0; + + default clocking cb @(posedge clk); + endclocking + + // Non-adjacent CRC bits to avoid LFSR shift correlation + assign a = crc[0]; + assign b = crc[5]; + assign c = crc[10]; + assign d = crc[15]; + assign e = crc[20]; + + // Form 1: cover sequence ( sexpr ) stmt + cover sequence (a | b | c | d | e) hit_simple++; + + // Form 2: cover sequence ( clocking_event sexpr ) stmt + cover sequence (@(posedge clk) (a | b | c | d | e) ##[1:3] b) hit_clocked++; + + // Form 3: cover sequence ( clocking_event disable iff (expr) sexpr ) stmt + cover sequence (@(posedge clk) disable iff (!rst_n) a ##1 b) hit_clocked_disable++; + + // Form 4: cover sequence ( disable iff (expr) sexpr ) stmt + cover sequence (disable iff (!rst_n) a ##1 c) hit_default_disable++; + + // Form 5: consecutive repetition, counted per end-of-match + cover sequence (a [* 2: 3]) hit_consrep_range++; + cover sequence (a [* 2]) hit_consrep_2++; + cover sequence (a [* 3]) hit_consrep_3++; + + always @(posedge clk) begin +`ifdef TEST_VERBOSE + $write("[%0t] cyc=%0d crc=%x a=%b b=%b c=%b d=%b e=%b\n", $time, cyc, crc, a, b, c, d, e); +`endif + cyc <= cyc + 1; + crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[1] ^ crc[0]}; + if (cyc == 2) rst_n <= 1'b1; + if (cyc == 99) begin + `checkh(crc, 64'h261a9f1371d7aadf); + $finish; + end + end + + // Read the counters in 'final', not the clocked block: a same-cycle read of a + // cover counter races the cover's increment under --threads (vltmt). Verilator + // counts one more end-of-match than Questa 2022.3 on some forms at the + // simulation boundary; the Questa value is noted per check. + final begin +`ifdef TEST_VERBOSE + $write("simple=%0d clocked=%0d clk_dis=%0d def_dis=%0d range=%0d 2=%0d 3=%0d\n", hit_simple, + hit_clocked, hit_clocked_disable, hit_default_disable, hit_consrep_range, hit_consrep_2, + hit_consrep_3); +`endif + `checkd(hit_simple, 96); // Questa: 95 + `checkd(hit_clocked, 149); // Questa: 149 + `checkd(hit_clocked_disable, 28); // Questa: 27 + `checkd(hit_default_disable, 30); // Questa: 30 + `checkd(hit_consrep_2, 30); // Questa: 29 + `checkd(hit_consrep_3, 14); // Questa: 13 + // a[*2:3] == a[*2] or a[*3] (IEEE 1800-2023 16.9.2) + `checkd(hit_consrep_range, hit_consrep_2 + hit_consrep_3); // 44; Questa: 42 + $write("*-* All Finished *-*\n"); + end +endmodule diff --git a/test_regress/t/t_cover_sequence_unsup.out b/test_regress/t/t_cover_sequence_unsup.out new file mode 100644 index 000000000..47af703cb --- /dev/null +++ b/test_regress/t/t_cover_sequence_unsup.out @@ -0,0 +1,18 @@ +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:21:33: Ignoring unsupported: cover sequence with a sequence operand of 'or' + 21 | cover sequence ((a ##[1:3] b) or 1'b0); + | ^~ + ... For warning description see https://verilator.org/warn/COVERIGN?v=latest + ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:24:32: Ignoring unsupported: cover sequence with a sequence operand of 'or' + 24 | cover sequence ((a [* 1: 3]) or 1'b0); + | ^~ +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:27:21: Ignoring unsupported: cover sequence with this ranged cycle delay + 27 | cover sequence (a ##[1:2] (b ##1 c)); + | ^~ +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:30:21: Ignoring unsupported: cover sequence with this ranged cycle delay + 30 | cover sequence (a ##[1:300] b); + | ^~ +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:33:21: Ignoring unsupported: cover sequence with a ranged goto repetition + 33 | cover sequence (a [-> 2: 3]); + | ^~~ +%Error: Exiting due to diff --git a/test_regress/t/t_cover_sequence_unsup.py b/test_regress/t/t_cover_sequence_unsup.py new file mode 100755 index 000000000..56c514bc5 --- /dev/null +++ b/test_regress/t/t_cover_sequence_unsup.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(expect_filename=test.golden_filename, + verilator_flags2=['--assert --error-limit 1000'], + fails=True) + +test.passes() diff --git a/test_regress/t/t_cover_sequence_unsup.v b/test_regress/t/t_cover_sequence_unsup.v new file mode 100644 index 000000000..b2770a2fa --- /dev/null +++ b/test_regress/t/t_cover_sequence_unsup.v @@ -0,0 +1,35 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk +); + + logic a, b, c; + + default clocking cb @(posedge clk); + endclocking + + // cover sequence (IEEE 1800-2023 16.14.3) counts every end-of-match. The + // following forms put a sub-sequence where only its final end is forwarded, + // so they are ignored (COVERIGN) rather than under-counted. + + // Sequence operand of 'or' (ranged cycle delay). + cover sequence ((a ##[1:3] b) or 1'b0); + + // Sequence operand of 'or' (consecutive repetition). + cover sequence ((a [* 1: 3]) or 1'b0); + + // Ranged cycle delay before a multi-cycle sequence. + cover sequence (a ##[1:2] (b ##1 c)); + + // Ranged cycle delay wide enough to use the counter FSM. + cover sequence (a ##[1:300] b); + + // Ranged goto repetition (every M..N-th match is a separate end). + cover sequence (a [-> 2: 3]); + +endmodule diff --git a/test_regress/t/t_covergroup_array_bins.out b/test_regress/t/t_covergroup_array_bins.out index 4c0767563..ccbbbc297 100644 --- a/test_regress/t/t_covergroup_array_bins.out +++ b/test_regress/t/t_covergroup_array_bins.out @@ -1,4 +1,12 @@ cg.data.grouped: 2 -cg.data.values: 3 -cg2.cp.range_arr: 3 -cg3.cp.range_sized: 3 +cg.data.values[0]: 1 +cg.data.values[1]: 1 +cg.data.values[2]: 1 +cg2.cp.range_arr[0]: 1 +cg2.cp.range_arr[1]: 1 +cg2.cp.range_arr[2]: 1 +cg2.cp.range_arr[3]: 0 +cg3.cp.range_sized[0]: 1 +cg3.cp.range_sized[1]: 1 +cg3.cp.range_sized[2]: 1 +cg3.cp.range_sized[3]: 0 diff --git a/test_regress/t/t_covergroup_clocked_sample.py b/test_regress/t/t_covergroup_clocked_sample.py index 9f6b5465d..be9b453d3 100755 --- a/test_regress/t/t_covergroup_clocked_sample.py +++ b/test_regress/t/t_covergroup_clocked_sample.py @@ -10,6 +10,7 @@ import vltest_bootstrap import coverage_covergroup_common -test.scenarios('vlt_all') +# Issue #7779 unstable with --vltmt +test.scenarios('vlt') coverage_covergroup_common.run(test) diff --git a/test_regress/t/t_covergroup_cross.out b/test_regress/t/t_covergroup_cross.out index 84d9104d6..a7311da80 100644 --- a/test_regress/t/t_covergroup_cross.out +++ b/test_regress/t/t_covergroup_cross.out @@ -65,6 +65,15 @@ cg_at_least.cp_addr.addr0: 1 cg_at_least.cp_addr.addr1: 1 cg_at_least.cp_cmd.read: 1 cg_at_least.cp_cmd.write: 1 +cg_def_cross.axc.a0_x_read [cross]: 1 +cg_def_cross.axc.a0_x_write [cross]: 0 +cg_def_cross.axc.a1_x_read [cross]: 0 +cg_def_cross.axc.a1_x_write [cross]: 0 +cg_def_cross.cp_a.a0: 1 +cg_def_cross.cp_a.a1: 0 +cg_def_cross.cp_a.ad: 1 +cg_def_cross.cp_c.read: 1 +cg_def_cross.cp_c.write: 1 cg_goal.addr_cmd_goal.addr0_x_read [cross]: 1 cg_goal.addr_cmd_goal.addr0_x_write [cross]: 0 cg_goal.addr_cmd_goal.addr1_x_read [cross]: 0 @@ -82,6 +91,16 @@ cg_ignore.cross_ab.a0_x_read [cross]: 1 cg_ignore.cross_ab.a0_x_write [cross]: 1 cg_ignore.cross_ab.a1_x_read [cross]: 1 cg_ignore.cross_ab.a1_x_write [cross]: 1 +cg_mixed.ab.addr0_x_read [cross]: 1 +cg_mixed.ab.addr0_x_write [cross]: 1 +cg_mixed.ab.addr1_x_read [cross]: 1 +cg_mixed.ab.addr1_x_write [cross]: 1 +cg_mixed.cp_addr.addr0: 2 +cg_mixed.cp_addr.addr1: 2 +cg_mixed.cp_cmd.read: 2 +cg_mixed.cp_cmd.write: 2 +cg_mixed.cp_solo.debug: 2 +cg_mixed.cp_solo.normal: 2 cg_range.addr_cmd_range.hi_range_x_read [cross]: 1 cg_range.addr_cmd_range.hi_range_x_write [cross]: 1 cg_range.addr_cmd_range.lo_range_x_read [cross]: 1 diff --git a/test_regress/t/t_covergroup_cross.v b/test_regress/t/t_covergroup_cross.v index 6245baa35..e86f179a4 100644 --- a/test_regress/t/t_covergroup_cross.v +++ b/test_regress/t/t_covergroup_cross.v @@ -99,6 +99,23 @@ module t; cross cp_a, cp_c; // no label: reported under the default cross name endgroup + // Cross plus an un-crossed coverpoint: get_inst_coverage must combine the converted + // (VlCoverpoint) coverpoint cp_solo with the legacy cross/crossed-coverpoint bins. + covergroup cg_mixed; + cp_addr: coverpoint addr {bins addr0 = {0}; bins addr1 = {1};} + cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} + cp_solo: coverpoint mode {bins normal = {0}; bins debug = {1};} // not crossed + ab: cross cp_addr, cp_cmd; + endgroup + + // Crossed (hence non-convertible) coverpoint that also has a default bin: exercises the + // legacy default-bin codegen path that converted coverpoints bypass. + covergroup cg_def_cross; + cp_a: coverpoint addr iff (mode) {bins a0 = {0}; bins a1 = {1}; bins ad = default;} + cp_c: coverpoint cmd {bins read = {0}; bins write = {1};} + axc: cross cp_a, cp_c; + endgroup + cg2 cg2_inst = new; cg_ignore cg_ignore_inst = new; cg_range cg_range_inst = new; @@ -109,6 +126,8 @@ module t; cg_goal cg_goal_inst = new; cg_unsup_cross_opt cg_unsup_cross_opt_inst = new; cg_unnamed_cross cg_unnamed_cross_inst = new; + cg_mixed cg_mixed_inst = new; + cg_def_cross cg_def_cross_inst = new; initial begin // Sample 2-way: hit all 4 combinations @@ -275,6 +294,23 @@ module t; cg_unnamed_cross_inst.sample(); // a1 x write `checkr(cg_unnamed_cross_inst.get_inst_coverage(), 75.0); + // Sample cg_mixed: 10 bins total (cp_addr 2 + cp_cmd 2 + cp_solo 2 + cross ab 4) + addr = 0; cmd = 0; mode = 0; + cg_mixed_inst.sample(); // addr0, read, solo normal, ab(addr0_x_read) + `checkr(cg_mixed_inst.get_inst_coverage(), 40.0); // 4/10 + addr = 0; cmd = 1; mode = 1; + cg_mixed_inst.sample(); // addr0, write, solo debug, ab(addr0_x_write) + addr = 1; cmd = 0; mode = 0; + cg_mixed_inst.sample(); // addr1, read, ab(addr1_x_read) + addr = 1; cmd = 1; mode = 1; + cg_mixed_inst.sample(); // addr1, write, ab(addr1_x_write) + `checkr(cg_mixed_inst.get_inst_coverage(), 100.0); // 10/10 + + // Sample cg_def_cross (default bin in a crossed coverpoint, gated by iff) + mode = 1; + addr = 0; cmd = 0; cg_def_cross_inst.sample(); // a0, read + addr = 2; cmd = 1; cg_def_cross_inst.sample(); // ad (default), write + $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_covergroup_default_bins.out b/test_regress/t/t_covergroup_default_bins.out index 9e4e7a1c6..e034545b8 100644 --- a/test_regress/t/t_covergroup_default_bins.out +++ b/test_regress/t/t_covergroup_default_bins.out @@ -1,11 +1,11 @@ cg.data.high: 1 cg.data.low: 1 -cg.data.other: 2 -cg2.cp_only_default.all: 4 +cg.data.other [default]: 2 +cg2.cp_only_default.all [default]: 4 cg3.data.bad [ignore]: 1 cg3.data.err [illegal]: 0 cg3.data.normal: 2 -cg3.data.other: 2 +cg3.data.other [default]: 2 cg4.cp_idx.auto_0: 1 cg4.cp_idx.auto_1: 1 cg4.cp_idx.auto_2: 1 diff --git a/test_regress/t/t_covergroup_default_bins.v b/test_regress/t/t_covergroup_default_bins.v index f4ba5148c..c97cf8f66 100644 --- a/test_regress/t/t_covergroup_default_bins.v +++ b/test_regress/t/t_covergroup_default_bins.v @@ -157,17 +157,17 @@ module t; // Sample cg3: verify ignore/illegal bins do not contribute to coverage data = 2; cg3_inst.sample(); // hits normal bin - `checkr(cg3_inst.get_inst_coverage(), 50.0); // 1/2 bins hit (normal) + `checkr(cg3_inst.get_inst_coverage(), 100.0); // 1/1: only 'normal' counts (default/ignore/illegal excluded, LRM 19.5) data = 7; cg3_inst.sample(); // hits normal bin again - `checkr(cg3_inst.get_inst_coverage(), 50.0); // no new bins + `checkr(cg3_inst.get_inst_coverage(), 100.0); // no new normal bins data = 255; - cg3_inst.sample(); // ignore_bins value; Verilator counts it toward default bin - `checkr(cg3_inst.get_inst_coverage(), 100.0); // 2/2: Verilator hits 'other' (default) even for ignore_bins + cg3_inst.sample(); // ignore_bins value; recorded but excluded from coverage + `checkr(cg3_inst.get_inst_coverage(), 100.0); // note: do not sample 254 (illegal_bins would cause runtime assertion) data = 100; - cg3_inst.sample(); // hits default (other) bin - `checkr(cg3_inst.get_inst_coverage(), 100.0); // 2/2 bins hit + cg3_inst.sample(); // hits default (other) bin; recorded but excluded from coverage + `checkr(cg3_inst.get_inst_coverage(), 100.0); // Sample cg4: auto-bins with one excluded value // idx=2 is in ignore_bins, so auto-bins cover 0, 1, 3 only (3 bins total) diff --git a/test_regress/t/t_covergroup_iff.out b/test_regress/t/t_covergroup_iff.out index 0e28a68cf..69a7711dd 100644 --- a/test_regress/t/t_covergroup_iff.out +++ b/test_regress/t/t_covergroup_iff.out @@ -2,9 +2,11 @@ cg_and.cp_concat.b00: 0 cg_and.cp_concat.b01: 1 cg_and.cp_concat.b10: 0 cg_and.cp_concat.b11: 0 -cg_array_iff.cp.arr: 1 +cg_array_iff.cp.arr[0]: 1 +cg_array_iff.cp.arr[1]: 0 +cg_array_iff.cp.arr[2]: 0 cg_bitw.cp.seven: 1 -cg_default_iff.cp.def: 1 +cg_default_iff.cp.def [default]: 1 cg_default_iff.cp.known: 1 cg_iff.cp_value.disabled_hi: 0 cg_iff.cp_value.disabled_lo: 0 diff --git a/test_regress/t/t_covergroup_iff.v b/test_regress/t/t_covergroup_iff.v index 1816bd327..8bf6e0016 100644 --- a/test_regress/t/t_covergroup_iff.v +++ b/test_regress/t/t_covergroup_iff.v @@ -129,7 +129,7 @@ module t; enable = 1; value = 10; cg2.sample(); // hits 'known' - `checkr(cg2.get_inst_coverage(), 50.0); + `checkr(cg2.get_inst_coverage(), 100.0); // 1/1: only 'known' counts (default excluded, LRM 19.5) value = 99; cg2.sample(); // hits 'def' (default) `checkr(cg2.get_inst_coverage(), 100.0); diff --git a/test_regress/t/t_covergroup_ignore_bins.out b/test_regress/t/t_covergroup_ignore_bins.out index bcd0b72e0..6a1cba188 100644 --- a/test_regress/t/t_covergroup_ignore_bins.out +++ b/test_regress/t/t_covergroup_ignore_bins.out @@ -1,5 +1,7 @@ -cg.data.arr [ignore]: 0 -cg.data.bad [illegal]: 0 +cg.data.arr[0] [ignore]: 0 +cg.data.arr[1] [ignore]: 0 +cg.data.bad[0] [illegal]: 0 +cg.data.bad[1] [illegal]: 0 cg.data.catch_all [ignore]: 0 cg.data.high: 1 cg.data.low: 1 diff --git a/test_regress/t/t_covergroup_illegal_bins.out b/test_regress/t/t_covergroup_illegal_bins.out index d2de75a65..798247e10 100644 --- a/test_regress/t/t_covergroup_illegal_bins.out +++ b/test_regress/t/t_covergroup_illegal_bins.out @@ -2,7 +2,9 @@ cg.data.forbidden [illegal]: 0 cg.data.high: 1 cg.data.low: 1 cg.data.mid: 1 -cg2.cp_arr.bad_arr [illegal]: 0 +cg2.cp_arr.bad_arr[0] [illegal]: 0 +cg2.cp_arr.bad_arr[1] [illegal]: 0 +cg2.cp_arr.bad_arr[2] [illegal]: 0 cg2.cp_arr.ok: 1 cg2.cp_arr.wlib [illegal]: 0 cg2.cp_trans.bad_2step [illegal]: 0 diff --git a/test_regress/t/t_covergroup_param_bins.v b/test_regress/t/t_covergroup_param_bins.v index ab57a4a62..ece6521cb 100644 --- a/test_regress/t/t_covergroup_param_bins.v +++ b/test_regress/t/t_covergroup_param_bins.v @@ -26,10 +26,10 @@ module t #( covergroup cg; cp: coverpoint value { bins negative = {[PMIN : -1]}; // parameter as range lower bound - bins zero = {0}; + bins zero = {0}; bins positive = {[1 : LMAX]}; // localparam as range upper bound - bins maxv = {LMAX}; // localparam as single value - bins minv = {PMIN}; // parameter as single value + bins maxv = {LMAX}; // localparam as single value + bins minv = {PMIN}; // parameter as single value } endgroup diff --git a/test_regress/t/t_covergroup_trans.out b/test_regress/t/t_covergroup_trans.out index f7621e74a..17e059ede 100644 --- a/test_regress/t/t_covergroup_trans.out +++ b/test_regress/t/t_covergroup_trans.out @@ -7,3 +7,5 @@ cg.cp_trans2.trans2: 1 cg.cp_trans2.trans3: 1 cg.cp_trans3.seq_a: 1 cg.cp_trans3.seq_b: 1 +cg_legacy.cp_mix.tr: 1 +cg_legacy.cp_mix.vals: 1 diff --git a/test_regress/t/t_covergroup_trans.v b/test_regress/t/t_covergroup_trans.v index dea93e6c5..f95561ed2 100644 --- a/test_regress/t/t_covergroup_trans.v +++ b/test_regress/t/t_covergroup_trans.v @@ -39,7 +39,17 @@ module t; } endgroup + // Non-convertible coverpoint (it has a transition bin) that also carries a value-array bin, + // so the legacy array codegen path (which converted coverpoints bypass) is still exercised. + covergroup cg_legacy; + cp_mix: coverpoint state { + bins tr = (0 => 1); + bins vals[] = {2, [4:5]}; // discrete + range elements (both legacy array paths) + } + endgroup + cg cg_inst = new; + cg_legacy cg_legacy_inst = new; initial begin // Drive sequence 0->1->2->3->4 which hits all bins @@ -56,6 +66,12 @@ module t; cg_inst.sample(); // 3=>4: seq_b done `checkr(cg_inst.get_inst_coverage(), 100.0); + // cg_legacy: exercise legacy array codegen (non-convertible coverpoint) + state = 0; cg_legacy_inst.sample(); + state = 1; cg_legacy_inst.sample(); // 0=>1: tr + state = 2; cg_legacy_inst.sample(); // vals + state = 3; cg_legacy_inst.sample(); // vals + $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_debug_emitv.out b/test_regress/t/t_debug_emitv.out index b65ab991e..53f8ac53b 100644 --- a/test_regress/t/t_debug_emitv.out +++ b/test_regress/t/t_debug_emitv.out @@ -651,6 +651,11 @@ module Vt_debug_emitv_t; $display("pass"); end end + begin : cover_sequence_concurrent + cover property (@(posedge clk) in##1 in + ) begin + end + end begin : assert_prop_always assert property (@(posedge clk) always ['sh0: 'sh3] in diff --git a/test_regress/t/t_debug_emitv.v b/test_regress/t/t_debug_emitv.v index edc1ec87b..7d3805052 100644 --- a/test_regress/t/t_debug_emitv.v +++ b/test_regress/t/t_debug_emitv.v @@ -343,6 +343,8 @@ module t (/*AUTOARG*/ cover_concurrent: cover property(prop); cover_concurrent_stmt: cover property(prop) $display("pass"); + cover_sequence_concurrent: cover sequence (@(posedge clk) in ##1 in); + assert_prop_always: assert property (@(posedge clk) always [0:3] in); assert_prop_s_always: assert property (@(posedge clk) s_always [1:2] in); assert_prop_overlap_impl: assert property (@(posedge clk) in |-> in); diff --git a/test_regress/t/t_default_disable_iff_gen_multi_bad.out b/test_regress/t/t_default_disable_iff_gen_multi_bad.out new file mode 100644 index 000000000..ed0df293e --- /dev/null +++ b/test_regress/t/t_default_disable_iff_gen_multi_bad.out @@ -0,0 +1,6 @@ +%Error: t/t_default_disable_iff_gen_multi_bad.v:15:7: Only one 'default disable iff' allowed per generate block (IEEE 1800-2023 16.15) + : ... note: In instance 't' + 15 | default disable iff (cyc < 7); + | ^~~~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: Exiting due to diff --git a/test_regress/t/t_default_disable_iff_gen_multi_bad.py b/test_regress/t/t_default_disable_iff_gen_multi_bad.py new file mode 100755 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_default_disable_iff_gen_multi_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('linter') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_default_disable_iff_gen_multi_bad.v b/test_regress/t/t_default_disable_iff_gen_multi_bad.v new file mode 100644 index 000000000..61e69830c --- /dev/null +++ b/test_regress/t/t_default_disable_iff_gen_multi_bad.v @@ -0,0 +1,20 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module t(input clk); + int cyc; + + default disable iff (cyc < 3); + + generate + begin : g + default disable iff (cyc < 5); + default disable iff (cyc < 7); + + assert property (@(posedge clk) 0); + end + endgenerate +endmodule diff --git a/test_regress/t/t_default_disable_iff_scope.py b/test_regress/t/t_default_disable_iff_scope.py new file mode 100755 index 000000000..35e44000c --- /dev/null +++ b/test_regress/t/t_default_disable_iff_scope.py @@ -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(timing_loop=True, verilator_flags2=['--assert', '--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_default_disable_iff_scope.v b/test_regress/t/t_default_disable_iff_scope.v new file mode 100644 index 000000000..6cdabba82 --- /dev/null +++ b/test_regress/t/t_default_disable_iff_scope.v @@ -0,0 +1,230 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + bit clk; + int cyc; + + always #1 clk = !clk; + + bit rst_mod; + int before_mod_false; + int before_mod_gen_false; + int before_gen_default_false; + int before_gen_child_default_false; + int prog_false_count; + + always_comb rst_mod = (cyc < 3); + + // The default declaration appears later in the module, but still applies here. + assert property (@(posedge clk) 0) + else before_mod_false++; + + generate + begin : g_before_mod_default + // Inherits the module default declaration that appears later in this module. + assert property (@(posedge clk) 0) + else before_mod_gen_false++; + end + + begin : g_before_local_default + // The generate-local default declaration appears later in the block. + assert property (@(posedge clk) 0) + else before_gen_default_false++; + + begin : g_child_inherit + // Inherits the generate-local default declaration that appears later. + assert property (@(posedge clk) 0) + else before_gen_child_default_false++; + end + default disable iff (cyc < 7); + end + endgenerate + + default disable iff (rst_mod); + + generate + begin : g_override + bit rst_gen; + int override_false; + + always_comb rst_gen = (cyc < 5); + + default disable iff (rst_gen); + + assert property (@(posedge clk) 0) + else override_false++; + end + + begin : g_inherit + bit rst_mod; + int inherit_false; + + always_comb rst_mod = (cyc < 8); + + // Inherits the module default, whose rst_mod was resolved in module scope. + assert property (@(posedge clk) 0) + else inherit_false++; + end + endgenerate + + if_scope u_if (.clk(clk), .cyc(cyc)); + p_scope u_prog (.clk(clk), .cyc(cyc), .false_count(prog_false_count)); + examples_with_default_count u_with (.clk(clk), .cyc(cyc)); + examples_without_default_count u_without (.clk(clk), .cyc(cyc)); + examples_with_default u_ieee_with (.a(1'b0), .b(1'b0), .clk(clk), .rst(1'b0), .rst1(1'b0)); + examples_without_default u_ieee_without (.a(1'b0), .b(1'b0), .clk(clk), .rst(1'b0)); + m_override u_m_override (.clk(clk), .cyc(cyc)); + + // The disable iff expression is unsampled, so same-edge updates race in MT simulation. + // Change clk on negedge while the properties are sampled on posedge to avoid races. + always @(negedge clk) begin + cyc++; + if (cyc == 12) begin + `checkd(before_mod_false, 9); + `checkd(before_mod_gen_false, 9); + `checkd(before_gen_default_false, 5); + `checkd(before_gen_child_default_false, 5); + `checkd(g_inherit.inherit_false, 9); + `checkd(g_override.override_false, 7); + `checkd(u_if.false_count, 6); + `checkd(u_if.g_inherit.false_count, 6); + `checkd(prog_false_count, 4); + `checkd(u_with.explicit_assert_false, 5); + `checkd(u_with.explicit_property_false, 5); + `checkd(u_with.inferred_default_false, 9); + `checkd(u_without.explicit_assert_false, 9); + `checkd(u_without.explicit_property_false, 9); + `checkd(u_m_override.false_count, 7); + `checkd(u_m_override.g_inherit_from_module.false_count, 7); + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule + +module examples_with_default (input logic a, b, clk, rst, rst1); +default disable iff rst; +property p1; +disable iff (rst1) a |=> b; +endproperty +// Disable condition is rst1 - explicitly specified within a1 +a1 : assert property (@(posedge clk) disable iff (rst1) a |=> b); +// Disable condition is rst1 - explicitly specified within p1 +a2 : assert property (@(posedge clk) p1); +// Disable condition is rst - no explicit specification, inferred from +// default disable iff declaration +a3 : assert property (@(posedge clk) a |=> b); +// Disable condition is 1'b0. This is the only way to +// cancel the effect of default disable. +a4 : assert property (@(posedge clk) disable iff (1'b0) a |=> b); +endmodule + +module examples_without_default (input logic a, b, clk, rst); +property p2; +disable iff (rst) a |=> b; +endproperty +// Disable condition is rst - explicitly specified within a5 +a5 : assert property (@(posedge clk) disable iff (rst) a |=> b); +// Disable condition is rst - explicitly specified within p2 +a6 : assert property (@ (posedge clk) p2); +// No disable condition +a7 : assert property (@ (posedge clk) a |=> b); +endmodule + +module examples_with_default_count(input bit clk, input int cyc); + int explicit_assert_false; + int explicit_property_false; + int inferred_default_false; + + default disable iff (cyc < 3); + + property p1; + disable iff (cyc < 7) 0; + endproperty + + // Disable condition is explicit in the assertion. + assert property (@(posedge clk) disable iff (cyc < 7) 0) + else explicit_assert_false++; + + // Disable condition is explicit in the property. + assert property (@(posedge clk) p1) + else explicit_property_false++; + + // Disable condition is inferred from the default. + assert property (@(posedge clk) 0) + else inferred_default_false++; + +endmodule + +module examples_without_default_count(input bit clk, input int cyc); + int explicit_assert_false; + int explicit_property_false; + + property p2; + disable iff (cyc < 3) 0; + endproperty + + // Disable condition is explicit in the assertion. + assert property (@(posedge clk) disable iff (cyc < 3) 0) + else explicit_assert_false++; + + // Disable condition is explicit in the property. + assert property (@(posedge clk) p2) + else explicit_property_false++; + +endmodule + +module m_override(input bit clk, input int cyc); + bit rst2; + int false_count; + + always_comb rst2 = (cyc < 5); + default disable iff (rst2); + + assert property (@(posedge clk) 0) + else false_count++; + + generate + begin : g_inherit_from_module + int false_count; + assert property (@(posedge clk) 0) + else false_count++; + end + endgenerate +endmodule + +interface if_scope(input bit clk, input int cyc); + bit rst_if; + int false_count; + + always_comb rst_if = (cyc < 6); + default disable iff (rst_if); + + assert property (@(posedge clk) 0) + else false_count++; + + generate + begin : g_inherit + int false_count; + assert property (@(posedge clk) 0) + else false_count++; + end + endgenerate +endinterface + +program p_scope(input bit clk, input int cyc, + output int false_count); + default disable iff (cyc < 8); + + assert property (@(posedge clk) 0) + else false_count++; +endprogram diff --git a/test_regress/t/t_dfg_break_cycles.py b/test_regress/t/t_dfg_break_cycles.py index 2c35f08f5..ec6c6b339 100755 --- a/test_regress/t/t_dfg_break_cycles.py +++ b/test_regress/t/t_dfg_break_cycles.py @@ -17,6 +17,8 @@ test.sim_time = 2000000 if not os.path.exists(test.root + "/.git"): test.skip("Not in a git repository") +test.top_filename = "t/t_dfg_break_cycles.v" + # Read expected source lines hit expectedLines = set() @@ -65,7 +67,8 @@ with open(rdFile, 'r', encoding="utf8") as rdFh, \ test.compile(verilator_flags2=[ "--stats", "--build", - "-fno-dfg-break-cycles", + "-fno-dfg" if test.name == "t_dfg_break_cycles" else "-fno-dfg-break-cycles", + "-fno-gate", "+incdir+" + test.obj_dir, "-Mdir", test.obj_dir + "/obj_ref", "--prefix", "Vref", @@ -73,8 +76,9 @@ test.compile(verilator_flags2=[ ]) # yapf:disable # Check we got the expected number of circular logic warnings -test.file_grep(test.obj_dir + "/obj_ref/Vref__stats.txt", - r'Warnings, Suppressed UNOPTFLAT\s+(\d+)', nExpectedCycles) +if test.name == "t_dfg_break_cycles": + test.file_grep(test.obj_dir + "/obj_ref/Vref__stats.txt", + r'Warnings, Suppressed UNOPTFLAT\s+(\d+)', nExpectedCycles) # Compile optimized - also builds executable test.compile(verilator_flags2=[ @@ -82,6 +86,7 @@ test.compile(verilator_flags2=[ "--build", "--exe", "-fno-const-before-dfg", + "-fno-gate", "+incdir+" + test.obj_dir, "-Mdir", test.obj_dir + "/obj_opt", "--prefix", "Vopt", @@ -90,7 +95,7 @@ test.compile(verilator_flags2=[ "--debug", "--debugi", "0", "--dumpi-tree", "0", "-CFLAGS \"-I .. -I ../obj_ref\"", "../obj_ref/Vref__ALL.a", - "../../t/" + test.name + ".cpp" + "../../t/t_dfg_break_cycles.cpp" ]) # yapf:disable # Execute test to check equivalence diff --git a/test_regress/t/t_dfg_break_cycles.v b/test_regress/t/t_dfg_break_cycles.v index 5de4c628b..80c5aad04 100644 --- a/test_regress/t/t_dfg_break_cycles.v +++ b/test_regress/t/t_dfg_break_cycles.v @@ -219,14 +219,14 @@ module t ( `signal(ARRAY_1, 3); // UNOPTFLAT assign ARRAY_1 = array_1[0]; - wire [2:0] array_2a [2]; - wire [2:0] array_2b [2]; + wire [2:0] array_2a [2]; // UNOPTFLAT + wire [2:0] array_2b [2]; // UNOPTFLAT assign array_2a[0][0] = rand_a[0]; assign array_2a[0][1] = array_2b[1][0]; assign array_2a[0][2] = array_2b[1][1]; assign array_2a[1] = array_2a[0]; assign array_2b = array_2a; - `signal(ARRAY_2, 3); // UNOPTFLAT + `signal(ARRAY_2, 3); assign ARRAY_2 = array_2a[0]; wire [2:0] array_3 [2]; diff --git a/test_regress/t/t_dfg_break_cycles_off.py b/test_regress/t/t_dfg_break_cycles_off.py new file mode 100755 index 000000000..bf6af753c --- /dev/null +++ b/test_regress/t/t_dfg_break_cycles_off.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +import runpy + +test.scenarios('vlt_all') + +runpy.run_path("t/t_dfg_break_cycles.py", globals()) diff --git a/test_regress/t/t_dfg_peephole.v b/test_regress/t/t_dfg_peephole.v index 3c9ce7ae8..98d9146be 100644 --- a/test_regress/t/t_dfg_peephole.v +++ b/test_regress/t/t_dfg_peephole.v @@ -50,6 +50,9 @@ module t ( assign unitArrayParts[0][1] = rand_a[1]; assign unitArrayParts[0][9] = rand_a[9]; + // Complicated way to write constant 0 that only Dfg can decipher + wire [63:0] convoluted_zero = (({64{rand_a[0]}} & ~{64{rand_a[0]}})); + `signal(FOLD_UNARY_LogNot, !const_a[0]); `signal(FOLD_UNARY_Negate, -const_a); `signal(FOLD_UNARY_Not, ~const_a); @@ -133,6 +136,13 @@ module t ( `signal(FOLD_SEL, const_a[3:1]); + int fold_arraysel_table_one; + ffs ffs_a(convoluted_zero[0] ? 8'hff: 8'd2, fold_arraysel_table_one); + int fold_arraysel_table_two; + ffs ffs_b(convoluted_zero[1] ? 8'hff: 8'd7, fold_arraysel_table_two); + `signal(FOLD_ARRAYSEL_TABLE_ONE, fold_arraysel_table_one); + `signal(FOLD_ARRAYSEL_TABLE_TWO, fold_arraysel_table_two); + `signal(SWAP_CONST_IN_COMMUTATIVE_BINARY, rand_a + const_a); `signal(SWAP_NOT_IN_COMMUTATIVE_BINARY, rand_a + ~rand_a); `signal(SWAP_VAR_IN_COMMUTATIVE_BINARY, rand_b + rand_a); @@ -283,10 +293,20 @@ module t ( `signal(REPLACE_COND_CONST_ZERO_ONAE, rand_a[0] ? 80'b0 : -80'b1); `signal(REPLACE_COND_CAT_LHS_CONST_ONE_ZERO, rand_a[0] ? {8'b1, rand_b[0]} : {8'b0, rand_b[1]}); `signal(REPLACE_COND_CAT_LHS_CONST_ZERO_ONE, rand_a[0] ? {8'b0, rand_b[0]} : {8'b1, rand_b[1]}); - `signal(REPLACE_COND_SAME_CAT_LHS, rand_a[0] ? {8'd0, rand_b[0]} : {8'd0, rand_b[1]}); - `signal(REPLACE_COND_SAME_CAT_RHS, rand_a[0] ? {rand_b[0], 8'd0} : {rand_b[1], 8'd0}); `signal(REPLACE_COND_SAM_COND_THEN, rand_a[0] ? (rand_a[0] ? rand_b[1:0] : rand_b[3:2]) : rand_b[5:4]); `signal(REPLACE_COND_SAM_COND_ELSE, rand_a[0] ? rand_b[1:0] : (rand_a[0] ? rand_b[3:2] : rand_b[5:4])); + + `signal(REPLACE_COND_COMMON_MSBS_A, rand_a[0] ? {8'd0, rand_b[0]} : {8'd0, rand_b[1]}); + `signal(REPLACE_COND_COMMON_MSBS_B, rand_a[0] ? {8'hf0, rand_b[1:0]} : {9'h1e2, rand_b[1]}); + `signal(REPLACE_COND_COMMON_MSBS_C, rand_a[0] ? {rand_a[63 -: 3] , rand_b[0]} : {rand_a[63 -: 2], rand_b[2:1]}); + `signal(REPLACE_COND_COMMON_LSBS_A, rand_a[0] ? {rand_b[0], 8'd0} : {rand_b[1], 8'd0}); + `signal(REPLACE_COND_COMMON_LSBS_B, rand_a[0] ? {rand_b[2:1], 8'h0f} : {rand_b[1], 9'h08f}); + `signal(REPLACE_COND_COMMON_LSBS_C, rand_a[0] ? {rand_b[0], rand_a[3:0]} : {rand_b[1:0], rand_a[2:0]}); + wire [5:0] tmp_REPLACE_COND_COMMON_LSBS_D = rand_b[5:0]; + wire [5:0] tmp_REPLACE_COND_COMMON_MSBS_D = rand_b[63:58]; + `signal(REPLACE_COND_COMMON_LSBS_D, rand_a[0] ? rand_b[4:0] : tmp_REPLACE_COND_COMMON_LSBS_D[4:0]); + `signal(REPLACE_COND_COMMON_MSBS_D, rand_a[0] ? rand_b[63:59] : tmp_REPLACE_COND_COMMON_MSBS_D[5:1]); + `signal(REMOVE_SHIFTL_ZERO, rand_a << 0); `signal(REPLACE_SHIFTL_OVER, rand_a << 64); `signal(REPLACE_SHIFTL_SEL, rand_a[27:0] << 4); @@ -352,7 +372,6 @@ module t ( `signal(REMOVE_EQ_BIT_1, 1'b1 == rand_a[0]); `signal(REMOVE_NEQ_BIT_0, 1'b0 != rand_a[0]); `signal(REPLACE_NEQ_BIT_1, 1'b1 != rand_a[0]); - `signal(REPLACE_COND_INSERT, rand_a[0] ? {rand_b[63:40], {1'd0, rand_b[38:0]}} : rand_b); `signal(REPLACE_REP_REP, {2{({3{rand_a[0]}})}}); // Operators that should work wiht mismatched widths @@ -418,3 +437,25 @@ module t ( assign zero = '0; assign ones = '1; endmodule + +module ffs( + input logic [7:0] i, + output int o +); + // V3Table will convert this + always_comb begin + // verilator lint_off CASEOVERLAP + casez (i) + 8'b1???????: o = 7; + 8'b?1??????: o = 6; + 8'b??1?????: o = 5; + 8'b???1????: o = 4; + 8'b????1???: o = 3; + 8'b?????1??: o = 2; + 8'b??????1?: o = 1; + 8'b???????1: o = 0; + 8'b00000000: o = -1; + endcase + // verilator lint_on CASEOVERLAP + end +endmodule diff --git a/test_regress/t/t_dfg_synthesis.v b/test_regress/t/t_dfg_synthesis.v index 9076b4101..c13d4f47c 100644 --- a/test_regress/t/t_dfg_synthesis.v +++ b/test_regress/t/t_dfg_synthesis.v @@ -577,4 +577,13 @@ module t ( end `signal(VIA_CRESET, via_creset); + logic [1:0] LUT [2] = '{0: 2'b11, 1: 2'b10}; + logic [1:0] array_read; + always_comb begin + array_read = 2'd0; + array_read += LUT[rand_a[0]]; + array_read += LUT[rand_a[1]]; + end + `signal(ARRAY_READ, array_read); + endmodule diff --git a/test_regress/t/t_disable_fork_nested.out b/test_regress/t/t_disable_fork_nested.out new file mode 100644 index 000000000..dce504813 --- /dev/null +++ b/test_regress/t/t_disable_fork_nested.out @@ -0,0 +1,3 @@ +Fast clock (200ns half-period): o_counter=7 +Slow clock (5400ns half-period): o_counter=3 +*-* All Finished *-* diff --git a/test_regress/t/t_disable_fork_nested.py b/test_regress/t/t_disable_fork_nested.py new file mode 100755 index 000000000..b716266af --- /dev/null +++ b/test_regress/t/t_disable_fork_nested.py @@ -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", "-Wno-ZERODLY"]) + +test.execute(expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_disable_fork_nested.v b/test_regress/t/t_disable_fork_nested.v new file mode 100644 index 000000000..de1b2526a --- /dev/null +++ b/test_regress/t/t_disable_fork_nested.v @@ -0,0 +1,89 @@ +// 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 + +// Clock-period detector exercising a named-block `disable` of a block whose +// body contains a nested fork..join. Disabling such a block from a sibling +// process must release the enclosing fork..join so the surrounding `always` +// block keeps iterating. + +module disable_fork ( + input logic i_clk, + output logic [2:0] o_counter +); + time delay1 = 500ns; // min period + time delay2 = 3333ns; // max period + + logic clk_re = 1'b0; // rising edge of the clock + logic [2:0] counter = 3'b000; + + always begin + fork + begin : check1 + #delay1; + #1 disable check2; + fork + begin : check3 + #(delay2 - delay1); + clk_re <= 1'b0; + #1 disable check4; + if (counter < 3'b111) counter <= counter + 3'b001; + end + begin : check4 + @(posedge i_clk); + clk_re <= 1'b1; + counter <= 3'b000; + #1 disable check3; + end + join + end + begin : check2 + @(posedge i_clk); + clk_re <= 1'b0; + #1 disable check1; + if (counter < 3'b111) counter <= counter + 3'b001; + end + join + end + + assign o_counter = counter; +endmodule + +// 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 t; + logic clk; + logic [2:0] counter; + + task clk_cycle(input time half_period); + clk = 1'b1; + #half_period; + clk = 1'b0; + #half_period; + endtask : clk_cycle + + initial begin + // Fast clock (period below delay1): every edge arrives before the + // min-period timeout, so the counter saturates at its max. + repeat (100) clk_cycle(200ns); + $display("Fast clock (200ns half-period): o_counter=%0d", counter); + `checkh(counter, 3'h7); + // Slow clock (period above delay2): the nested fork path runs, which + // only works if disabling check1 releases the inner fork..join. + repeat (100) clk_cycle(5400ns); + $display("Slow clock (5400ns half-period): o_counter=%0d", counter); + `checkh(counter, 3'h3); + $write("*-* All Finished *-*\n"); + $finish; + end + + disable_fork a_inst ( + .i_clk(clk), + .o_counter(counter) + ); +endmodule diff --git a/test_regress/t/t_gate_inline_wide_noexclude_small_wide.py b/test_regress/t/t_dpi_export_unpack.py similarity index 71% rename from test_regress/t/t_gate_inline_wide_noexclude_small_wide.py rename to test_regress/t/t_dpi_export_unpack.py index c20d324db..b365ee618 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_small_wide.py +++ b/test_regress/t/t_dpi_export_unpack.py @@ -7,12 +7,14 @@ # SPDX-FileCopyrightText: 2024 Wilson Snyder # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +# 9-Jun-2026: Modifications for this test contributed by Yilin Li. + import vltest_bootstrap -test.scenarios('vlt') +test.scenarios('simulator') -test.lint(verilator_flags2=['--stats', '--expand-limit 5']) +test.compile(verilator_flags2=["--binary"]) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) +test.execute() test.passes() diff --git a/test_regress/t/t_dpi_export_unpack.v b/test_regress/t/t_dpi_export_unpack.v new file mode 100644 index 000000000..91cc32577 --- /dev/null +++ b/test_regress/t/t_dpi_export_unpack.v @@ -0,0 +1,33 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// 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: 2020 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +// 9-Jun-2026: Modifications for this test contributed by Yilin Li. + +export "DPI-C" task readHEX; +export "DPI-C" task loadHEX; + +task readHEX; + input string file; + output logic [7:0] stimuli[32'h00010000]; + $readmemh(file, stimuli); +endtask + +task loadHEX; + input string file; + logic [7:0] stimuli[32'h00010000]; + readHEX(file, stimuli); +endtask + +module tb (); + + logic [7:0] result[32'h00010000]; + initial begin + loadHEX("dummy"); + end + +endmodule diff --git a/test_regress/t/t_flag_expand_limit.py b/test_regress/t/t_flag_expand_limit.py index ec0cc54a6..d9b19b528 100755 --- a/test_regress/t/t_flag_expand_limit.py +++ b/test_regress/t/t_flag_expand_limit.py @@ -13,6 +13,6 @@ test.scenarios('vlt') test.compile(verilator_flags2=['--expand-limit 1 --stats -fno-dfg']) -test.file_grep(test.stats, r'Optimizations, expand limited\s+(\d+)', 7) +test.file_grep(test.stats, r'Optimizations, expand limited\s+(\d+)', 29) test.passes() diff --git a/test_regress/t/t_fsm_duplicate.py b/test_regress/t/t_fsm_duplicate.py new file mode 100755 index 000000000..7f4205522 --- /dev/null +++ b/test_regress/t/t_fsm_duplicate.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM no duplicate variables test +# +# 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=["--coverage -Wno-PINMISSING"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_fsm_duplicate.v b/test_regress/t/t_fsm_duplicate.v new file mode 100644 index 000000000..c0edc5755 --- /dev/null +++ b/test_regress/t/t_fsm_duplicate.v @@ -0,0 +1,157 @@ +// 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 rr #( +) ( + input logic clk, + input logic rst, + input logic [7:0] data, + input logic data_q +); + logic a; + logic [15:0] dcnt; + typedef enum logic [7:0] { + S0, + S1, + S2, + S3 + } state_t; + state_t state_d, state_q; + always_ff @(posedge clk or negedge rst) if (!rst) state_q <= S0; + always_ff @(posedge clk) + unique case (state_q) + S1: if (a) dcnt[7:0] <= data; + S2: if (a) dcnt[15:8] <= data; + S3: if (data_q) dcnt <= dcnt - 1; + default: dcnt <= dcnt; + endcase +endmodule +module re #( +) ( + input logic clk, + input logic rst, + output logic o, + input unused0, /* block optimizations */ + input unused1, + input unused2, + input unused3, + input unused4, + input unused5, + input unused6, + input unused7, + input unused8, + input unused9, + input unused10, + input unused11, + input unused12, + input unused13, + input unused14, + input unused15, + input unused16, + input unused17, + input unused18, + input unused19, + input unused20, + input unused21, + input unused22, + input unused23, + input unused24, + input unused25, + input unused26, + input unused27, + input unused28, + input unused29, + input unused30, + input unused31, + input unused32, + input unused33, + input unused34, + input unused35, + input unused36, + input unused37, + input unused38, + input unused39, + input unused40 +); + logic [15:0] dcnt; + typedef enum logic [7:0] { + S0, + S1 + } state_t; + state_t state_d, state_q; + always_ff @(posedge clk or negedge rst) if (!rst) state_q <= S0; + always_ff @(posedge clk) + unique case (state_q) + S1: o <= dcnt[0]; + default: o <= '0; + endcase + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule +module rh #( +) ( + input logic clk +); + logic [7:0] a; + logic b; + logic c; + logic d; + logic rst; + rr xrr ( + .clk, + .rst(rst), + .data(a), + .data_q(b & c) + ); + re xre ( + .clk, + .rst(rst), + .o(d) + ); +endmodule +module U #( +) ( + input clk, + input rst +); + rh xrh (.clk(clk)); +endmodule +module C #( +) ( + input clk, + input rst +); + U U ( + .clk, + .rst + ); +endmodule +module A #() (); + logic clk; + logic rst; + C c0 ( + .clk, + .rst + ); + C c1 ( + .clk, + .rst + ); +endmodule +module B #() (); + logic clk; + logic rst; + C xC ( + .clk, + .rst + ); +endmodule +module t #() (); + B b (); + A a (); +endmodule diff --git a/test_regress/t/t_gate_chained.py b/test_regress/t/t_gate_chained.py index 7fa18e3a3..ddf8bef62 100755 --- a/test_regress/t/t_gate_chained.py +++ b/test_regress/t/t_gate_chained.py @@ -48,6 +48,6 @@ test.compile( test.execute() # Must be <<9000 above to prove this worked -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 8550) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 8550) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_exclude_multiple.py b/test_regress/t/t_gate_inline_wide_exclude_multiple.py index 76a8991cf..2bf5b1241 100755 --- a/test_regress/t/t_gate_inline_wide_exclude_multiple.py +++ b/test_regress/t/t_gate_inline_wide_exclude_multiple.py @@ -13,7 +13,7 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 2) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 4) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 0) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py b/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py index ccbd8c7e7..15370ae7c 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py +++ b/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py @@ -11,9 +11,9 @@ import vltest_bootstrap test.scenarios('vlt') -test.lint(verilator_flags2=['--stats', '--expand-limit 5']) +test.lint(verilator_flags2=['--stats', '--expand-limit 5', '-fno-dfg']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 1) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 1) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_const.py b/test_regress/t/t_gate_inline_wide_noexclude_const.py index 5102631f7..cb6511e21 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_const.py +++ b/test_regress/t/t_gate_inline_wide_noexclude_const.py @@ -13,7 +13,7 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 2) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 2) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_other_scope.v b/test_regress/t/t_gate_inline_wide_noexclude_other_scope.v deleted file mode 100644 index c70022d24..000000000 --- a/test_regress/t/t_gate_inline_wide_noexclude_other_scope.v +++ /dev/null @@ -1,34 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain -// SPDX-FileCopyrightText: 2024 Antmicro -// SPDX-License-Identifier: CC0-1.0 - -localparam N = 256; // Wider than expand limit. - -module t ( - input wire [N-1:0] i, - output wire [N-1:0] o -); - - // Do not exclude from inlining wides referenced in different scope. - wire [N-1:0] wide = N ~^ i; - - sub sub ( - i, - wide, - o - ); -endmodule - -module sub ( - input wire [N-1:0] i, - input wire [N-1:0] wide, - output logic [N-1:0] o -); - initial begin - for (integer n = 0; n < N; ++n) begin - o[n] = i[N-1-n] | wide[N-1-n]; - end - end -endmodule diff --git a/test_regress/t/t_gate_inline_wide_noexclude_sel.py b/test_regress/t/t_gate_inline_wide_noexclude_sel.py index aa65429ab..41d13c55d 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_sel.py +++ b/test_regress/t/t_gate_inline_wide_noexclude_sel.py @@ -13,8 +13,8 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5', '-fno-var-split']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 1) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 1) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 2) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 1) test.file_grep(test.stats, r'SplitVar, packed variables split automatically\s+(\d+)', 0) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_small_wide.v b/test_regress/t/t_gate_inline_wide_noexclude_small_wide.v deleted file mode 100644 index 1b1231c4e..000000000 --- a/test_regress/t/t_gate_inline_wide_noexclude_small_wide.v +++ /dev/null @@ -1,21 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain -// SPDX-FileCopyrightText: 2024 Antmicro -// SPDX-License-Identifier: CC0-1.0 - -localparam N = 65; // Wide but narrower than expand limit - -module t ( - input wire [N-1:0] i, - output wire [N-1:0] o -); - - // Do not exclude from inlining wides small enough to be handled by - // V3Expand. - wire [65:0] wide_small = N << i * i / N; - - for (genvar n = 0; n < N; ++n) begin - assign o[n] = i[n] ^ wide_small[n]; - end -endmodule diff --git a/test_regress/t/t_gate_inline_wide_noexclude_varref.py b/test_regress/t/t_gate_inline_wide_noexclude_varref.py index 1d91e6493..282e88b84 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_varref.py +++ b/test_regress/t/t_gate_inline_wide_noexclude_varref.py @@ -13,7 +13,7 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 0) test.passes() diff --git a/test_regress/t/t_interface_virtual_func_module_call.py b/test_regress/t/t_interface_virtual_func_module_call.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_interface_virtual_func_module_call.py @@ -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() diff --git a/test_regress/t/t_interface_virtual_func_module_call.v b/test_regress/t/t_interface_virtual_func_module_call.v new file mode 100644 index 000000000..7489bead4 --- /dev/null +++ b/test_regress/t/t_interface_virtual_func_module_call.v @@ -0,0 +1,24 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +interface iface; + int cnt = 0; + function void bump(); + cnt++; + endfunction +endinterface + +module t; + iface theIf (); + virtual iface vif; + initial begin + vif = theIf; + vif.bump(); + if (theIf.cnt !== 1) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_json_only_begin_hier.out b/test_regress/t/t_json_only_begin_hier.out index 8e779675c..68cc2bd11 100644 --- a/test_regress/t/t_json_only_begin_hier.out +++ b/test_regress/t/t_json_only_begin_hier.out @@ -2,7 +2,7 @@ "modulesp": [ {"type":"MODULE","name":"test","addr":"(E)","loc":"d,21:8,21:12","origName":"test","verilogName":"test","level":1,"timeunit":"1ps","inlinesp": [], "stmtsp": [ - {"type":"VAR","name":"N","addr":"(F)","loc":"d,22:10,22:11","dtypep":"(G)","origName":"N","verilogName":"N","direction":"NONE","isUsedLoopIdx":true,"lifetime":"VSTATICI","varType":"GENVAR","dtypeName":"integer","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"N","addr":"(F)","loc":"d,22:10,22:11","dtypep":"(G)","origName":"N","verilogName":"N","direction":"NONE","isUsedLoopIdx":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"GENVAR","dtypeName":"integer","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"GENBLOCK","name":"FOR_GENERATE","addr":"(H)","loc":"d,24:5,24:8","implied":true,"genforp": [],"itemsp": []}, {"type":"GENBLOCK","name":"FOR_GENERATE[0]","addr":"(I)","loc":"d,25:14,25:24","genforp": [], "itemsp": [ diff --git a/test_regress/t/t_json_only_first.out b/test_regress/t/t_json_only_first.out index f32e2e41d..0f60e9c25 100644 --- a/test_regress/t/t_json_only_first.out +++ b/test_regress/t/t_json_only_first.out @@ -2,13 +2,13 @@ "modulesp": [ {"type":"MODULE","name":"t","addr":"(E)","loc":"d,7:8,7:9","origName":"t","verilogName":"t","level":1,"timeunit":"1ps","inlinesp": [], "stmtsp": [ - {"type":"VAR","name":"q","addr":"(F)","loc":"d,16:21,16:22","dtypep":"(G)","origName":"q","verilogName":"q","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"q","addr":"(F)","loc":"d,16:21,16:22","dtypep":"(G)","origName":"q","verilogName":"q","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"clk","addr":"(H)","loc":"d,14:9,14:12","dtypep":"(I)","origName":"clk","verilogName":"clk","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"d","addr":"(J)","loc":"d,15:15,15:16","dtypep":"(G)","origName":"d","verilogName":"d","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"between","addr":"(K)","loc":"d,18:15,18:22","dtypep":"(G)","origName":"between","verilogName":"between","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"direct_named","addr":"(L)","loc":"d,19:9,19:21","dtypep":"(I)","origName":"direct_named","verilogName":"direct_named","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"computed_named","addr":"(M)","loc":"d,20:9,20:23","dtypep":"(I)","origName":"computed_named","verilogName":"computed_named","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"anonymous_expr","addr":"(N)","loc":"d,21:9,21:23","dtypep":"(I)","origName":"anonymous_expr","verilogName":"anonymous_expr","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"between","addr":"(K)","loc":"d,18:15,18:22","dtypep":"(G)","origName":"between","verilogName":"between","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"direct_named","addr":"(L)","loc":"d,19:9,19:21","dtypep":"(I)","origName":"direct_named","verilogName":"direct_named","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"computed_named","addr":"(M)","loc":"d,20:9,20:23","dtypep":"(I)","origName":"computed_named","verilogName":"computed_named","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"anonymous_expr","addr":"(N)","loc":"d,21:9,21:23","dtypep":"(I)","origName":"anonymous_expr","verilogName":"anonymous_expr","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"S_IDLE","addr":"(O)","loc":"d,23:26,23:32","dtypep":"(P)","origName":"S_IDLE","verilogName":"S_IDLE","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"LPARAM","dtypeName":"logic","isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], "valuep": [ {"type":"CONST","name":"2'h0","addr":"(Q)","loc":"d,23:35,23:40","dtypep":"(R)"} @@ -122,7 +122,7 @@ "stmtsp": [ {"type":"VAR","name":"clk","addr":"(QC)","loc":"d,62:11,62:14","dtypep":"(I)","origName":"clk","verilogName":"clk","direction":"INPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"d","addr":"(KC)","loc":"d,63:17,63:18","dtypep":"(G)","origName":"d","verilogName":"d","direction":"INPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"q","addr":"(NC)","loc":"d,64:23,64:24","dtypep":"(G)","origName":"q","verilogName":"q","direction":"OUTPUT","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"q","addr":"(NC)","loc":"d,64:23,64:24","dtypep":"(G)","origName":"q","verilogName":"q","direction":"OUTPUT","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"ALWAYS","name":"","addr":"(SC)","loc":"d,67:12,67:13","keyword":"cont_assign","sentreep": [], "stmtsp": [ {"type":"ASSIGNW","name":"","addr":"(TC)","loc":"d,67:12,67:13","dtypep":"(G)", @@ -142,7 +142,7 @@ ],"attrsp": []}, {"type":"VAR","name":"clk","addr":"(CC)","loc":"d,50:11,50:14","dtypep":"(I)","origName":"clk","verilogName":"clk","direction":"INPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"d","addr":"(FC)","loc":"d,51:23,51:24","dtypep":"(G)","origName":"d","verilogName":"d","direction":"INPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"q","addr":"(ZB)","loc":"d,52:30,52:31","dtypep":"(G)","origName":"q","verilogName":"q","direction":"OUTPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"q","addr":"(ZB)","loc":"d,52:30,52:31","dtypep":"(G)","origName":"q","verilogName":"q","direction":"OUTPUT","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"IGNORED","addr":"(AD)","loc":"d,55:14,55:21","dtypep":"(XC)","origName":"IGNORED","verilogName":"IGNORED","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"LPARAM","dtypeName":"logic","isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], "valuep": [ {"type":"CONST","name":"32'sh1","addr":"(BD)","loc":"d,55:24,55:25","dtypep":"(ZC)"} diff --git a/test_regress/t/t_json_only_flat.out b/test_regress/t/t_json_only_flat.out index 727724001..e90bba08b 100644 --- a/test_regress/t/t_json_only_flat.out +++ b/test_regress/t/t_json_only_flat.out @@ -2,16 +2,16 @@ "modulesp": [ {"type":"MODULE","name":"$root","addr":"(F)","loc":"d,7:8,7:9","origName":"$root","verilogName":"$root","level":1,"modPublic":true,"timeunit":"1ps","inlinesp": [], "stmtsp": [ - {"type":"VAR","name":"q","addr":"(G)","loc":"d,16:21,16:22","dtypep":"(H)","origName":"q","verilogName":"q","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"q","addr":"(G)","loc":"d,16:21,16:22","dtypep":"(H)","origName":"q","verilogName":"q","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"clk","addr":"(I)","loc":"d,14:9,14:12","dtypep":"(J)","origName":"clk","verilogName":"clk","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"d","addr":"(K)","loc":"d,15:15,15:16","dtypep":"(H)","origName":"d","verilogName":"d","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.q","addr":"(L)","loc":"d,16:21,16:22","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.q","addr":"(L)","loc":"d,16:21,16:22","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.clk","addr":"(M)","loc":"d,14:9,14:12","dtypep":"(J)","origName":"clk","verilogName":"clk","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.d","addr":"(N)","loc":"d,15:15,15:16","dtypep":"(H)","origName":"d","verilogName":"d","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.between","addr":"(O)","loc":"d,18:15,18:22","dtypep":"(H)","origName":"between","verilogName":"between","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.direct_named","addr":"(P)","loc":"d,19:9,19:21","dtypep":"(J)","origName":"direct_named","verilogName":"direct_named","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.computed_named","addr":"(Q)","loc":"d,20:9,20:23","dtypep":"(J)","origName":"computed_named","verilogName":"computed_named","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.anonymous_expr","addr":"(R)","loc":"d,21:9,21:23","dtypep":"(J)","origName":"anonymous_expr","verilogName":"anonymous_expr","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.between","addr":"(O)","loc":"d,18:15,18:22","dtypep":"(H)","origName":"between","verilogName":"between","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.direct_named","addr":"(P)","loc":"d,19:9,19:21","dtypep":"(J)","origName":"direct_named","verilogName":"direct_named","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.computed_named","addr":"(Q)","loc":"d,20:9,20:23","dtypep":"(J)","origName":"computed_named","verilogName":"computed_named","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.anonymous_expr","addr":"(R)","loc":"d,21:9,21:23","dtypep":"(J)","origName":"anonymous_expr","verilogName":"anonymous_expr","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.S_IDLE","addr":"(S)","loc":"d,23:26,23:32","dtypep":"(T)","origName":"S_IDLE","verilogName":"S_IDLE","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"LPARAM","dtypeName":"logic","isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], "valuep": [ {"type":"CONST","name":"2'h0","addr":"(U)","loc":"d,23:35,23:40","dtypep":"(V)"} @@ -30,14 +30,14 @@ ],"attrsp": []}, {"type":"VAR","name":"t.cell1.clk","addr":"(EB)","loc":"d,50:11,50:14","dtypep":"(J)","origName":"clk","verilogName":"clk","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.cell1.d","addr":"(FB)","loc":"d,51:23,51:24","dtypep":"(H)","origName":"d","verilogName":"d","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.cell1.q","addr":"(GB)","loc":"d,52:30,52:31","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.cell1.q","addr":"(GB)","loc":"d,52:30,52:31","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.cell1.IGNORED","addr":"(HB)","loc":"d,55:14,55:21","dtypep":"(BB)","origName":"IGNORED","verilogName":"IGNORED","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"LPARAM","dtypeName":"logic","isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], "valuep": [ {"type":"CONST","name":"32'sh1","addr":"(IB)","loc":"d,55:24,55:25","dtypep":"(DB)"} ],"attrsp": []}, {"type":"VAR","name":"t.cell2.clk","addr":"(JB)","loc":"d,62:11,62:14","dtypep":"(J)","origName":"clk","verilogName":"clk","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.cell2.d","addr":"(KB)","loc":"d,63:17,63:18","dtypep":"(H)","origName":"d","verilogName":"d","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.cell2.q","addr":"(LB)","loc":"d,64:23,64:24","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.cell2.q","addr":"(LB)","loc":"d,64:23,64:24","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"TOPSCOPE","name":"","addr":"(E)","loc":"d,7:8,7:9","senTreesp": [], "scopep": [ {"type":"SCOPE","name":"TOP","addr":"(MB)","loc":"d,7:8,7:9","aboveScopep":"UNLINKED","aboveCellp":"UNLINKED","modp":"(F)", diff --git a/test_regress/t/t_json_only_flat_vlvbound.out b/test_regress/t/t_json_only_flat_vlvbound.out index ff23ac385..1725da9ef 100644 --- a/test_regress/t/t_json_only_flat_vlvbound.out +++ b/test_regress/t/t_json_only_flat_vlvbound.out @@ -4,12 +4,12 @@ "stmtsp": [ {"type":"VAR","name":"i_a","addr":"(G)","loc":"d,8:24,8:27","dtypep":"(H)","origName":"i_a","verilogName":"i_a","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"i_b","addr":"(I)","loc":"d,9:24,9:27","dtypep":"(H)","origName":"i_b","verilogName":"i_b","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"o_a","addr":"(J)","loc":"d,10:24,10:27","dtypep":"(K)","origName":"o_a","verilogName":"o_a","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"o_b","addr":"(L)","loc":"d,11:24,11:27","dtypep":"(K)","origName":"o_b","verilogName":"o_b","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"o_a","addr":"(J)","loc":"d,10:24,10:27","dtypep":"(K)","origName":"o_a","verilogName":"o_a","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"o_b","addr":"(L)","loc":"d,11:24,11:27","dtypep":"(K)","origName":"o_b","verilogName":"o_b","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"vlvbound_test.i_a","addr":"(M)","loc":"d,8:24,8:27","dtypep":"(H)","origName":"i_a","verilogName":"i_a","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"vlvbound_test.i_b","addr":"(N)","loc":"d,9:24,9:27","dtypep":"(H)","origName":"i_b","verilogName":"i_b","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"vlvbound_test.o_a","addr":"(O)","loc":"d,10:24,10:27","dtypep":"(K)","origName":"o_a","verilogName":"o_a","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"vlvbound_test.o_b","addr":"(P)","loc":"d,11:24,11:27","dtypep":"(K)","origName":"o_b","verilogName":"o_b","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"vlvbound_test.o_a","addr":"(O)","loc":"d,10:24,10:27","dtypep":"(K)","origName":"o_a","verilogName":"o_a","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"vlvbound_test.o_b","addr":"(P)","loc":"d,11:24,11:27","dtypep":"(K)","origName":"o_b","verilogName":"o_b","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"TOPSCOPE","name":"","addr":"(E)","loc":"d,7:8,7:21","senTreesp": [], "scopep": [ {"type":"SCOPE","name":"TOP","addr":"(Q)","loc":"d,7:8,7:21","aboveScopep":"UNLINKED","aboveCellp":"UNLINKED","modp":"(F)", diff --git a/test_regress/t/t_json_only_primary_io.out b/test_regress/t/t_json_only_primary_io.out index 896aa716b..a611f6917 100644 --- a/test_regress/t/t_json_only_primary_io.out +++ b/test_regress/t/t_json_only_primary_io.out @@ -5,8 +5,8 @@ {"type":"VAR","name":"clk","addr":"(F)","loc":"d,13:9,13:12","dtypep":"(G)","origName":"clk","verilogName":"clk","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"a1","addr":"(H)","loc":"d,14:9,14:11","dtypep":"(G)","origName":"a1","verilogName":"a1","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"a2","addr":"(I)","loc":"d,15:9,15:11","dtypep":"(G)","origName":"a2","verilogName":"a2","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"ready","addr":"(J)","loc":"d,16:10,16:15","dtypep":"(G)","origName":"ready","verilogName":"ready","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"ready_reg","addr":"(K)","loc":"d,18:8,18:17","dtypep":"(G)","origName":"ready_reg","verilogName":"ready_reg","direction":"NONE","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"ready","addr":"(J)","loc":"d,16:10,16:15","dtypep":"(G)","origName":"ready","verilogName":"ready","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"ready_reg","addr":"(K)","loc":"d,18:8,18:17","dtypep":"(G)","origName":"ready_reg","verilogName":"ready_reg","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"CELL","name":"and_cell","addr":"(L)","loc":"d,20:11,20:19","origName":"and_cell","verilogName":"and_cell","modp":"(M)", "pinsp": [ {"type":"PIN","name":"a1","addr":"(N)","loc":"d,21:8,21:10","svDotName":true,"modVarp":"(O)","modPTypep":"UNLINKED", @@ -37,7 +37,7 @@ "stmtsp": [ {"type":"VAR","name":"a1","addr":"(O)","loc":"d,30:16,30:18","dtypep":"(G)","origName":"a1","verilogName":"a1","direction":"INPUT","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"a2","addr":"(R)","loc":"d,31:16,31:18","dtypep":"(G)","origName":"a2","verilogName":"a2","direction":"INPUT","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"zn","addr":"(U)","loc":"d,32:17,32:19","dtypep":"(G)","origName":"zn","verilogName":"zn","direction":"OUTPUT","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"zn","addr":"(U)","loc":"d,32:17,32:19","dtypep":"(G)","origName":"zn","verilogName":"zn","direction":"OUTPUT","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"ALWAYS","name":"","addr":"(AB)","loc":"d,34:13,34:14","keyword":"cont_assign","sentreep": [], "stmtsp": [ {"type":"ASSIGNW","name":"","addr":"(BB)","loc":"d,34:13,34:14","dtypep":"(G)", diff --git a/test_regress/t/t_json_only_tag.out b/test_regress/t/t_json_only_tag.out index c0d396689..c7e9999c6 100644 --- a/test_regress/t/t_json_only_tag.out +++ b/test_regress/t/t_json_only_tag.out @@ -9,7 +9,7 @@ {"type":"CELL","name":"itop","addr":"(L)","loc":"d,29:7,29:11","origName":"itop","verilogName":"itop","modp":"(M)","pinsp": [],"paramsp": [],"rangep": [],"intfRefsp": []}, {"type":"VAR","name":"itop","addr":"(N)","loc":"d,29:7,29:11","dtypep":"(O)","origName":"itop__Viftop","verilogName":"itop__Viftop","direction":"NONE","lifetime":"VSTATICI","varType":"IFACEREF","dtypeName":"","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"this_struct","addr":"(P)","loc":"d,31:13,31:24","dtypep":"(Q)","origName":"this_struct","verilogName":"this_struct","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"dotted","addr":"(R)","loc":"d,33:15,33:21","dtypep":"(S)","origName":"dotted","verilogName":"dotted","direction":"NONE","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"dotted","addr":"(R)","loc":"d,33:15,33:21","dtypep":"(S)","origName":"dotted","verilogName":"dotted","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"ALWAYS","name":"","addr":"(T)","loc":"d,33:22,33:23","keyword":"cont_assign","sentreep": [], "stmtsp": [ {"type":"ASSIGNW","name":"","addr":"(U)","loc":"d,33:22,33:23","dtypep":"(S)", diff --git a/test_regress/t/t_opt_balance_cats.py b/test_regress/t/t_opt_balance_cats.py index 577c19985..d5e863848 100755 --- a/test_regress/t/t_opt_balance_cats.py +++ b/test_regress/t/t_opt_balance_cats.py @@ -14,7 +14,7 @@ test.scenarios('vlt') test.compile( verilator_flags2=["--stats", "--build", "--gate-stmts", "10000", "--expand-limit", "128"]) -test.file_grep(test.stats, r'Optimizations, FuncOpt concat trees balanced\s+(\d+)', 2) +test.file_grep(test.stats, r'Optimizations, FuncOpt concat trees balanced\s+(\d+)', 3) test.file_grep(test.stats, r'Optimizations, FuncOpt concat splits\s+(\d+)', 67) test.passes() diff --git a/test_regress/t/t_property_s_eventually_iface_param.py b/test_regress/t/t_property_s_eventually_iface_param.py new file mode 100755 index 000000000..1ddad07d5 --- /dev/null +++ b/test_regress/t/t_property_s_eventually_iface_param.py @@ -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(timing_loop=True, verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_property_s_eventually_iface_param.v b/test_regress/t/t_property_s_eventually_iface_param.v new file mode 100644 index 000000000..6a76feabf --- /dev/null +++ b/test_regress/t/t_property_s_eventually_iface_param.v @@ -0,0 +1,40 @@ +// 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 + +interface iface_if #( + parameter int W = 8 +) ( + input bit clk +); + logic [W-1:0] sig = 0; + int passed = 0; + assert property (@(posedge clk) s_eventually (sig == 1)) passed++; +endinterface + +module t; + bit clk = 0; + initial forever #1 clk = ~clk; + int cyc = 0; + + // Two distinct specializations: V3Param clones the interface into two + // modules, each with its own s_eventually tracking. The generated final + // block must stay per-module. + iface_if #(.W(4)) a (.clk(clk)); + iface_if #(.W(8)) b (.clk(clk)); + + always @(posedge clk) begin + ++cyc; + if (cyc == 2) begin + a.sig <= 1; + b.sig <= 1; + end + if (cyc == 5) begin + if (a.passed == 0 || b.passed == 0) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_property_sexpr_multi.v b/test_regress/t/t_property_sexpr_multi.v index 9a99739e5..ff126810f 100644 --- a/test_regress/t/t_property_sexpr_multi.v +++ b/test_regress/t/t_property_sexpr_multi.v @@ -1,7 +1,7 @@ // DESCRIPTION: Verilator: Verilog Test module // // This file ONLY is placed under the Creative Commons Public Domain. -// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-FileCopyrightText: 2025 Antmicro // SPDX-License-Identifier: CC0-1.0 // verilog_format: off diff --git a/test_regress/t/t_property_sexpr_range_delay.v b/test_regress/t/t_property_sexpr_range_delay.v index 5594b6a54..cd3037d03 100644 --- a/test_regress/t/t_property_sexpr_range_delay.v +++ b/test_regress/t/t_property_sexpr_range_delay.v @@ -30,6 +30,7 @@ module t ( int count_fail2 = 0; int count_fail3 = 0; int count_fail4 = 0; + int count_fail5 = 0; always_ff @(posedge clk) begin `ifdef TEST_VERBOSE @@ -49,13 +50,11 @@ module t ( else if (cyc == 99) begin `checkh(crc, 64'hc77bb9b3784ea091); `checkh(sum, 64'h38c614665c6b71ad); - // count_fail1 overcounts Questa by 1: NFA per-cycle reject merging - // OR's requiredStep-fail and terminal-fail into one signal; Questa - // resolves the same overlap as a single per-attempt miss. - `checkd(count_fail1, 25); // Questa: 24 - `checkd(count_fail2, 50); // Questa: 50 - `checkd(count_fail3, 24); // Questa: 24 - `checkd(count_fail4, 1); // Questa: 1 + `checkd(count_fail1, 24); + `checkd(count_fail2, 50); + `checkd(count_fail3, 24); + `checkd(count_fail4, 1); + `checkd(count_fail5, 1); $write("*-* All Finished *-*\n"); $finish; end @@ -131,4 +130,25 @@ module t ( assert property (@(posedge clk) disable iff (cyc < 2) a |-> ##[+] b ##1 (a | b | c | d | e)); + // Finite range delay with a multi-cycle RHS must not reject on an earlier + // candidate when a later candidate in the same window matches. + assert property (@(posedge clk) + cyc == 10 |-> ##[2:4] ((cyc == 12 || cyc == 13) ##1 cyc == 14)); + + // Same shape, but every RHS candidate in the range window rejects, so the + // assertion attempt itself must reject once. + assert property (@(posedge clk) + cyc == 20 |-> ##[2:4] ((cyc == 22 || cyc == 23) ##1 cyc == 25)) + else count_fail5 <= count_fail5 + 1; + + // Variable-length nested RHS, then another finite range below + // the liveness scope. + assert property (@(posedge clk) + cyc == 30 |-> ##[1:2] ((cyc == 31) ##[1:2] ((cyc == 32) ##1 1'b1))); + + // Finite range whose RHS ends in an NFA state, not a final + // boolean condition. + assert property (@(posedge clk) + cyc == 40 |-> ##[1:2] (##1 (((cyc == 43) ##1 1'b1) or ((cyc == 43) ##1 1'b1)))); + endmodule diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned.cpp b/test_regress/t/t_sched_ico_change_detect_input_assigned.cpp new file mode 100644 index 000000000..27e7a3d39 --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned.cpp @@ -0,0 +1,45 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +// +// 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 + +#include + +#include +#include + +#include VM_PREFIX_INCLUDE + +int main(int argc, char** argv) { + const std::unique_ptr contextp{new VerilatedContext}; + contextp->threads(1); + contextp->commandArgs(argc, argv); + + const std::unique_ptr topp{new VM_PREFIX{contextp.get(), "top"}}; + topp->clk = 0; + topp->i = 0; + topp->eval(); + + while ((contextp->time() < 10000) && !contextp->gotFinish()) { + contextp->timeInc(1); + topp->clk = !topp->clk; + // Always set to the same constant value, so change detection will think it's not changing + topp->i = 500000; + topp->eval(); + if (topp->o != topp->i + 10) { + const std::string msg = "%Error: incorrect output, got: " + std::to_string(topp->o) + + " expected: " + std::to_string(topp->i + 10); + vl_fatal(__FILE__, __LINE__, "main", msg.c_str()); + break; + } + } + + if (!contextp->gotFinish()) { + vl_fatal(__FILE__, __LINE__, "main", "%Error: Timeout; never got a $finish"); + } + topp->final(); + return 0; +} diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned.py b/test_regress/t/t_sched_ico_change_detect_input_assigned.py new file mode 100755 index 000000000..c837fa2b9 --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_sched_ico_change_detect_input_assigned.v" + +test.compile(make_top_shell=False, + make_main=False, + verilator_flags2=[ + "--exe", "--stats", "-Wno-ASSIGNIN", + "t/t_sched_ico_change_detect_input_assigned.cpp" + ]) + +test.execute() + +# There should be a change detect for 'clk', but not for 'i' +test.file_grep(test.stats, r"Scheduling, 'ico' change detect triggers\s+(\d+)", 1) + +test.passes() diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned.v b/test_regress/t/t_sched_ico_change_detect_input_assigned.v new file mode 100644 index 000000000..9a4f79b0b --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned.v @@ -0,0 +1,41 @@ +// 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); +`define checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + clk, + i, + o, + cyc +); + + input clk, i; + output o, cyc; + + logic clk; + int i; // Primary input that the design also drives + int o; + int cyc = 0; + + // Logic dependent on primary input 'i' + always_comb o = i + 10; + + always @(posedge clk) begin + cyc <= cyc + 1; + // On even cycles, assign 'i' + if (cyc % 2 == 0) i = cyc + 1000; + if (cyc == 99) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned_off.py b/test_regress/t/t_sched_ico_change_detect_input_assigned_off.py new file mode 100755 index 000000000..b98a8e1b6 --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned_off.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_sched_ico_change_detect_input_assigned.v" + +test.compile(make_top_shell=False, + make_main=False, + verilator_flags2=[ + "--exe", "--stats", "-Wno-ASSIGNIN", + "t/t_sched_ico_change_detect_input_assigned.cpp", "-fno-ico-change-detect" + ]) + +test.execute() + +test.file_grep(test.stats, r"Scheduling, 'ico' change detect triggers\s+(\d+)", 0) + +test.passes() diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi.py b/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi.py new file mode 100755 index 000000000..cd22fef43 --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_sched_ico_change_detect_input_assigned.v" + +test.compile(make_top_shell=False, + make_main=False, + verilator_flags2=[ + "--exe", "--stats", "-Wno-ASSIGNIN", + "t/t_sched_ico_change_detect_input_assigned.cpp", "--vpi" + ]) + +test.execute() + +test.file_grep(test.stats, r"Scheduling, 'ico' change detect triggers\s+(\d+)", 0) + +test.passes() diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi_on.py b/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi_on.py new file mode 100755 index 000000000..afd44e7ce --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi_on.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_sched_ico_change_detect_input_assigned.v" + +test.compile(make_top_shell=False, + make_main=False, + verilator_flags2=[ + "--exe", "--stats", "-Wno-ASSIGNIN", + "t/t_sched_ico_change_detect_input_assigned.cpp", "-fico-change-detect", "--vpi" + ]) + +test.execute() + +test.file_grep(test.stats, r"Scheduling, 'ico' change detect triggers\s+(\d+)", 1) + +test.passes() diff --git a/test_regress/t/t_select_bound4.py b/test_regress/t/t_select_bound4.py new file mode 100755 index 000000000..00e3aae74 --- /dev/null +++ b/test_regress/t/t_select_bound4.py @@ -0,0 +1,20 @@ +#!/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: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--stats"]) + +test.execute() + +test.file_grep(test.stats, r'Unknowns, else branches created\s+(\d+)', 0) + +test.passes() diff --git a/test_regress/t/t_select_bound4.v b/test_regress/t/t_select_bound4.v new file mode 100644 index 000000000..d09a59a52 --- /dev/null +++ b/test_regress/t/t_select_bound4.v @@ -0,0 +1,29 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +module t; + int q[2][$]; + + task automatic pop_q(input int qid, input int expected); + int actual; + actual = q[qid].pop_front(); + if (qid < 2 && actual !== expected) $stop; + endtask + + initial begin + for (int i = 0; i < 4; i++) begin + q[i].push_back(i); + end + + for (int i = 0; i < 4; i++) begin + pop_q(i, i); + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_select_bound_side_effect.py b/test_regress/t/t_select_bound_side_effect.py new file mode 100755 index 000000000..d696b53d9 --- /dev/null +++ b/test_regress/t/t_select_bound_side_effect.py @@ -0,0 +1,20 @@ +#!/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: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--stats"]) + +test.execute() + +test.file_grep(test.stats, r'Unknowns, else branches created\s+(\d+)', 1) + +test.passes() diff --git a/test_regress/t/t_select_bound_side_effect.v b/test_regress/t/t_select_bound_side_effect.v new file mode 100644 index 000000000..b6a182d04 --- /dev/null +++ b/test_regress/t/t_select_bound_side_effect.v @@ -0,0 +1,74 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t; + int arr[5]; + int x = 0; + int y = 0; + int z = 0; + + task automatic incr(input int i, input int expected); + arr[i] = x++; + if (i < 5) `checkh(arr[i], expected); + endtask + + function int get_y; + y++; + return y; + endfunction + + task automatic assign_side_effect(input int i, input int expected); + arr[i] = get_y(); + if (i < 5) `checkh(arr[i], expected); + endtask + + task automatic add_z(inout int a); + a += z; + z++; + endtask + + task automatic assign_side_effect_inout(input int i, input int expected); + if (i < 5) arr[i] = 1; + add_z(arr[i]); + if (i < 5) `checkh(arr[i], expected); + endtask + + initial begin + incr(0, 0); + incr(7, 0); + incr(4, 2); + + assign_side_effect(3, 1); + assign_side_effect(8, 0); + assign_side_effect(9, 0); + assign_side_effect(3, 4); + + assign_side_effect_inout(3, 1); + assign_side_effect_inout(4, 2); + assign_side_effect_inout(5, 0); + assign_side_effect_inout(1, 4); + + y = 0; + for (int i = 0; i < 10; i++) begin + arr[get_y()] = i; + if (y < 5) `checkh(arr[y], i); + `checkh(y, 2 * i + 1); + arr[get_y()%(i+1)] = i; + if (y % (i + 1) < 5) `checkh(arr[y%(i+1)], i); + `checkh(y, 2 * (i + 1)); + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_gate_inline_wide_noexclude_other_scope.py b/test_regress/t/t_select_bound_timing_intra.py similarity index 71% rename from test_regress/t/t_gate_inline_wide_noexclude_other_scope.py rename to test_regress/t/t_select_bound_timing_intra.py index c20d324db..4fae43d09 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_other_scope.py +++ b/test_regress/t/t_select_bound_timing_intra.py @@ -9,10 +9,12 @@ import vltest_bootstrap -test.scenarios('vlt') +test.scenarios('simulator') -test.lint(verilator_flags2=['--stats', '--expand-limit 5']) +test.compile(verilator_flags2=["--binary", "--stats"]) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) +test.execute() + +test.file_grep(test.stats, r'Unknowns, else branches created\s+(\d+)', 1) test.passes() diff --git a/test_regress/t/t_select_bound_timing_intra.v b/test_regress/t/t_select_bound_timing_intra.v new file mode 100644 index 000000000..31d30c48c --- /dev/null +++ b/test_regress/t/t_select_bound_timing_intra.v @@ -0,0 +1,34 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t; + int arr[5]; + + task automatic intra(input int i); + time t = $time; + arr[i] = #1 i; + #1; + if (i < 5) `checkh(arr[i], i); + `checkh($time, t + 2); + endtask + + initial begin + intra(0); + intra(7); + intra(4); + intra(1); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_sequence_sexpr_unsup.out b/test_regress/t/t_sequence_sexpr_unsup.out index 975a06491..9bf5bf9ea 100644 --- a/test_regress/t/t_sequence_sexpr_unsup.out +++ b/test_regress/t/t_sequence_sexpr_unsup.out @@ -8,15 +8,4 @@ %Error-UNSUPPORTED: t/t_sequence_sexpr_unsup.v:99:5: Unsupported: first_match with sequence_match_items 99 | first_match (a, res0 = 1, res1 = 2); | ^~~~~~~~~~~ -%Warning-COVERIGN: t/t_sequence_sexpr_unsup.v:102:9: Ignoring unsupported: cover sequence - 102 | cover sequence (s_a) $display(""); - | ^~~~~~~~ - ... For warning description see https://verilator.org/warn/COVERIGN?v=latest - ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. -%Warning-COVERIGN: t/t_sequence_sexpr_unsup.v:103:9: Ignoring unsupported: cover sequence - 103 | cover sequence (@(posedge a) disable iff (b) s_a) $display(""); - | ^~~~~~~~ -%Warning-COVERIGN: t/t_sequence_sexpr_unsup.v:104:9: Ignoring unsupported: cover sequence - 104 | cover sequence (disable iff (b) s_a) $display(""); - | ^~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_stream_queue.py b/test_regress/t/t_stream_queue.py index 3a46a7545..f7785cc52 100755 --- a/test_regress/t/t_stream_queue.py +++ b/test_regress/t/t_stream_queue.py @@ -9,7 +9,8 @@ import vltest_bootstrap -test.scenarios('simulator') +# Issue #7780 unstable with --vltmt +test.scenarios('simulator_st') test.compile(verilator_flags2=["--timing"]) diff --git a/test_regress/t/t_stream_unpacked_struct.py b/test_regress/t/t_stream_unpacked_struct.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct.py @@ -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() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_stream_unpacked_struct.v b/test_regress/t/t_stream_unpacked_struct.v new file mode 100644 index 000000000..8a169c20e --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct.v @@ -0,0 +1,348 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// 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 + +// Ref. to IEEE 1800-2023 11.4.14, A.8.1 + +module t; + + `define checkh(gotv, expv) \ + do if ((gotv) !== (expv)) begin \ + $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__, `__LINE__, (gotv), (expv)); \ + $stop; \ + end while (0); + + typedef struct packed { + logic [3:0] hi; + logic [3:0] lo; + } packed_pair_t; + + typedef union packed { + logic [7:0] byte_data; + packed_pair_t pair; + } packed_union_t; + + typedef struct { + byte a; + logic [3:0] b; + packed_pair_t p; + } simple_t; + + typedef struct { + byte prefix; + byte data[2]; + packed_pair_t p; + } array_t; + + typedef struct { + simple_t inner; + byte tail[2]; + } nested_t; + + typedef struct { + byte prefix; + simple_t items[2]; + byte suffix; + } struct_array_t; + + typedef struct { + byte data[1:0]; + } descending_array_t; + + typedef struct { + byte matrix[2][3]; + } matrix_array_t; + + typedef struct { + logic [1:0][3:0] data[2]; + } mixed_array_t; + + typedef enum logic [2:0] { + E0 = 3'd0, + E5 = 3'd5 + } enum_t; + + typedef struct { + enum_t e; + logic [4:0] x; + } enum_struct_t; + + typedef struct { + byte tag; + real r; + realtime rt; + real ra[2]; + } real_struct_t; + + typedef struct { + packed_union_t u; + byte tail; + } packed_union_struct_t; + + simple_t simple; + simple_t simple_out; + simple_t simple_cont_out; + array_t arrayed; + array_t arrayed_out; + nested_t nested; + nested_t nested_out; + struct_array_t struct_array; + struct_array_t struct_array_out; + descending_array_t descending; + descending_array_t descending_out; + matrix_array_t matrix_array; + matrix_array_t matrix_array_out; + mixed_array_t mixed_array; + mixed_array_t mixed_array_out; + simple_t simple_array[2]; + simple_t simple_array_out[2]; + enum_struct_t enum_struct; + enum_struct_t enum_struct_out; + real_struct_t real_struct; + real_struct_t real_struct_out; + packed_union_struct_t packed_union_struct; + packed_union_struct_t packed_union_struct_out; + + logic [19:0] simple_bits; + logic [31:0] wide_simple_bits; + logic [31:0] array_bits; + logic [35:0] nested_bits; + logic [55:0] struct_array_bits; + logic [15:0] descending_bits; + logic [47:0] matrix_array_bits; + logic [15:0] mixed_array_bits; + logic [39:0] simple_array_bits; + logic [31:0] byteswapped_bits; + logic [7:0] enum_bits; + logic [263:0] real_bits; + logic [15:0] packed_union_bits; + logic [11:0] narrow_bits; + logic [19:0] simple_streaml_src; + byte byte_array_out[2]; + + assign {>>{simple_cont_out}} = 20'habcde; + + initial begin + byteswapped_bits = {<<8{32'h11223344}}; + `checkh(byteswapped_bits, 32'h44332211); + byteswapped_bits = {<>{simple_bits}} = 20'h13579; + `checkh(simple_bits, 20'h13579); + {<<4{simple_bits}} = 20'h12345; + `checkh(simple_bits, 20'h54321); + + simple = '{8'h12, 4'ha, '{4'hb, 4'hc}}; + simple_bits = {>>{simple}}; + `checkh(simple_bits, 20'h12abc); + /* verilator lint_off WIDTHEXPAND */ + wide_simple_bits = {>>{simple}}; + /* verilator lint_on WIDTHEXPAND */ + `checkh(wide_simple_bits, 32'h12abc000); + simple_bits = {<<4{simple}}; + `checkh(simple_bits, 20'hcba21); + + {>>{simple_out}} = 20'h345de; + `checkh(simple_out.a, 8'h34); + `checkh(simple_out.b, 4'h5); + `checkh(simple_out.p, 8'hde); + `checkh(simple_cont_out.a, 8'hab); + `checkh(simple_cont_out.b, 4'hc); + `checkh(simple_cont_out.p, 8'hde); + + {<<4{simple_out}} = 20'h789ab; + `checkh(simple_out.a, 8'hba); + `checkh(simple_out.b, 4'h9); + `checkh(simple_out.p, 8'h87); + + simple_out = {>>{20'hfedcb}}; + `checkh(simple_out.a, 8'hfe); + `checkh(simple_out.b, 4'hd); + `checkh(simple_out.p, 8'hcb); + + simple_out = {>>{simple}}; + `checkh(simple_out.a, 8'h12); + `checkh(simple_out.b, 4'ha); + `checkh(simple_out.p, 8'hbc); + + {>>{simple_out}} = simple; + `checkh(simple_out.a, 8'h12); + `checkh(simple_out.b, 4'ha); + `checkh(simple_out.p, 8'hbc); + + if ($test$plusargs("t_stream_unpacked_struct_alt")) begin + narrow_bits = 12'h123; + end + else begin + narrow_bits = 12'habd; + end + /* verilator lint_off WIDTHEXPAND */ + simple_bits = {>>{narrow_bits}}; + /* verilator lint_on WIDTHEXPAND */ + `checkh(simple_bits, {narrow_bits, 8'h00}); + + simple_out = {>>{narrow_bits}}; + `checkh(simple_out.a, narrow_bits[11:4]); + `checkh(simple_out.b, narrow_bits[3:0]); + `checkh(simple_out.p, 8'h00); + + {>>{simple_out}} = 24'habcdef; + `checkh(simple_out.a, 8'hab); + `checkh(simple_out.b, 4'hc); + `checkh(simple_out.p, 8'hde); + + simple_out = {<<4{20'h13579}}; + `checkh(simple_out.a, 8'h97); + `checkh(simple_out.b, 4'h5); + `checkh(simple_out.p, 8'h31); + + simple_streaml_src = 20'h2468a; + simple_out = {<<4{simple_streaml_src}}; + `checkh(simple_out.a, 8'ha8); + `checkh(simple_out.b, 4'h6); + `checkh(simple_out.p, 8'h42); + + {<<4{simple_out}} = simple_streaml_src; + `checkh(simple_out.a, 8'ha8); + `checkh(simple_out.b, 4'h6); + `checkh(simple_out.p, 8'h42); + + {<<8{byte_array_out}} = 16'h1234; + `checkh(byte_array_out[0], 8'h34); + `checkh(byte_array_out[1], 8'h12); + + arrayed = '{8'h11, '{8'h22, 8'h33}, '{4'h4, 4'h5}}; + array_bits = {>>{arrayed}}; + `checkh(array_bits, 32'h11223345); + array_bits = {<<8{arrayed}}; + `checkh(array_bits, 32'h45332211); + + {>>{arrayed_out}} = 32'haabbccdd; + `checkh(arrayed_out.prefix, 8'haa); + `checkh(arrayed_out.data[0], 8'hbb); + `checkh(arrayed_out.data[1], 8'hcc); + `checkh(arrayed_out.p, 8'hdd); + + nested = '{'{8'h12, 4'ha, '{4'hb, 4'hc}}, '{8'h34, 8'h56}}; + nested_bits = {>>{nested}}; + `checkh(nested_bits, 36'h12abc3456); + + {>>{nested_out}} = 36'h6543210fe; + `checkh(nested_out.inner.a, 8'h65); + `checkh(nested_out.inner.b, 4'h4); + `checkh(nested_out.inner.p, 8'h32); + `checkh(nested_out.tail[0], 8'h10); + `checkh(nested_out.tail[1], 8'hfe); + + struct_array.prefix = 8'haa; + struct_array.items[0] = '{8'h12, 4'h3, '{4'h4, 4'h5}}; + struct_array.items[1] = '{8'h67, 4'h8, '{4'h9, 4'ha}}; + struct_array.suffix = 8'hbb; + struct_array_bits = {>>{struct_array}}; + `checkh(struct_array_bits, 56'haa123456789abb); + + {>>{struct_array_out}} = 56'hccdef0123456dd; + `checkh(struct_array_out.prefix, 8'hcc); + `checkh(struct_array_out.items[0].a, 8'hde); + `checkh(struct_array_out.items[0].b, 4'hf); + `checkh(struct_array_out.items[0].p, 8'h01); + `checkh(struct_array_out.items[1].a, 8'h23); + `checkh(struct_array_out.items[1].b, 4'h4); + `checkh(struct_array_out.items[1].p, 8'h56); + `checkh(struct_array_out.suffix, 8'hdd); + + descending = '{data: '{8'hcc, 8'hdd}}; + descending_bits = {>>{descending}}; + `checkh(descending_bits, 16'hccdd); + + {>>{descending_out}} = 16'h1234; + `checkh(descending_out.data[1], 8'h12); + `checkh(descending_out.data[0], 8'h34); + + matrix_array.matrix[0][0] = 8'h11; + matrix_array.matrix[0][1] = 8'h22; + matrix_array.matrix[0][2] = 8'h33; + matrix_array.matrix[1][0] = 8'h44; + matrix_array.matrix[1][1] = 8'h55; + matrix_array.matrix[1][2] = 8'h66; + matrix_array_bits = {>>{matrix_array}}; + `checkh(matrix_array_bits, 48'h112233445566); + + {>>{matrix_array_out}} = 48'haabbccddeeff; + `checkh(matrix_array_out.matrix[0][0], 8'haa); + `checkh(matrix_array_out.matrix[0][1], 8'hbb); + `checkh(matrix_array_out.matrix[0][2], 8'hcc); + `checkh(matrix_array_out.matrix[1][0], 8'hdd); + `checkh(matrix_array_out.matrix[1][1], 8'hee); + `checkh(matrix_array_out.matrix[1][2], 8'hff); + + mixed_array.data[0] = 8'hab; + mixed_array.data[1] = 8'hcd; + mixed_array_bits = {>>{mixed_array}}; + `checkh(mixed_array_bits, 16'habcd); + + {>>{mixed_array_out}} = 16'h1234; + `checkh(mixed_array_out.data[0], 8'h12); + `checkh(mixed_array_out.data[0][1], 4'h1); + `checkh(mixed_array_out.data[0][0], 4'h2); + `checkh(mixed_array_out.data[1], 8'h34); + `checkh(mixed_array_out.data[1][1], 4'h3); + `checkh(mixed_array_out.data[1][0], 4'h4); + + simple_array[0] = '{8'h12, 4'ha, '{4'hb, 4'hc}}; + simple_array[1] = '{8'h34, 4'hd, '{4'he, 4'hf}}; + simple_array_bits = {>>{simple_array}}; + `checkh(simple_array_bits, 40'h12abc34def); + + {>>{simple_array_out}} = 40'h567899abcd; + `checkh(simple_array_out[0].a, 8'h56); + `checkh(simple_array_out[0].b, 4'h7); + `checkh(simple_array_out[0].p, 8'h89); + `checkh(simple_array_out[1].a, 8'h9a); + `checkh(simple_array_out[1].b, 4'hb); + `checkh(simple_array_out[1].p, 8'hcd); + + enum_struct = '{E5, 5'ha}; + enum_bits = {>>{enum_struct}}; + `checkh(enum_bits, 8'haa); + + {>>{enum_struct_out}} = 8'h71; + `checkh(enum_struct_out.e, 3'h3); + `checkh(enum_struct_out.x, 5'h11); + + real_struct.tag = 8'h42; + real_struct.r = 1.0; + real_struct.rt = 3.0; + real_struct.ra[0] = 4.0; + real_struct.ra[1] = 5.0; + real_bits = {>>{real_struct}}; + `checkh(real_bits, + {8'h42, $realtobits(1.0), $realtobits(3.0), $realtobits(4.0), $realtobits(5.0)}); + + {>>{real_struct_out}} + = {8'h99, $realtobits(2.0), $realtobits(6.0), $realtobits(7.0), $realtobits(8.0)}; + `checkh(real_struct_out.tag, 8'h99); + `checkh($realtobits(real_struct_out.r), $realtobits(2.0)); + `checkh($realtobits(real_struct_out.rt), $realtobits(6.0)); + `checkh($realtobits(real_struct_out.ra[0]), $realtobits(7.0)); + `checkh($realtobits(real_struct_out.ra[1]), $realtobits(8.0)); + + packed_union_struct.u.byte_data = 8'hbe; + packed_union_struct.tail = 8'hef; + packed_union_bits = {>>{packed_union_struct}}; + `checkh(packed_union_bits, 16'hbeef); + + {>>{packed_union_struct_out}} = 16'hcafe; + `checkh(packed_union_struct_out.u.byte_data, 8'hca); + `checkh(packed_union_struct_out.tail, 8'hfe); + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_stream_unpacked_struct_bad.out b/test_regress/t/t_stream_unpacked_struct_bad.out new file mode 100644 index 000000000..9955e9208 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct_bad.out @@ -0,0 +1,30 @@ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:54:12: Unsupported: Stream operation on a variable of a type 'struct{}t.queue_struct_t' + : ... note: In instance 't' + 54 | out = {>>{queue_struct}}; + | ^~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:55:12: Unsupported: Stream operation on a variable of a type 'struct{}t.dynamic_struct_t' + : ... note: In instance 't' + 55 | out = {>>{dynamic_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:56:12: Unsupported: Stream operation on a variable of a type 'struct{}t.associative_struct_t' + : ... note: In instance 't' + 56 | out = {>>{associative_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:57:12: Unsupported: Stream operation on a variable of a type 'struct{}t.union_struct_t' + : ... note: In instance 't' + 57 | out = {>>{union_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:58:12: Unsupported: Stream operation on a variable of a type 'struct{}t.string_struct_t' + : ... note: In instance 't' + 58 | out = {>>{string_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:59:12: Unsupported: Stream operation on a variable of a type 'struct{}t.chandle_struct_t' + : ... note: In instance 't' + 59 | out = {>>{chandle_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:60:12: Unsupported: Stream operation on a variable of a type 'struct{}t.event_struct_t' + : ... note: In instance 't' + 60 | out = {>>{event_struct}}; + | ^~ +%Error: Exiting due to diff --git a/test_regress/t/t_stream_unpacked_struct_bad.py b/test_regress/t/t_stream_unpacked_struct_bad.py new file mode 100755 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('linter') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_stream_unpacked_struct_bad.v b/test_regress/t/t_stream_unpacked_struct_bad.v new file mode 100644 index 000000000..2c1e00b97 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct_bad.v @@ -0,0 +1,63 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// 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 + +module t; + + typedef struct { + byte bytes[$]; + } queue_struct_t; + + typedef struct { + byte bytes[]; + } dynamic_struct_t; + + typedef struct { + byte bytes[int]; + } associative_struct_t; + + typedef union { + byte a; + logic [15:0] b; + } unpacked_union_t; + + typedef struct { + unpacked_union_t u; + } union_struct_t; + + typedef struct { + string s; + } string_struct_t; + + typedef struct { + chandle c; + } chandle_struct_t; + + typedef struct { + event e; + } event_struct_t; + + logic [255:0] out; + queue_struct_t queue_struct; + dynamic_struct_t dynamic_struct; + associative_struct_t associative_struct; + union_struct_t union_struct; + string_struct_t string_struct; + chandle_struct_t chandle_struct; + event_struct_t event_struct; + + initial begin + out = {>>{queue_struct}}; + out = {>>{dynamic_struct}}; + out = {>>{associative_struct}}; + out = {>>{union_struct}}; + out = {>>{string_struct}}; + out = {>>{chandle_struct}}; + out = {>>{event_struct}}; + end + +endmodule diff --git a/test_regress/t/t_upd_nonsequential.py b/test_regress/t/t_udp_nonsequential.py similarity index 100% rename from test_regress/t/t_upd_nonsequential.py rename to test_regress/t/t_udp_nonsequential.py diff --git a/test_regress/t/t_upd_nonsequential.v b/test_regress/t/t_udp_nonsequential.v similarity index 100% rename from test_regress/t/t_upd_nonsequential.v rename to test_regress/t/t_udp_nonsequential.v index c9496ea84..91c538e39 100644 --- a/test_regress/t/t_upd_nonsequential.v +++ b/test_regress/t/t_udp_nonsequential.v @@ -23,22 +23,22 @@ module t ( sel = 0; end else if (cycle == 1) begin + if (z != 0) $stop; a = 1; b = 1; sel = 0; - if (z != 0) $stop; end else if (cycle == 2) begin + if (z != 1) $stop; a = 0; b = 1; sel = 0; - if (z != 1) $stop; end else if (cycle == 3) begin + if (z != 0) $stop; a = 1; b = 0; sel = 0; - if (z != 0) $stop; end else if (cycle == 4) begin if (z != 1) $stop; diff --git a/test_regress/t/t_unopt_combo_isolate.py b/test_regress/t/t_unopt_combo_isolate.py index f7b4618d3..3ffd80545 100755 --- a/test_regress/t/t_unopt_combo_isolate.py +++ b/test_regress/t/t_unopt_combo_isolate.py @@ -9,37 +9,14 @@ import vltest_bootstrap -test.scenarios('simulator') +# Note: This test is historical for the isolate_assignments attribute, which +# was deprecated and has no effect today. This test ensures it still parses +# in SystemVerilog and Verilator control files for backward compatibility. + +test.scenarios('vlt') test.top_filename = "t/t_unopt_combo.v" -out_filename = test.obj_dir + "/V" + test.name + ".tree.json" - -test.compile(verilator_flags2=[ - "--no-json-edit-nums", "+define+ISOLATE", "--stats", "-fno-dfg", "-fno-lift-expr" -]) - -if test.vlt_all: - test.file_grep(test.stats, r'Optimizations, isolate_assignments blocks\s+4') - test.file_grep( - out_filename, - r'{"type":"VAR","name":"t.b",.*"loc":"\w,23:[^"]*",.*"origName":"b",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vfunc_t.file.get_31_16__0__Vfuncout",.*"loc":"\w,99:[^"]*",.*"origName":"__Vfunc_t__DOT__file__DOT__get_31_16__0__Vfuncout",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vfunc_t.file.get_31_16__0__t_crc",.*"loc":"\w,100:[^"]*",.*"origName":"__Vfunc_t__DOT__file__DOT__get_31_16__0__t_crc",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vtask_t.file.set_b_d__1__t_crc",.*"loc":"\w,112:[^"]*",.*"origName":"__Vtask_t__DOT__file__DOT__set_b_d__1__t_crc",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vtask_t.file.set_b_d__1__t_c",.*"loc":"\w,113:[^"]*",.*"origName":"__Vtask_t__DOT__file__DOT__set_b_d__1__t_c",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) +test.compile(verilator_flags2=["+define+ISOLATE"]) test.execute() diff --git a/test_regress/t/t_unopt_combo_isolate_vlt.py b/test_regress/t/t_unopt_combo_isolate_vlt.py index 35e0cc8d2..1a041dac0 100755 --- a/test_regress/t/t_unopt_combo_isolate_vlt.py +++ b/test_regress/t/t_unopt_combo_isolate_vlt.py @@ -9,38 +9,14 @@ import vltest_bootstrap -test.scenarios('simulator') +# Note: This test is historical for the isolate_assignments attribute, which +# was deprecated and has no effect today. This test ensures it still parses +# in SystemVerilog and Verilator control files for backward compatibility. + +test.scenarios('vlt') test.top_filename = "t/t_unopt_combo.v" -out_filename = test.obj_dir + "/V" + test.name + ".tree.json" - -test.compile(verilator_flags2=[ - "--no-json-edit-nums", "--stats", test.t_dir + - "/t_unopt_combo_isolate.vlt", "-fno-dfg", "-fno-lift-expr" -]) - -if test.vlt_all: - test.file_grep(test.stats, r'Optimizations, isolate_assignments blocks\s+4') - test.file_grep( - out_filename, - r'{"type":"VAR","name":"t.b",.*"loc":"\w,23:[^"]*",.*"origName":"b",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vfunc_t.file.get_31_16__0__Vfuncout",.*"loc":"\w,104:[^"]*",.*"origName":"__Vfunc_t__DOT__file__DOT__get_31_16__0__Vfuncout",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vfunc_t.file.get_31_16__0__t_crc",.*"loc":"\w,105:[^"]*",.*"origName":"__Vfunc_t__DOT__file__DOT__get_31_16__0__t_crc",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vtask_t.file.set_b_d__1__t_crc",.*"loc":"\w,115:[^"]*",.*"origName":"__Vtask_t__DOT__file__DOT__set_b_d__1__t_crc",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vtask_t.file.set_b_d__1__t_c",.*"loc":"\w,116:[^"]*",.*"origName":"__Vtask_t__DOT__file__DOT__set_b_d__1__t_c",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) +test.compile(verilator_flags2=[test.t_dir + "/t_unopt_combo_isolate.vlt"]) test.execute() diff --git a/test_regress/t/t_unoptflat_simple_2_bad.out b/test_regress/t/t_unoptflat_simple_2_bad.out index 8588ac55c..4c8793b04 100644 --- a/test_regress/t/t_unoptflat_simple_2_bad.out +++ b/test_regress/t/t_unoptflat_simple_2_bad.out @@ -10,5 +10,5 @@ t/t_unoptflat_simple_2.v:16:14: t.x, width 3, circular fanout 2, can split_var ... Candidates with the highest fanout: t/t_unoptflat_simple_2.v:16:14: t.x, width 3, circular fanout 2, can split_var - ... Suggest add /*verilator split_var*/ or /*verilator isolate_assignments*/ to appropriate variables above. + ... Suggest add /*verilator split_var*/ to appropriate variables above. %Error: Exiting due to diff --git a/test_regress/t/t_vlcov_covergroup.annotate.out b/test_regress/t/t_vlcov_covergroup.annotate.out index 436973cb7..b8616ad2d 100644 --- a/test_regress/t/t_vlcov_covergroup.annotate.out +++ b/test_regress/t/t_vlcov_covergroup.annotate.out @@ -14,9 +14,9 @@ module t; %000001 logic [1:0] addr; --000001 point: type=toggle comment=addr[0]:0->1 hier=top.t +-000000 point: type=toggle comment=addr[0]:0->1 hier=top.t -000000 point: type=toggle comment=addr[0]:1->0 hier=top.t --000000 point: type=toggle comment=addr[1]:0->1 hier=top.t +-000001 point: type=toggle comment=addr[1]:0->1 hier=top.t -000000 point: type=toggle comment=addr[1]:1->0 hier=top.t %000001 logic cmd; -000001 point: type=toggle comment=cmd:0->1 hier=top.t @@ -278,6 +278,50 @@ // cross: [a1, write] endgroup + // Cross plus an un-crossed coverpoint: get_inst_coverage must combine the converted + // (VlCoverpoint) coverpoint cp_solo with the legacy cross/crossed-coverpoint bins. + covergroup cg_mixed; +%000002 cp_addr: coverpoint addr {bins addr0 = {0}; bins addr1 = {1};} +-000002 point: type=covergroup comment= hier=cg_mixed.cp_addr.addr0 +-000002 point: type=covergroup comment= hier=cg_mixed.cp_addr.addr1 +%000002 cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} +-000002 point: type=covergroup comment= hier=cg_mixed.cp_cmd.read +-000002 point: type=covergroup comment= hier=cg_mixed.cp_cmd.write +%000002 cp_solo: coverpoint mode {bins normal = {0}; bins debug = {1};} // not crossed +-000002 point: type=covergroup comment= hier=cg_mixed.cp_solo.normal +-000002 point: type=covergroup comment= hier=cg_mixed.cp_solo.debug +%000001 ab: cross cp_addr, cp_cmd; +-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr0_x_read + // cross: [addr0, read] +-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr0_x_write + // cross: [addr0, write] +-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr1_x_read + // cross: [addr1, read] +-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr1_x_write + // cross: [addr1, write] + endgroup + + // Crossed (hence non-convertible) coverpoint that also has a default bin: exercises the + // legacy default-bin codegen path that converted coverpoints bypass. + covergroup cg_def_cross; +%000001 cp_a: coverpoint addr iff (mode) {bins a0 = {0}; bins a1 = {1}; bins ad = default;} +-000001 point: type=covergroup comment= hier=cg_def_cross.cp_a.a0 +-000000 point: type=covergroup comment= hier=cg_def_cross.cp_a.a1 +-000001 point: type=covergroup comment= hier=cg_def_cross.cp_a.ad +%000001 cp_c: coverpoint cmd {bins read = {0}; bins write = {1};} +-000001 point: type=covergroup comment= hier=cg_def_cross.cp_c.read +-000001 point: type=covergroup comment= hier=cg_def_cross.cp_c.write +%000001 axc: cross cp_a, cp_c; +-000001 point: type=covergroup comment= hier=cg_def_cross.axc.a0_x_read + // cross: [a0, read] +-000000 point: type=covergroup comment= hier=cg_def_cross.axc.a0_x_write + // cross: [a0, write] +-000000 point: type=covergroup comment= hier=cg_def_cross.axc.a1_x_read + // cross: [a1, read] +-000000 point: type=covergroup comment= hier=cg_def_cross.axc.a1_x_write + // cross: [a1, write] + endgroup + %000001 cg2 cg2_inst = new; -000001 point: type=line comment=block hier=top.t %000001 cg_ignore cg_ignore_inst = new; @@ -298,6 +342,10 @@ -000001 point: type=line comment=block hier=top.t %000001 cg_unnamed_cross cg_unnamed_cross_inst = new; -000001 point: type=line comment=block hier=top.t +%000001 cg_mixed cg_mixed_inst = new; +-000001 point: type=line comment=block hier=top.t +%000001 cg_def_cross cg_def_cross_inst = new; +-000001 point: type=line comment=block hier=top.t %000001 initial begin -000001 point: type=line comment=block hier=top.t @@ -641,6 +689,40 @@ -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t + // Sample cg_mixed: 10 bins total (cp_addr 2 + cp_cmd 2 + cp_solo 2 + cross ab 4) +%000001 addr = 0; cmd = 0; mode = 0; +-000001 point: type=line comment=block hier=top.t +%000001 cg_mixed_inst.sample(); // addr0, read, solo normal, ab(addr0_x_read) +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_mixed_inst.get_inst_coverage(), 40.0); // 4/10 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t +%000001 addr = 0; cmd = 1; mode = 1; +-000001 point: type=line comment=block hier=top.t +%000001 cg_mixed_inst.sample(); // addr0, write, solo debug, ab(addr0_x_write) +-000001 point: type=line comment=block hier=top.t +%000001 addr = 1; cmd = 0; mode = 0; +-000001 point: type=line comment=block hier=top.t +%000001 cg_mixed_inst.sample(); // addr1, read, ab(addr1_x_read) +-000001 point: type=line comment=block hier=top.t +%000001 addr = 1; cmd = 1; mode = 1; +-000001 point: type=line comment=block hier=top.t +%000001 cg_mixed_inst.sample(); // addr1, write, ab(addr1_x_write) +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_mixed_inst.get_inst_coverage(), 100.0); // 10/10 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t + + // Sample cg_def_cross (default bin in a crossed coverpoint, gated by iff) +%000001 mode = 1; +-000001 point: type=line comment=block hier=top.t +%000001 addr = 0; cmd = 0; cg_def_cross_inst.sample(); // a0, read +-000001 point: type=line comment=block hier=top.t +%000001 addr = 2; cmd = 1; cg_def_cross_inst.sample(); // ad (default), write +-000001 point: type=line comment=block hier=top.t + %000001 $write("*-* All Finished *-*\n"); -000001 point: type=line comment=block hier=top.t %000001 $finish; diff --git a/test_regress/t/t_vlt_syntax_bad.out b/test_regress/t/t_vlt_syntax_bad.out index f1ad706be..65a3941ea 100644 --- a/test_regress/t/t_vlt_syntax_bad.out +++ b/test_regress/t/t_vlt_syntax_bad.out @@ -2,28 +2,25 @@ 9 | public -module "t" @(posedge clk) | ^ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error: t/t_vlt_syntax_bad.vlt:11:1: isolate_assignments only applies to signals or functions/tasks - 11 | isolate_assignments -module "t" - | ^~~~~~~~~~~~~~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:13:1: Argument -match only supported for lint_off - 13 | tracing_off --file "*" -match "nothing" +%Error: t/t_vlt_syntax_bad.vlt:11:1: Argument -match only supported for lint_off + 11 | tracing_off --file "*" -match "nothing" | ^~~~~~~~~~~ +%Error: t/t_vlt_syntax_bad.vlt:13:1: Argument -scope only supported for tracing_on/off + 13 | lint_off --rule UNOPTFLAT -scope "top*" + | ^~~~~~~~ +%Error: t/t_vlt_syntax_bad.vlt:14:1: Argument -scope only supported for tracing_on/off_off + 14 | lint_off --rule UNOPTFLAT -scope "top*" -levels 0 + | ^~~~~~~~ %Error: t/t_vlt_syntax_bad.vlt:15:1: Argument -scope only supported for tracing_on/off - 15 | lint_off --rule UNOPTFLAT -scope "top*" - | ^~~~~~~~ + 15 | lint_on --rule UNOPTFLAT -scope "top*" + | ^~~~~~~ %Error: t/t_vlt_syntax_bad.vlt:16:1: Argument -scope only supported for tracing_on/off_off - 16 | lint_off --rule UNOPTFLAT -scope "top*" -levels 0 - | ^~~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:17:1: Argument -scope only supported for tracing_on/off - 17 | lint_on --rule UNOPTFLAT -scope "top*" + 16 | lint_on --rule UNOPTFLAT -scope "top*" -levels 0 | ^~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:18:1: Argument -scope only supported for tracing_on/off_off - 18 | lint_on --rule UNOPTFLAT -scope "top*" -levels 0 - | ^~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:20:1: forceable missing -module - 20 | forceable -module "" -var "net_*" +%Error: t/t_vlt_syntax_bad.vlt:18:1: forceable missing -module + 18 | forceable -module "" -var "net_*" | ^~~~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:22:1: missing -var - 22 | forceable -module "top" -var "" +%Error: t/t_vlt_syntax_bad.vlt:20:1: missing -var + 20 | forceable -module "top" -var "" | ^~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_vlt_syntax_bad.vlt b/test_regress/t/t_vlt_syntax_bad.vlt index 2ebcefa7c..0a5cdbce0 100644 --- a/test_regress/t/t_vlt_syntax_bad.vlt +++ b/test_regress/t/t_vlt_syntax_bad.vlt @@ -7,8 +7,6 @@ `verilator_config public -module "t" @(posedge clk) -// only signals/functions/tasks -isolate_assignments -module "t" // -match not supported tracing_off --file "*" -match "nothing" // -scope not supported diff --git a/test_regress/t/t_wrapper_context__top0.dat.out b/test_regress/t/t_wrapper_context__top0.dat.out index 07d4800da..ae165bca9 100644 --- a/test_regress/t/t_wrapper_context__top0.dat.out +++ b/test_regress/t/t_wrapper_context__top0.dat.out @@ -139,12 +139,12 @@ C 'ft/t_wrapper_context.vl21n3tlinepagev_line/topoblockS21-24,28,3 C 'ft/t_wrapper_context.vl35n3tlinepagev_line/topoblockS35htop0.top' 11 C 'ft/t_wrapper_context.vl36n5tbranchpagev_branch/topoifS36htop0.top' 1 C 'ft/t_wrapper_context.vl36n6tbranchpagev_branch/topoelseS37htop0.top' 10 -C 'ft/t_wrapper_context.vl39n3tlinepagev_line/topoblockS39-40htop0.top' 34 +C 'ft/t_wrapper_context.vl39n3tlinepagev_line/topoblockS39-40htop0.top' 13 C 'ft/t_wrapper_context.vl41n5tbranchpagev_branch/topoifS41htop0.top' 0 -C 'ft/t_wrapper_context.vl41n6tbranchpagev_branch/topoelseS47htop0.top' 34 +C 'ft/t_wrapper_context.vl41n6tbranchpagev_branch/topoelseS47htop0.top' 13 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==0) => 0htop0.top' 0 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==1 && stop==1) => 1htop0.top' 0 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo(stop==0) => 0htop0.top' 0 C 'ft/t_wrapper_context.vl42n8tlinepagev_line/topoelsehtop0.top' 0 C 'ft/t_wrapper_context.vl48n7tbranchpagev_branch/topoifS48-50htop0.top' 1 -C 'ft/t_wrapper_context.vl48n8tbranchpagev_branch/topoelsehtop0.top' 33 +C 'ft/t_wrapper_context.vl48n8tbranchpagev_branch/topoelsehtop0.top' 12 diff --git a/test_regress/t/t_wrapper_context__top1.dat.out b/test_regress/t/t_wrapper_context__top1.dat.out index 8ef66f3d0..073c8c4b5 100644 --- a/test_regress/t/t_wrapper_context__top1.dat.out +++ b/test_regress/t/t_wrapper_context__top1.dat.out @@ -139,12 +139,12 @@ C 'ft/t_wrapper_context.vl21n3tlinepagev_line/topoblockS21-24,28,3 C 'ft/t_wrapper_context.vl35n3tlinepagev_line/topoblockS35htop1.top' 6 C 'ft/t_wrapper_context.vl36n5tbranchpagev_branch/topoifS36htop1.top' 1 C 'ft/t_wrapper_context.vl36n6tbranchpagev_branch/topoelseS37htop1.top' 5 -C 'ft/t_wrapper_context.vl39n3tlinepagev_line/topoblockS39-40htop1.top' 19 -C 'ft/t_wrapper_context.vl41n5tbranchpagev_branch/topoifS41htop1.top' 19 +C 'ft/t_wrapper_context.vl39n3tlinepagev_line/topoblockS39-40htop1.top' 8 +C 'ft/t_wrapper_context.vl41n5tbranchpagev_branch/topoifS41htop1.top' 8 C 'ft/t_wrapper_context.vl41n6tbranchpagev_branch/topoelseS47htop1.top' 0 -C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==0) => 0htop1.top' 18 +C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==0) => 0htop1.top' 7 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==1 && stop==1) => 1htop1.top' 1 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo(stop==0) => 0htop1.top' 0 -C 'ft/t_wrapper_context.vl42n8tlinepagev_line/topoelsehtop1.top' 18 +C 'ft/t_wrapper_context.vl42n8tlinepagev_line/topoelsehtop1.top' 7 C 'ft/t_wrapper_context.vl48n7tbranchpagev_branch/topoifS48-50htop1.top' 0 C 'ft/t_wrapper_context.vl48n8tbranchpagev_branch/topoelsehtop1.top' 0