diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index ddcb6c657..553885c58 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -48,7 +48,7 @@ jobs: ls -lsha tree -L 3 pages - name: Upload pages artifact - uses: actions/upload-pages-artifact@v4 + uses: actions/upload-pages-artifact@v5 with: path: pages diff --git a/Changes b/Changes index 85e13e2c3..a63de82f7 100644 --- a/Changes +++ b/Changes @@ -65,9 +65,11 @@ Verilator 5.047 devel * Improve assignment-compatibility type check (#2843) (#5666) (#7052). [Pawel Kojma, Antmicro Ltd.] * Improve error message when variable used as data type (#7318). [Ryszard Rozak, Antmicro Ltd.] * Improve E_UNSUPPORTED warning messages (#7329). [Eunseo Song] +* Improve NFA-based multi-cycle SVA evaluation engine (#7430). [Yilou Wang] * Change array tracing to dump left index to right index (#7205). [Geza Lore, Testorrent USA, Inc.] * Change `--converge-limit` default to 10000 (#7209). * Remove DFG extract optimization pass (#7394). [Geza Lore, Testorrent USA, Inc.] +* Remove multi-threaded FST tracing (#7443). [Geza Lore, Testorrent USA, Inc.] * Optimize trace code for faster compiles on repeated types (#6707) (#6832). [Todd Strader] * Optimize size of trace declaration object code (#7150). [Szymon Gizler, Antmicro Ltd.] * Optimize function call return value temporaries (#7152). [Geza Lore, Testorrent USA, Inc.] @@ -80,11 +82,15 @@ Verilator 5.047 devel * Optimize more patterns in DfgPeephole (#7332). [Geza Lore, Testorrent USA, Inc.] * Optimize read references in DFG (#7354). [Geza Lore, Testorrent USA, Inc.] * Optimize DFG only once, after scoping (#7362). [Geza Lore, Testorrent USA, Inc.] -* Optimize more DFG peephole patterns (#7423). [Geza Lore, Testorrent USA, Inc.] +* Optimize more DFG peephole patterns (#7423) (#7452). [Geza Lore, Testorrent USA, Inc.] +* Optimize DfgBreakCycles IndependentBits analysis ordering (#7446). [Geza Lore, Testorrent USA, Inc.] +* Optimize select patterns in DfgPeephole. [Geza Lore, Testorrent USA, Inc.] +* Optimize temporary insertion in DfgPeephole. [Geza Lore, Testorrent USA, Inc.] +* Optimize arithmetic right shift (>>>) in DfgBreakCycles (#7447). [Geza Lore, Testorrent USA, Inc.] * Fix recursive default assignment for sub-arrays (#4589) (#7202). [Julian Carrier] * Fix virtual interface member trigger convergence (#5116) (#7323). [Yilou Wang] * Fix shift width mismatch in constraint solver SMT emission (#5420) (#7265). [Yilou Wang] -* Fix returning wrong type from static function in parameterized class (#5479) (#7387) (#7411) (#7418). [em2machine] +* Fix returning wrong type from static function in parameterized class (#5479) (#7387) (#7411) (#7418) (#7445) (#7450). [em2machine] * Fix randomize size+element queue constraints (#5582) (#7225). [Rahul Behl, Testorrent USA, Inc.] * Fix null assignment to virtual interfaces (#5974) (#5990). [Maxim Fonarev] * Fix typedef scope resolution for parameterized class aliases (#5977) (#7319). [Nick Brereton] @@ -151,8 +157,10 @@ Verilator 5.047 devel * Fix delete inside foreach skipping elements (#7407) (#7410) * Fix std::randomize in parameterized-derived class (#7409) (#7416). [Yilou Wang] * Fix virtual interface implied comparison with null (#7421). [Alex Solomatnikov] +* Fix uvm_hdl_release_and_read to release value and check success (#7425). [Christian Hecken, Heidelberg University] * Fix inline constraint on array-indexed randomize target (#7431) (#7434). [Yilou Wang] * Fix modification of members of object with const handle (#7433). [Kamil Danecki, Antmicro Ltd.] +* Fix `dist` under implication in constraints (#7440) (#7442). [Alex Solomatnikov] [Yilou Wang] Verilator 5.046 2026-02-28 diff --git a/bin/verilator b/bin/verilator index d1d40944e..78d1088f5 100755 --- a/bin/verilator +++ b/bin/verilator @@ -358,6 +358,7 @@ detailed descriptions of these arguments. --coverage Enable all coverage --coverage-expr Enable expression coverage --coverage-expr-max Maximum permutations allowed for an expression + --coverage-fsm Enable FSM state/arc coverage --coverage-line Enable line coverage --coverage-max-width Maximum array depth for coverage --coverage-toggle Enable toggle coverage @@ -379,6 +380,7 @@ detailed descriptions of these arguments. --dump- Enable dumping everything in source file --dump-defines Show preprocessor defines with -E --dump-dfg Enable dumping DfgGraphs to .dot files + --dump-dfg-patterns Enable dumping Dfg pattern statistics --dump-graph Enable dumping V3Graphs to .dot files --dump-inputs Enable dumping preprocessed input files --dump-tree Enable dumping Ast .tree files diff --git a/bin/verilator_coverage b/bin/verilator_coverage index 9f0b22e16..be48ff433 100755 --- a/bin/verilator_coverage +++ b/bin/verilator_coverage @@ -175,6 +175,7 @@ L. --annotate-points Annotates info from each coverage point. --filter-type Keep only records of given coverage type. --help Displays this message and version and exits. + --include-reset-arcs Include reset arcs in FSM arc summaries. --rank Compute relative importance of tests. --unlink With --write, unlink all inputs --version Displays program version and exits. diff --git a/ci/ci-install.bash b/ci/ci-install.bash index b268794ec..b3ae2e06d 100755 --- a/ci/ci-install.bash +++ b/ci/ci-install.bash @@ -40,12 +40,32 @@ if [ "$CI_OS_NAME" = "linux" ]; then echo "path-exclude /usr/share/info/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc fi -install-vcddiff() { - TMP_DIR="$(mktemp -d)" - git clone https://github.com/veripool/vcddiff "$TMP_DIR" - git -C "${TMP_DIR}" checkout 4db0d84a27e8f148b127e916fc71d650837955c5 - "$MAKE" -C "${TMP_DIR}" - sudo cp "${TMP_DIR}/vcddiff" /usr/local/bin +install-wavediff() { + source ci/docker/buildenv/wavetools.conf + local _base_url="https://github.com/hudson-trading/wavetools/releases/download/${WAVETOOLS_VERSION}" + local _platform + if [ "$CI_OS_NAME" = "linux" ]; then + _platform="linux-x86_64" + elif [ "$CI_OS_NAME" = "osx" ]; then + _platform="macos-arm64" + elif [ "$CI_OS_NAME" = "windows" ]; then + _platform="windows-x86_64" + else + echo "WARNING: No wavetools binary available for CI_OS_NAME=$CI_OS_NAME, skipping" + return 0 + fi + local _tmpdir + _tmpdir=$(mktemp -d) + local _archive="wavetools-${WAVETOOLS_VERSION}-${_platform}" + if [ "$CI_OS_NAME" = "windows" ]; then + wget -q -O "${_tmpdir}/${_archive}.zip" "${_base_url}/${_archive}.zip" + unzip -o "${_tmpdir}/${_archive}.zip" -d "${_tmpdir}" + else + wget -q -O "${_tmpdir}/${_archive}.tar.gz" "${_base_url}/${_archive}.tar.gz" + tar -xzf "${_tmpdir}/${_archive}.tar.gz" -C "${_tmpdir}" + fi + sudo cp "${_tmpdir}/${_archive}/wavediff" /usr/local/bin/wavediff + rm -rf "${_tmpdir}" } if [ "$CI_BUILD_STAGE_NAME" = "build" ]; then @@ -120,7 +140,7 @@ elif [ "$CI_BUILD_STAGE_NAME" = "test" ]; then fatal "Unknown CI_OS_NAME: '$CI_OS_NAME'" fi # Common installs - install-vcddiff + install-wavediff # Workaround -fsanitize=address crash sudo sysctl -w vm.mmap_rnd_bits=28 else diff --git a/ci/docker/buildenv/Dockerfile b/ci/docker/buildenv/Dockerfile index d205c08e2..27a58073a 100644 --- a/ci/docker/buildenv/Dockerfile +++ b/ci/docker/buildenv/Dockerfile @@ -53,10 +53,14 @@ RUN apt-get update \ WORKDIR /tmp -RUN git clone https://github.com/veripool/vcddiff.git && \ - make -C vcddiff && \ - cp -p vcddiff/vcddiff /usr/local/bin/vcddiff && \ - rm -rf vcddiff +COPY wavetools.conf /tmp/wavetools.conf +ARG WGET_EXTRA_ARGS= +RUN . /tmp/wavetools.conf && \ + wget -q ${WGET_EXTRA_ARGS} -O /tmp/wavetools.tar.gz \ + "https://github.com/hudson-trading/wavetools/releases/download/${WAVETOOLS_VERSION}/wavetools-${WAVETOOLS_VERSION}-linux-x86_64.tar.gz" && \ + tar -xzf /tmp/wavetools.tar.gz -C /tmp && \ + cp -p /tmp/wavetools-${WAVETOOLS_VERSION}-linux-x86_64/wavediff /usr/local/bin/wavediff && \ + rm -rf /tmp/wavetools.tar.gz /tmp/wavetools-* /tmp/wavetools.conf COPY build.sh /tmp/build.sh diff --git a/ci/docker/buildenv/wavetools.conf b/ci/docker/buildenv/wavetools.conf new file mode 100644 index 000000000..d327112c5 --- /dev/null +++ b/ci/docker/buildenv/wavetools.conf @@ -0,0 +1 @@ +WAVETOOLS_VERSION=v0.1.2 diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index 53241ebdc..3a41ed65f 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -58,6 +58,7 @@ Drew Ranck Drew Taussig Driss Hafdi Edgar E. Iglesias +Eric Mejdrich Eric Müller Eric Rippey Eunseo Song @@ -302,3 +303,4 @@ em2machine emmettifelts Àlex Torregrosa Ícaro Lima +Yogish Sekhar diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index dd16f6b19..23cdead2f 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -281,7 +281,8 @@ Summary: .. option:: --coverage Enables all forms of coverage, an alias for :vlopt:`--coverage-line` - :vlopt:`--coverage-toggle` :vlopt:`--coverage-expr` :vlopt:`--coverage-user`. + :vlopt:`--coverage-toggle` :vlopt:`--coverage-expr` :vlopt:`--coverage-fsm` + :vlopt:`--coverage-user`. .. option:: --coverage-expr @@ -293,6 +294,10 @@ Summary: covered for a given expression. Defaults to 32. Increasing may slow coverage simulations and make analyzing the results unwieldy. +.. option:: --coverage-fsm + + Enables native FSM state and arc coverage. See :ref:`FSM Coverage`. + .. option:: --coverage-line Enables basic block line coverage analysis. See :ref:`Line Coverage`. @@ -478,6 +483,10 @@ Summary: Rarely needed. Enable dumping DfgGraph .dot debug files with dumping level 3. +.. option:: --dump-dfg-patterns + + Rarely needed. Enable dumping DfgGraph pattern statistics. + .. option:: --dump-graph Rarely needed. Enable dumping V3Graph .dot debug files with dumping diff --git a/docs/guide/exe_verilator_coverage.rst b/docs/guide/exe_verilator_coverage.rst index 23bbd6b8f..2eac8e5fc 100644 --- a/docs/guide/exe_verilator_coverage.rst +++ b/docs/guide/exe_verilator_coverage.rst @@ -129,13 +129,20 @@ verilator_coverage Arguments .. option:: --filter-type Skips records of coverage types that matches with - Possible values are `toggle`, `line`, `branch`, `expr`, `user` and - a wildcard with `\*` or `?`. The default value is `\*`. + Possible values are `toggle`, `line`, `branch`, `expr`, `user`, + `fsm_state`, `fsm_arc` and a wildcard with `\*` or `?`. The default + value is `\*`. .. option:: --help Displays a help summary, the program version, and exits. +.. option:: --include-reset-arcs + + Includes FSM reset arcs in the printed summaries and annotated output. + By default, reset arcs are tracked but summarized separately from the + non-reset FSM arcs. + .. option:: --rank Prints an experimental report listing the relative importance of each diff --git a/docs/guide/simulating.rst b/docs/guide/simulating.rst index fd92bb1ba..5a4878dd9 100644 --- a/docs/guide/simulating.rst +++ b/docs/guide/simulating.rst @@ -185,6 +185,7 @@ SystemVerilog code coverage. With :vlopt:`--coverage`, Verilator enables all forms of coverage: - :ref:`User Coverage` +- :ref:`FSM Coverage` - :ref:`Line Coverage` - :ref:`Toggle Coverage` @@ -208,6 +209,47 @@ point under the coverage name "DefaultClock": DefaultClock: cover property (@(posedge clk) cyc==3); +.. _fsm coverage: + +FSM Coverage +------------ + +With :vlopt:`--coverage` or :vlopt:`--coverage-fsm`, Verilator can +instrument a conservative subset of single-process FSMs and report both +state coverage (`fsm_state`) and transition coverage (`fsm_arc`). + +This feature is currently experimental and might change in subsequent +releases. In particular, the native FSM coverage extraction heuristics, +:vlopt:`--coverage-fsm`, and the Verilator-specific FSM metacomments below +should be treated as subject to change while the interface settles. + +FSM extraction is intentionally narrow. The current implementation targets +clocked, enum-driven state machines that can be recovered directly from the +RTL. It does not claim broad support for two-process FSMs, one-hot +inference, helper-function next-state recovery, or deeply nested control +recovery. + +The following metacomments may be attached to the state variable to steer +the extracted coverage model: + +- ``/*verilator fsm_state*/`` forces the variable to be treated as + FSM state. +- ``/*verilator fsm_reset_arc*/`` marks reset transitions as + user-visible reset arcs instead of defaulting to a hidden reset-only + summary. +- ``/*verilator fsm_arc_include_cond*/`` keeps conditional branch + arcs that would otherwise be skipped by the conservative extractor. + +Reset transitions are included in the collected data either way. By +default, :command:`verilator_coverage` summarizes reset-only arcs rather +than printing them alongside non-reset arcs. Use +:option:`verilator_coverage --include-reset-arcs` to include +those arcs in the printed summary and annotated output. + +Annotated output produced by :command:`verilator_coverage --annotate` will +label FSM points with `fsm_state` and `fsm_arc`, and synthetic fallback +transitions with `SYNTHETIC DEFAULT ARC`. + .. _line coverage: diff --git a/docs/guide/verilating.rst b/docs/guide/verilating.rst index a91eb66b9..80de09f58 100644 --- a/docs/guide/verilating.rst +++ b/docs/guide/verilating.rst @@ -505,7 +505,7 @@ include directories and link to the SystemC libraries. Deprecated and has no effect. - Before Verialtor 5.048: Optional. Enable multithreaded FST trace; see + In versions before 5.048: Optional. Enable multithreaded FST trace; see :vlopt:`--trace-threads`. .. describe:: TRACE_VCD diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index 53c2958ab..0ba178158 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -306,7 +306,7 @@ List Of Warnings else array[address] <= data; - While this is supported in typical synthesizeable code (including the + While this is supported in typical synthesizable code (including the example above), some complicated cases are not supported. Namely: 1. If the above loop is inside a suspendable process or fork statement. @@ -837,6 +837,18 @@ List Of Warnings with a newline." +.. option:: FSMMULTI + + Warns that the same always block contains multiple enum-typed case + statements that look like FSM candidates for native FSM coverage when + :vlopt:`--coverage-fsm` or :vlopt:`--coverage` is enabled. + + Verilator's FSM coverage instruments only the first such candidate in + source order. Split the FSMs into separate always blocks, or explicitly + annotate the intended state variables and restructure the RTL for full + coverage of such multiple state machines. + + .. option:: FUNCTIMECTL Error that a function contains a time-controlling statement or call of a diff --git a/docs/internals.rst b/docs/internals.rst index 02f6ed0c0..519a52a94 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -1568,8 +1568,8 @@ For all tests to pass, you must install the following packages: - SystemC to compile the SystemC outputs, see https://systemc.org -- vcddiff to find differences in VCD outputs. See the readme at - https://github.com/veripool/vcddiff +- wavediff to find differences in waveform outputs. See the readme at + https://github.com/hudson-trading/wavetools - Cmake for build paths that use it. diff --git a/docs/spelling.txt b/docs/spelling.txt index 7e86ffdad..b25c946e1 100644 --- a/docs/spelling.txt +++ b/docs/spelling.txt @@ -2,6 +2,7 @@ ABCp Aadi Accellera Aditya +allocator Affe Aleksander Alexandre @@ -333,6 +334,7 @@ Muhlestein Multithreaded Multithreading Mykyta +NFA NOUNOPTFLAT NaN Nalbantis @@ -361,6 +363,7 @@ Olofsson Ondrej Oron Oyvind +output PLI Pakanati Palaniappan @@ -401,8 +404,10 @@ Ranjan Rapp Redhat Reitan +reentrant Renga Requin +reusability Riaz Rodas Rodionov @@ -837,6 +842,7 @@ gotFinish goto gprof gtkwave +hdl hdr hdzhangdoc hh @@ -1211,6 +1217,7 @@ upcasting urandom uselib utimes +uvm uwire uwires valgrind @@ -1257,6 +1264,7 @@ vpm vpp warmup wavealloca +wavediff waveforms whitespace widthed diff --git a/include/verilated_cov.cpp b/include/verilated_cov.cpp index 0f8eced98..1370c38ec 100644 --- a/include/verilated_cov.cpp +++ b/include/verilated_cov.cpp @@ -499,6 +499,26 @@ void VerilatedCovContext::_insertp(A(0), A(1), A(2), A(3), A(4), A(5), A(6), A(7 C(13), C(14), C(15), C(16), C(17), C(18), C(19), N(20), N(21), N(22), N(23), N(24), N(25), N(26), N(27), N(28), N(29)); } +// Backward compatibility for mixed inserts with integer-valued +// lineno/column pairs and C-string-valued metadata pairs. +void VerilatedCovContext::_insertp(A(0), A(1), K(2), int val2, K(3), int val3, A(4), A(5), A(6), + A(7)) VL_MT_SAFE { + const std::string val2str = std::to_string(val2); + const std::string val3str = std::to_string(val3); + _insertp(C(0), C(1), key2, val2str.c_str(), key3, val3str.c_str(), C(4), C(5), C(6), C(7), + N(8), N(9), N(10), N(11), N(12), N(13), N(14), N(15), N(16), N(17), N(18), N(19), + N(20), N(21), N(22), N(23), N(24), N(25), N(26), N(27), N(28), N(29)); +} +// Backward compatibility for mixed inserts with integer-valued +// lineno/column pairs and additional FSM metadata pairs. +void VerilatedCovContext::_insertp(A(0), A(1), K(2), int val2, K(3), int val3, A(4), A(5), A(6), + A(7), A(8), A(9), A(10), A(11)) VL_MT_SAFE { + const std::string val2str = std::to_string(val2); + const std::string val3str = std::to_string(val3); + _insertp(C(0), C(1), key2, val2str.c_str(), key3, val3str.c_str(), C(4), C(5), C(6), C(7), + C(8), C(9), C(10), C(11), N(12), N(13), N(14), N(15), N(16), N(17), N(18), N(19), + N(20), N(21), N(22), N(23), N(24), N(25), N(26), N(27), N(28), N(29)); +} // Backward compatibility for Verilator void VerilatedCovContext::_insertp(A(0), A(1), K(2), int val2, K(3), int val3, K(4), const std::string& val4, A(5), A(6), A(7)) VL_MT_SAFE { diff --git a/include/verilated_cov.h b/include/verilated_cov.h index a6cbd2d4e..e1dcad8d2 100644 --- a/include/verilated_cov.h +++ b/include/verilated_cov.h @@ -191,6 +191,13 @@ public: void _insertp(A(0), A(1), A(2), A(3), A(4), A(5), A(6), A(7), A(8), A(9), A(10), A(11), A(12), A(13), A(14), A(15), A(16), A(17), A(18), A(19), A(20), D(21), D(22), D(23), D(24), D(25), D(26), D(27), D(28), D(29)) VL_MT_SAFE; + // Backward compatibility for mixed inserts with integer-valued + // lineno/column pairs and C-string-valued metadata pairs. + void _insertp(A(0), A(1), K(2), int val2, K(3), int val3, A(4), A(5), A(6), A(7)) VL_MT_SAFE; + // Backward compatibility for mixed inserts with integer-valued + // lineno/column pairs and additional FSM metadata pairs. + void _insertp(A(0), A(1), K(2), int val2, K(3), int val3, A(4), A(5), A(6), A(7), A(8), A(9), + A(10), A(11)) VL_MT_SAFE; // Backward compatibility for Verilator void _insertp(A(0), A(1), K(2), int val2, K(3), int val3, K(4), const std::string& val4, A(5), A(6), A(7)) VL_MT_SAFE; diff --git a/include/verilated_cov_key.h b/include/verilated_cov_key.h index cb0a2efa9..fd7947aa2 100644 --- a/include/verilated_cov_key.h +++ b/include/verilated_cov_key.h @@ -40,6 +40,10 @@ VLCOVGEN_ITEM("'name':'thresh', 'short':'s', 'group':1, 'default':None, 'd VLCOVGEN_ITEM("'name':'type', 'short':'t', 'group':1, 'default':'', 'descr':'Type of coverage (block, line, fsm, etc)'") // Bin attributes VLCOVGEN_ITEM("'name':'comment', 'short':'o', 'group':0, 'default':'', 'descr':'Textual description for the item'") +VLCOVGEN_ITEM("'name':'fsm_from', 'short':'Ff', 'group':0, 'default':'', 'descr':'FSM source state name for structured FSM coverage points'") +VLCOVGEN_ITEM("'name':'fsm_tag', 'short':'Fg', 'group':0, 'default':'', 'descr':'FSM point tag such as reset, reset_include, or default'") +VLCOVGEN_ITEM("'name':'fsm_to', 'short':'Ft', 'group':0, 'default':'', 'descr':'FSM destination state name for structured FSM coverage points'") +VLCOVGEN_ITEM("'name':'fsm_var', 'short':'Fv', 'group':0, 'default':'', 'descr':'FSM state variable name for structured FSM coverage points'") VLCOVGEN_ITEM("'name':'hier', 'short':'h', 'group':0, 'default':'', 'descr':'Hierarchy path name for the item'") VLCOVGEN_ITEM("'name':'lineno', 'short':'l', 'group':0, 'default':0, 'descr':'Line number for the item'") VLCOVGEN_ITEM("'name':'weight', 'short':'w', 'group':0, 'default':None, 'descr':'For totaling items, weight of this item'") @@ -49,6 +53,10 @@ VLCOVGEN_ITEM("'name':'weight', 'short':'w', 'group':0, 'default':None, 'd #define VL_CIK_COLUMN "n" #define VL_CIK_COMMENT "o" #define VL_CIK_FILENAME "f" +#define VL_CIK_FSM_FROM "Ff" +#define VL_CIK_FSM_TAG "Fg" +#define VL_CIK_FSM_TO "Ft" +#define VL_CIK_FSM_VAR "Fv" #define VL_CIK_HIER "h" #define VL_CIK_LINENO "l" #define VL_CIK_LINESCOV "S" @@ -70,6 +78,10 @@ public: if (key == "column") return VL_CIK_COLUMN; if (key == "comment") return VL_CIK_COMMENT; if (key == "filename") return VL_CIK_FILENAME; + if (key == "fsm_from") return VL_CIK_FSM_FROM; + if (key == "fsm_tag") return VL_CIK_FSM_TAG; + if (key == "fsm_to") return VL_CIK_FSM_TO; + if (key == "fsm_var") return VL_CIK_FSM_VAR; if (key == "hier") return VL_CIK_HIER; if (key == "lineno") return VL_CIK_LINENO; if (key == "linescov") return VL_CIK_LINESCOV; diff --git a/include/verilated_force.h b/include/verilated_force.h new file mode 100644 index 000000000..0a65a8b35 --- /dev/null +++ b/include/verilated_force.h @@ -0,0 +1,258 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// +// Code available from: https://verilator.org +// +// Copyright 2026-2026 by Wilson Snyder. 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 +// +//************************************************************************* +/// +/// \file +/// \brief Verilator: Runtime support for force/release statements +/// +/// This file provides runtime data structures for efficient dynamic +/// resolution of force/release statements. A sorted list of active +/// forces is maintained that can be efficiently queried and modified +/// at runtime. +/// +//************************************************************************* + +#ifndef VERILATOR_VERILATED_FORCE_H_ +#define VERILATOR_VERILATED_FORCE_H_ + +#include "verilatedos.h" + +#include +#include +#include +#include +#include + +template +using VlForceBaseType = typename std::remove_cv::type>::type; + +// VlForceRead - Helper functions to read a forced value +// +// These functions combine original value with forced values based on +// VlForceVec entries. +// This achieves O(k) complexity where k = number of active forces. + +template +struct VlForceTypeInfo final { + using Type = VlForceBaseType; + static constexpr bool bitwise + = std::is_integral::value || std::is_enum::value || VlIsVlWide::value; + static constexpr bool unpackedArray = false; +}; + +template +struct VlForceArrayIndexer final { + static constexpr std::size_t size = 1; + + static T& elem(T& value, std::size_t) { return value; } +}; + +template +struct VlForceArrayIndexer> final { + static constexpr std::size_t size = N * VlForceArrayIndexer::size; + + static auto& elem(VlUnpacked& array, std::size_t index) { + constexpr std::size_t subSize = VlForceArrayIndexer::size; + return VlForceArrayIndexer::elem(array[index / subSize], index % subSize); + } +}; + +template +struct VlForceTypeInfo> final { + using Type = VlUnpacked; + static constexpr bool bitwise = false; + static constexpr bool unpackedArray = true; +}; + +template ::value> +struct VlForceStorageTypeOf final { + using type = typename std::make_unsigned::type; +}; + +template +struct VlForceStorageTypeOf final { + using type = typename std::make_unsigned::type>::type; +}; + +template +using VlForceStorageType = typename VlForceStorageTypeOf>::type; + +//============================================================================= +// VlForceVec - Vector of active force entries for a signal +// +// This class maintains a sorted vector of non-overlapping force entries. +// When a new force is added, it removes or trims existing entries that +// overlap with the new range. +// +// The generated code will: +// 1. Use addForce/release to update the active forces +// 2. Call a generated read function that iterates entries and evaluates RHS + +class VlForceVec final { +private: + struct Entry final { + int m_lsb; // Inclusive lower bit + int m_msb; // Inclusive upper bit + int m_rhsLsb; // Destination index that maps to RHS index 0 + const void* m_rhsDatap; // Pointer to RHS storage + + bool operator<(const Entry& other) const { return m_msb < other.m_msb; } + }; + + std::vector m_entries; // Sorted by msb, non-overlapping + + std::vector::iterator trimEntries(int lsb, int msb) { + auto it = std::lower_bound(m_entries.begin(), m_entries.end(), lsb, + [](const Entry& e, int bit) { return e.m_msb < bit; }); + while (it != m_entries.end() && it->m_lsb <= msb) { + if (it->m_lsb < lsb && it->m_msb > msb) { + const Entry right{msb + 1, it->m_msb, it->m_rhsLsb, it->m_rhsDatap}; + it->m_msb = lsb - 1; + return m_entries.insert(++it, right); + } + if (it->m_lsb < lsb) { + it->m_msb = lsb - 1; + ++it; + continue; + } + if (it->m_msb > msb) { + it->m_lsb = msb + 1; + return it; + } + it = m_entries.erase(it); + } + return it; + } + + static QData extractRhsChunk(const Entry& entry, int rhsLsb, int width) { + assert(width > 0 && width <= VL_QUADSIZE); + assert(rhsLsb >= 0); + + const QData mask = static_cast(VL_MASK_Q(width)); + const int rhsWidth = entry.m_msb - entry.m_rhsLsb + 1; + if (rhsWidth <= VL_QUADSIZE) { + const QData rhsVal = static_cast(*static_cast(entry.m_rhsDatap)); + return (rhsVal >> rhsLsb) & mask; + } + + const EData* const rhswp = static_cast(entry.m_rhsDatap); + return VL_SEL_QWII(rhsWidth, rhswp, rhsLsb, width) & mask; + } + + template + static T applyBits(T cur, const Entry& entry, int lsb, int width, int rhsLsb) { + const T lowMask = static_cast(VL_MASK_Q(width)); + const T mask = static_cast(lowMask << lsb); + const T rhsBits = static_cast( + (static_cast(extractRhsChunk(entry, rhsLsb, width)) & lowMask) << lsb); + return static_cast((cur & ~mask) | (rhsBits & mask)); + } + + template + static typename std::enable_if::value, T>::type applyEntry(T result, + const Entry& entry) { + EData* const reswp = result.data(); + const int lword = VL_BITWORD_E(entry.m_lsb); + const int hword = VL_BITWORD_E(entry.m_msb); + for (int word = lword; word <= hword; ++word) { + const int wordLsb = word * VL_EDATASIZE; + const int segLsb = std::max(entry.m_lsb, wordLsb); + const int segMsb = std::min(entry.m_msb, wordLsb + VL_EDATASIZE - 1); + const int segWidth = segMsb - segLsb + 1; + const int bitOffset = segLsb - wordLsb; + const int rhsLsb = segLsb - entry.m_rhsLsb; + reswp[word] = applyBits(reswp[word], entry, bitOffset, segWidth, rhsLsb); + } + return result; + } + + template + static typename std::enable_if::value && VlForceTypeInfo::bitwise, T>::type + applyEntry(T result, const Entry& entry) { + using U = VlForceStorageType; + const int width = entry.m_msb - entry.m_lsb + 1; + const int bits = static_cast(sizeof(U) * 8); + const int rhsLsb = entry.m_lsb - entry.m_rhsLsb; + const QData rhsChunk = extractRhsChunk(entry, rhsLsb, width); + if (width >= bits) return static_cast(static_cast(rhsChunk)); + return static_cast( + applyBits(static_cast(result), entry, entry.m_lsb, width, rhsLsb)); + } + + template + static typename std::enable_if::bitwise, T>::type + applyEntry(T result, const Entry& entry) { + static_cast(result); + return *static_cast*>(entry.m_rhsDatap); + } + +public: + VlForceVec() = default; + + template + T read(T val) const { + if VL_CONSTEXPR_CXX17 (VlForceTypeInfo::unpackedArray) { + // Handling the case of a nested flattened array using recursion + using ElemRef + = decltype(VlForceArrayIndexer::elem(val, static_cast(0))); + using Elem = VlForceBaseType; + const int total = static_cast(VlForceArrayIndexer::size); + for (const auto& entry : m_entries) { + const Elem* const rhsBasep = static_cast(entry.m_rhsDatap); + const int startIdx = entry.m_lsb; + const int endIdx = entry.m_msb; + for (int idx = startIdx; idx <= endIdx; idx++) { + const int rhsIndex = idx - entry.m_rhsLsb; + const std::size_t uidx = static_cast(idx); + VlForceArrayIndexer::elem(val, uidx) = rhsBasep[rhsIndex]; + } + } + return val; + } + + for (const auto& entry : m_entries) { val = applyEntry(val, entry); } + return val; + } + + template + T readIndex(T origVal, int index) const { + if (m_entries.empty()) return origVal; + + const auto it = std::lower_bound(m_entries.begin(), m_entries.end(), index, + [](const Entry& e, int idx) { return e.m_msb < idx; }); + if (it != m_entries.end() && it->m_lsb <= index) { + const int rhsIndex = index - it->m_rhsLsb; + const T* const rhsBasep = static_cast(it->m_rhsDatap); + return rhsBasep[rhsIndex]; + } + return origVal; + } + + void addForce(int lsb, int msb, const void* rhsDatap, int rhsLsb) { + assert(lsb <= msb); + assert(rhsDatap); + assert(rhsLsb <= lsb); + + auto it = trimEntries(lsb, msb); + m_entries.insert(it, {lsb, msb, rhsLsb, rhsDatap}); + } + + void release(int lsb, int msb) { + assert(lsb <= msb); + trimEntries(lsb, msb); + } + + void touch() {} +}; + +#endif // guard diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 00d6d9b92..71f416129 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -100,6 +100,7 @@ set(HEADERS V3File.h V3FileLine.h V3Force.h + V3FsmDetect.h V3Fork.h V3FuncOpt.h V3FunctionTraits.h @@ -277,6 +278,7 @@ set(COMMON_SOURCES V3File.cpp V3FileLine.cpp V3Force.cpp + V3FsmDetect.cpp V3Fork.cpp V3FuncOpt.cpp V3Gate.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index a3441cae2..5cf0d2eaa 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -280,6 +280,7 @@ RAW_OBJS_PCH_ASTNOMT = \ V3ExecGraph.o \ V3Expand.o \ V3Force.o \ + V3FsmDetect.o \ V3Fork.o \ V3Gate.o \ V3HierBlock.o \ diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index 5105fe73a..25962098e 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -311,6 +311,9 @@ public: // VAR_BASE, // V3LinkResolve creates for AstPreSel, V3LinkParam removes VAR_FORCEABLE, // V3LinkParse moves to AstVar::isForceable + VAR_FSM_ARC_INCLUDE_COND, // V3LinkParse moves to AstVar::attrFsmArcInclCond + VAR_FSM_RESET_ARC, // V3LinkParse moves to AstVar::attrFsmResetArc + VAR_FSM_STATE, // V3LinkParse moves to AstVar::attrFsmState VAR_PORT_DTYPE, // V3LinkDot for V3Width to check port dtype VAR_PUBLIC, // V3LinkParse moves to AstVar::sigPublic VAR_PUBLIC_FLAT, // V3LinkParse moves to AstVar::sigPublic @@ -336,10 +339,10 @@ public: "ENUM_NEXT", "ENUM_PREV", "ENUM_NAME", "ENUM_VALID", "FUNC_ARG_PROTO", "FUNC_RETURN_PROTO", "TYPEID", "TYPENAME", - "VAR_BASE", "VAR_FORCEABLE", "VAR_PORT_DTYPE", "VAR_PUBLIC", - "VAR_PUBLIC_FLAT", "VAR_PUBLIC_FLAT_RD", "VAR_PUBLIC_FLAT_RW", - "VAR_ISOLATE_ASSIGNMENTS", "VAR_SC_BIGUINT", "VAR_SC_BV", "VAR_SFORMAT", - "VAR_SPLIT_VAR" + "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_SC_BIGUINT", "VAR_SC_BV", "VAR_SFORMAT", "VAR_SPLIT_VAR" }; // clang-format on return names[m_e]; @@ -809,6 +812,11 @@ public: EVENT_FIRE, EVENT_IS_FIRED, EVENT_IS_TRIGGERED, + FORCE_ADD, + FORCE_READ, + FORCE_READ_INDEX, + FORCE_RELEASE, + FORCE_TOUCH, FORK_DONE, FORK_INIT, FORK_JOIN, @@ -955,6 +963,11 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) { {EVENT_FIRE, "fire", false}, \ {EVENT_IS_FIRED, "isFired", true}, \ {EVENT_IS_TRIGGERED, "isTriggered", true}, \ + {FORCE_ADD, "addForce", false}, \ + {FORCE_READ, "read", true}, \ + {FORCE_READ_INDEX, "readIndex", true}, \ + {FORCE_RELEASE, "release", false}, \ + {FORCE_TOUCH, "touch", false}, \ {FORK_DONE, "done", false}, \ {FORK_INIT, "init", false}, \ {FORK_JOIN, "join", false}, \ diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index f13167e22..fa6fa02d6 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -2889,6 +2889,7 @@ public: }; class AstConcat final : public AstNodeBiop { // If you're looking for {#{}}, see AstReplicate + // @astgen makeDfgVertex public: AstConcat(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_Concat(fl, lhsp, rhsp) { @@ -2938,6 +2939,7 @@ public: bool stringFlavor() const override { return true; } }; class AstDiv final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstDiv(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_Div(fl, lhsp, rhsp) { @@ -2980,6 +2982,7 @@ public: bool doubleFlavor() const override { return true; } }; class AstDivS final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstDivS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_DivS(fl, lhsp, rhsp) { @@ -3003,6 +3006,7 @@ public: }; class AstEqWild final : public AstNodeBiop { // Note wildcard operator rhs differs from lhs + // @astgen makeDfgVertex public: AstEqWild(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_EqWild(fl, lhsp, rhsp) { @@ -3109,6 +3113,7 @@ public: bool sizeMattersRhs() const override { return false; } }; class AstGt final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstGt(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_Gt(fl, lhsp, rhsp) { @@ -3171,6 +3176,7 @@ public: bool stringFlavor() const override { return true; } }; class AstGtS final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstGtS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_GtS(fl, lhsp, rhsp) { @@ -3192,6 +3198,7 @@ public: bool signedFlavor() const override { return true; } }; class AstGte final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstGte(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_Gte(fl, lhsp, rhsp) { @@ -3254,6 +3261,7 @@ public: bool stringFlavor() const override { return true; } }; class AstGteS final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstGteS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_GteS(fl, lhsp, rhsp) { @@ -3275,6 +3283,7 @@ public: bool signedFlavor() const override { return true; } }; class AstLogAnd final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstLogAnd(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_LogAnd(fl, lhsp, rhsp) { @@ -3296,6 +3305,7 @@ public: int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; } }; class AstLogIf final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstLogIf(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_LogIf(fl, lhsp, rhsp) { @@ -3317,6 +3327,7 @@ public: int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; } }; class AstLogOr final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstLogOr(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_LogOr(fl, lhsp, rhsp) { @@ -3338,6 +3349,7 @@ public: int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; } }; class AstLt final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstLt(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_Lt(fl, lhsp, rhsp) { @@ -3400,6 +3412,7 @@ public: bool stringFlavor() const override { return true; } }; class AstLtS final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstLtS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_LtS(fl, lhsp, rhsp) { @@ -3421,6 +3434,7 @@ public: bool signedFlavor() const override { return true; } }; class AstLte final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstLte(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_Lte(fl, lhsp, rhsp) { @@ -3483,6 +3497,7 @@ public: bool stringFlavor() const override { return true; } }; class AstLteS final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstLteS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_LteS(fl, lhsp, rhsp) { @@ -3504,6 +3519,7 @@ public: bool signedFlavor() const override { return true; } }; class AstModDiv final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstModDiv(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_ModDiv(fl, lhsp, rhsp) { @@ -3525,6 +3541,7 @@ public: int instrCount() const override { return widthInstrs() * INSTR_COUNT_INT_DIV; } }; class AstModDivS final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstModDivS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_ModDivS(fl, lhsp, rhsp) { @@ -3547,6 +3564,7 @@ public: bool signedFlavor() const override { return true; } }; class AstNeqWild final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstNeqWild(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_NeqWild(fl, lhsp, rhsp) { @@ -3566,6 +3584,7 @@ public: bool sizeMattersRhs() const override { return false; } }; class AstPow final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstPow(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_Pow(fl, lhsp, rhsp) { @@ -3606,6 +3625,7 @@ public: bool doubleFlavor() const override { return true; } }; class AstPowSS final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstPowSS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_PowSS(fl, lhsp, rhsp) { @@ -3627,6 +3647,7 @@ public: bool signedFlavor() const override { return true; } }; class AstPowSU final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstPowSU(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_PowSU(fl, lhsp, rhsp) { @@ -3648,6 +3669,7 @@ public: bool signedFlavor() const override { return true; } }; class AstPowUS final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstPowUS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_PowUS(fl, lhsp, rhsp) { @@ -3673,6 +3695,7 @@ class AstReplicate final : public AstNodeBiop { // Verilog {rhs{lhs}} - Note rhsp() is the replicate value, not the lhsp() // @astgen alias op1 := srcp // @astgen alias op2 := countp + // @astgen makeDfgVertex public: AstReplicate(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_Replicate(fl, lhsp, rhsp) { @@ -3900,6 +3923,7 @@ public: void declElWidth(int flag) { m_declElWidth = flag; } }; class AstShiftL final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstShiftL(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp, int setwidth = 0) : ASTGEN_SUPER_ShiftL(fl, lhsp, rhsp) { @@ -3943,6 +3967,7 @@ public: bool sizeMattersRhs() const override { return false; } }; class AstShiftR final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstShiftR(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp, int setwidth = 0) : ASTGEN_SUPER_ShiftR(fl, lhsp, rhsp) { @@ -3990,6 +4015,7 @@ public: class AstShiftRS final : public AstNodeBiop { // Shift right with sign extension, >>> operator // Output data type's width determines which bit is used for sign extension + // @astgen makeDfgVertex public: AstShiftRS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp, int setwidth = 0) : ASTGEN_SUPER_ShiftRS(fl, lhsp, rhsp) { @@ -4036,6 +4062,7 @@ public: bool signedFlavor() const override { return true; } }; class AstSub final : public AstNodeBiop { + // @astgen makeDfgVertex public: AstSub(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_Sub(fl, lhsp, rhsp) { @@ -4101,6 +4128,7 @@ public: // === AstNodeBiCom === class AstEq final : public AstNodeBiCom { + // @astgen makeDfgVertex public: AstEq(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_Eq(fl, lhsp, rhsp) { @@ -4123,6 +4151,7 @@ public: bool sizeMattersRhs() const override { return false; } }; class AstEqCase final : public AstNodeBiCom { + // @astgen makeDfgVertex public: AstEqCase(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_EqCase(fl, lhsp, rhsp) { @@ -4203,6 +4232,7 @@ public: int instrCount() const override { return INSTR_COUNT_STR; } }; class AstLogEq final : public AstNodeBiCom { + // @astgen makeDfgVertex public: AstLogEq(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_LogEq(fl, lhsp, rhsp) { @@ -4224,6 +4254,7 @@ public: int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; } }; class AstNeq final : public AstNodeBiCom { + // @astgen makeDfgVertex public: AstNeq(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_Neq(fl, lhsp, rhsp) { @@ -4245,6 +4276,7 @@ public: bool sizeMattersRhs() const override { return false; } }; class AstNeqCase final : public AstNodeBiCom { + // @astgen makeDfgVertex public: AstNeqCase(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_NeqCase(fl, lhsp, rhsp) { @@ -4327,6 +4359,7 @@ public: // === AstNodeBiComAsv === class AstAdd final : public AstNodeBiComAsv { + // @astgen makeDfgVertex public: AstAdd(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_Add(fl, lhsp, rhsp) { @@ -4368,6 +4401,7 @@ public: bool doubleFlavor() const override { return true; } }; class AstAnd final : public AstNodeBiComAsv { + // @astgen makeDfgVertex public: AstAnd(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_And(fl, lhsp, rhsp) { @@ -4389,6 +4423,7 @@ public: const char* widthMismatch() const override VL_MT_STABLE; }; class AstMul final : public AstNodeBiComAsv { + // @astgen makeDfgVertex public: AstMul(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_Mul(fl, lhsp, rhsp) { @@ -4431,6 +4466,7 @@ public: bool doubleFlavor() const override { return true; } }; class AstMulS final : public AstNodeBiComAsv { + // @astgen makeDfgVertex public: AstMulS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_MulS(fl, lhsp, rhsp) { @@ -4454,6 +4490,7 @@ public: bool signedFlavor() const override { return true; } }; class AstOr final : public AstNodeBiComAsv { + // @astgen makeDfgVertex public: AstOr(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_Or(fl, lhsp, rhsp) { @@ -4475,6 +4512,7 @@ public: const char* widthMismatch() const override VL_MT_STABLE; }; class AstXor final : public AstNodeBiComAsv { + // @astgen makeDfgVertex public: AstXor(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_Xor(fl, lhsp, rhsp) { @@ -4532,6 +4570,7 @@ public: // === AstNodeSel === class AstArraySel final : public AstNodeSel { + // @astgen makeDfgVertex void init(const AstNode* fromp) { if (fromp && VN_IS(fromp->dtypep()->skipRefp(), NodeArrayDType)) { // Strip off array to find what array references @@ -4644,6 +4683,7 @@ public: // === AstNodeStream === class AstStreamL final : public AstNodeStream { // Verilog {rhs{lhs}} - Note rhsp() is the slice size, not the lhsp() + // @astgen makeDfgVertex public: AstStreamL(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_StreamL(fl, lhsp, rhsp) {} @@ -4662,6 +4702,7 @@ public: }; class AstStreamR final : public AstNodeStream { // Verilog {rhs{lhs}} - Note rhsp() is the slice size, not the lhsp() + // @astgen makeDfgVertex public: AstStreamR(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) : ASTGEN_SUPER_StreamR(fl, lhsp, rhsp) {} @@ -4950,6 +4991,7 @@ class AstCond final : public AstNodeTriop { // @astgen alias op1 := condp // @astgen alias op2 := thenp // @astgen alias op3 := elsep + // @astgen makeDfgVertex public: AstCond(FileLine* fl, AstNodeExpr* condp, AstNodeExpr* thenp, AstNodeExpr* elsep); ASTGEN_MEMBERS_AstCond; @@ -5298,6 +5340,7 @@ public: }; class AstCountOnes final : public AstNodeUniop { // Number of bits set in vector + // @astgen makeDfgVertex public: AstCountOnes(FileLine* fl, AstNodeExpr* lhsp) : ASTGEN_SUPER_CountOnes(fl, lhsp) {} @@ -5329,6 +5372,7 @@ public: }; class AstExtend final : public AstNodeUniop { // Expand a value into a wider entity by 0 extension. Width is implied from nodep->width() + // @astgen makeDfgVertex public: AstExtend(FileLine* fl, AstNodeExpr* lhsp) : ASTGEN_SUPER_Extend(fl, lhsp) {} @@ -5352,6 +5396,7 @@ public: }; class AstExtendS final : public AstNodeUniop { // Expand a value into a wider entity by sign extension. Width is implied from nodep->width() + // @astgen makeDfgVertex public: AstExtendS(FileLine* fl, AstNodeExpr* lhsp) : ASTGEN_SUPER_ExtendS(fl, lhsp) {} @@ -5498,6 +5543,7 @@ public: bool sizeMattersLhs() const override { return false; } }; class AstLogNot final : public AstNodeUniop { + // @astgen makeDfgVertex public: AstLogNot(FileLine* fl, AstNodeExpr* lhsp) : ASTGEN_SUPER_LogNot(fl, lhsp) { @@ -5529,6 +5575,7 @@ public: bool sizeMattersLhs() const override { return false; } }; class AstNegate final : public AstNodeUniop { + // @astgen makeDfgVertex public: AstNegate(FileLine* fl, AstNodeExpr* lhsp) : ASTGEN_SUPER_Negate(fl, lhsp) { @@ -5562,6 +5609,7 @@ public: bool doubleFlavor() const override { return true; } }; class AstNot final : public AstNodeUniop { + // @astgen makeDfgVertex public: AstNot(FileLine* fl, AstNodeExpr* lhsp) : ASTGEN_SUPER_Not(fl, lhsp) { @@ -5683,6 +5731,7 @@ public: bool isSystemFunc() const override { return true; } }; class AstRedAnd final : public AstNodeUniop { + // @astgen makeDfgVertex public: AstRedAnd(FileLine* fl, AstNodeExpr* lhsp) : ASTGEN_SUPER_RedAnd(fl, lhsp) { @@ -5697,6 +5746,7 @@ public: bool sizeMattersLhs() const override { return false; } }; class AstRedOr final : public AstNodeUniop { + // @astgen makeDfgVertex public: AstRedOr(FileLine* fl, AstNodeExpr* lhsp) : ASTGEN_SUPER_RedOr(fl, lhsp) { @@ -5711,6 +5761,7 @@ public: bool sizeMattersLhs() const override { return false; } }; class AstRedXor final : public AstNodeUniop { + // @astgen makeDfgVertex public: AstRedXor(FileLine* fl, AstNodeExpr* lhsp) : ASTGEN_SUPER_RedXor(fl, lhsp) { diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 1972577f0..32a38ca0b 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -656,7 +656,8 @@ class AstCell final : public AstNode { // A instantiation cell or interface call (don't know which until link) // @astgen op1 := pinsp : List[AstPin] // List of port assignments // @astgen op2 := paramsp : List[AstPin] // List of parameter assignments - // @astgen op3 := rangep : Optional[AstRange] // Range for arrayed instances + // @astgen op3 := rangep : List[AstRange] // Range(s) for arrayed instances; multi-dim chains + // via nextp() // @astgen op4 := intfRefsp : List[AstIntfRef] // List of interface references, for tracing // // @astgen ptr := m_modp : Optional[AstNodeModule] // [AfterLink] Pointer to module instanced @@ -680,7 +681,7 @@ public: , m_trace{true} { addPinsp(pinsp); addParamsp(paramsp); - this->rangep(rangep); + addRangep(rangep); } ASTGEN_MEMBERS_AstCell; // No cloneRelink, we presume cloneee's want the same module linkages @@ -1933,6 +1934,9 @@ class AstVar final : public AstNode { 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 + bool m_attrFsmResetArc : 1; // declared with fsm_reset_arc metacomment + 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_isConst : 1; // Table contains constant data @@ -1991,6 +1995,9 @@ class AstVar final : public AstNode { m_attrIsolateAssign = false; m_attrSFormat = false; m_attrSplitVar = false; + m_attrFsmState = false; + m_attrFsmResetArc = false; + m_attrFsmArcInclCond = false; m_fileDescr = false; m_gotNansiType = false; m_isConst = false; @@ -2135,6 +2142,9 @@ public: 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; } + void attrFsmResetArc(bool flag) { m_attrFsmResetArc = flag; } + void attrFsmArcInclCond(bool flag) { m_attrFsmArcInclCond = flag; } void rand(const VRandAttr flag) { m_rand = flag; } void usedParam(bool flag) { m_usedParam = flag; } void usedLoopIdx(bool flag) { m_usedLoopIdx = flag; } @@ -2298,6 +2308,9 @@ public: bool attrFileDescr() const { return m_fileDescr; } bool attrSFormat() const { return m_attrSFormat; } bool attrSplitVar() const { return m_attrSplitVar; } + bool attrFsmState() const { return m_attrFsmState; } + 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; } @@ -2383,12 +2396,22 @@ class AstCoverOtherDecl final : public AstNodeCoverDecl { // Coverage analysis point declaration // Used for other than toggle types of coverage string m_linescov; + string m_fsmVar; + string m_fsmFrom; + string m_fsmTo; + string m_fsmTag; int m_offset; // Offset column numbers to uniq-ify IFs public: AstCoverOtherDecl(FileLine* fl, const string& page, const string& comment, - const string& linescov, int offset) + const string& linescov, int offset, const string& fsmVar = "", + const string& fsmFrom = "", const string& fsmTo = "", + const string& fsmTag = "") : ASTGEN_SUPER_CoverOtherDecl(fl, page, comment) , m_linescov{linescov} + , m_fsmVar{fsmVar} + , m_fsmFrom{fsmFrom} + , m_fsmTo{fsmTo} + , m_fsmTag{fsmTag} , m_offset{offset} {} ASTGEN_MEMBERS_AstCoverOtherDecl; void dump(std::ostream& str) const override; @@ -2396,6 +2419,10 @@ public: int offset() const { return m_offset; } int size() const override { return 1; } const string& linescov() const { return m_linescov; } + const string& fsmVar() const { return m_fsmVar; } + const string& fsmFrom() const { return m_fsmFrom; } + const string& fsmTo() const { return m_fsmTo; } + const string& fsmTag() const { return m_fsmTag; } bool sameNode(const AstNode* samep) const override { const AstCoverOtherDecl* const asamep = VN_DBG_AS(samep, CoverOtherDecl); return AstNodeCoverDecl::sameNode(samep) && linescov() == asamep->linescov(); diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index acf4b10fa..510703e4f 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -3004,6 +3004,9 @@ void AstVar::dump(std::ostream& str) const { if (processQueue()) str << " [PROCQ]"; if (sampled()) str << " [SAMPLED]"; if (attrIsolateAssign()) str << " [aISO]"; + if (attrFsmState()) str << " [aFSMSTATE]"; + if (attrFsmResetArc()) str << " [aFSMRESETARC]"; + if (attrFsmArcInclCond()) str << " [aFSMARCCOND]"; if (attrFileDescr()) str << " [aFD]"; if (isFuncReturn()) { str << " [FUNCRTN]"; @@ -3036,6 +3039,9 @@ void AstVar::dumpJson(std::ostream& str) const { dumpJsonBoolFuncIf(str, processQueue); dumpJsonBoolFuncIf(str, sampled); dumpJsonBoolFuncIf(str, attrIsolateAssign); + dumpJsonBoolFuncIf(str, attrFsmState); + dumpJsonBoolFuncIf(str, attrFsmResetArc); + dumpJsonBoolFuncIf(str, attrFsmArcInclCond); dumpJsonBoolFuncIf(str, attrFileDescr); dumpJsonBoolFuncIf(str, isDpiOpenArray); dumpJsonBoolFuncIf(str, isFuncReturn); @@ -3283,10 +3289,18 @@ void AstNodeCoverDecl::dumpJson(std::ostream& str) const { void AstCoverOtherDecl::dump(std::ostream& str) const { this->AstNodeCoverDecl::dump(str); if (!linescov().empty()) str << " lc=" << linescov(); + if (!fsmVar().empty()) str << " fv=" << fsmVar(); + if (!fsmFrom().empty()) str << " ff=" << fsmFrom(); + if (!fsmTo().empty()) str << " ft=" << fsmTo(); + if (!fsmTag().empty()) str << " fg=" << fsmTag(); } void AstCoverOtherDecl::dumpJson(std::ostream& str) const { this->AstNodeCoverDecl::dumpJson(str); dumpJsonStrFunc(str, linescov); + dumpJsonStrFunc(str, fsmVar); + dumpJsonStrFunc(str, fsmFrom); + dumpJsonStrFunc(str, fsmTo); + dumpJsonStrFunc(str, fsmTag); } void AstCoverToggleDecl::dump(std::ostream& str) const { this->AstNodeCoverDecl::dump(str); diff --git a/src/V3Dead.cpp b/src/V3Dead.cpp index f2453eec2..761fbe1a2 100644 --- a/src/V3Dead.cpp +++ b/src/V3Dead.cpp @@ -329,8 +329,12 @@ class DeadVisitor final : public VNVisitor { void visit(AstNodeFTask* nodep) override { iterateChildren(nodep); checkAll(nodep); - if (!nodep->taskPublic() && !nodep->dpiExport() && !nodep->dpiImport()) + if (nodep->taskPublic() || nodep->dpiExport() || nodep->dpiImport()) { + if (m_modp && !m_modp->dead() && !m_modp->verilatorLib()) + m_modp->user1Inc(); // Keep container + } else { m_tasksp.push(nodep); + } if (nodep->classOrPackagep()) { if (m_elimCells) { nodep->classOrPackagep(nullptr); diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index e330b5163..bdf7eb3a1 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -672,7 +672,6 @@ void DfgVertex::typeCheck(const DfgGraph& dfg) const { case VDfgType::Add: case VDfgType::And: - case VDfgType::BufIf1: case VDfgType::Div: case VDfgType::DivS: case VDfgType::ModDiv: @@ -759,18 +758,6 @@ void DfgVertex::typeCheck(const DfgGraph& dfg) const { return; } - case VDfgType::SAnd: - case VDfgType::SIntersect: - case VDfgType::SOr: - case VDfgType::SThroughout: - case VDfgType::SWithin: { - // LCOV_EXCL_START // Lowered before DFG - UASSERT_OBJ(false, this, - "SAnd/SIntersect/SOr/SThroughout/SWithin should be removed before DFG"); - return; - // LCOV_EXCL_STOP - } - case VDfgType::LogAnd: case VDfgType::LogEq: case VDfgType::LogIf: @@ -938,6 +925,160 @@ void DfgVertex::unlinkDelete(DfgGraph& dfg) { delete this; } +class DfgPatternString final { + std::ostream& m_os; + + std::map m_internedConsts; // Interned constants + std::map m_internedSelLsbs; // Interned lsb value for selects + std::map m_internedWordWidths; // Interned widths + std::map m_internedWideWidths; // Interned widths + std::map m_internedVertices; // Interned vertices + // Multiplicity and depth of vertices + std::map> m_multiplicityAndDepth; + + static std::string toLetters(size_t value, bool lowerCase = false) { + const char base = lowerCase ? 'a' : 'A'; + std::string s; + do { s += static_cast(base + value % 26); } while (value /= 26); + return s; + } + + const std::string& internConst(const DfgConst& vtx) { + const auto pair = m_internedConsts.emplace(vtx.num().ascii(false), ""); + if (pair.second) pair.first->second += toLetters(m_internedConsts.size() - 1); + return pair.first->second; + } + + const std::string& internSelLsb(uint32_t value) { + const auto pair = m_internedSelLsbs.emplace(value, ""); + if (pair.second) pair.first->second += toLetters(m_internedSelLsbs.size() - 1); + return pair.first->second; + } + + const std::string& internWordWidth(uint32_t value) { + const auto pair = m_internedWordWidths.emplace(value, ""); + if (pair.second) pair.first->second += toLetters(m_internedWordWidths.size() - 1, true); + return pair.first->second; + } + + const std::string& internWideWidth(uint32_t value) { + const auto pair = m_internedWideWidths.emplace(value, ""); + if (pair.second) pair.first->second += toLetters(m_internedWideWidths.size() - 1); + return pair.first->second; + } + + const std::string& internVertex(const DfgVertex& vtx) { + const auto pair = m_internedVertices.emplace(&vtx, ""); + if (pair.second) pair.first->second += toLetters(m_internedVertices.size() - 1); + return pair.first->second; + } + + void recordMultiplicityAndDepth(const DfgVertex& vtx, uint32_t depth) { + std::pair& value = m_multiplicityAndDepth + .emplace(std::piecewise_construct, // + std::forward_as_tuple(&vtx), // + std::forward_as_tuple(0, depth)) + .first->second; + value.first += 1; + value.second = std::max(value.second, depth); + if (!depth) return; + vtx.foreachSource([&](const DfgVertex& src) { + recordMultiplicityAndDepth(src, depth - 1); + return false; + }); + } + + // Render the vertx into ss, and return true if the recursion reached the given depth, + // meaning an S-expression with that nesting level has been rendered. + void render(const DfgVertex& vtx, uint32_t depth, bool isRoot = true) { + if (const DfgConst* const constp = vtx.cast()) { + // Base case 1: constant + if (constp->isZero()) { + m_os << "(CONST ZERO)"; + } else if (constp->isOnes()) { + m_os << "(CONST ONES)"; + } else { + m_os << "(CONST #" << internConst(*constp) << ')'; + } + } else if (!isRoot && m_multiplicityAndDepth.at(&vtx).first > 1) { + // Base case 2: vertex appearing multiple times + m_os << internVertex(vtx); + } else if (!vtx.foreachSource([&](const DfgVertex&) { return true; })) { + // Base case 3: vertex with no inputs (input variable) + m_os << '(' << vtx.typeName() << ')'; + } else if (depth == 0) { + // Base case 4: deep vertex (apperaing only once) + m_os << "_"; + } else { + // Recursively print an S-expression for the vertex + m_os << '('; + // Name + m_os << vtx.typeName(); + // Specials + if (const DfgSel* const selp = vtx.cast()) { + m_os << '@'; + if (selp->lsb() == 0) { + m_os << '0'; + } else { + m_os << internSelLsb(selp->lsb()); + } + } + // Operands + vtx.foreachSource([&](const DfgVertex& src) { + m_os << ' '; + render(src, depth - 1, false); + return false; + }); + // S-expression end + m_os << ')'; + } + + // Annotate type + m_os << ':'; + if (!vtx.dtype().isPacked()) { + vtx.dtype().astDtypep()->dumpSmall(m_os); + } else { + const uint32_t width = vtx.size(); + if (width == 1) { + m_os << '1'; + } else if (width <= VL_QUADSIZE) { + m_os << internWordWidth(width); + } else { + m_os << internWideWidth(width); + } + } + + // Mark it if it has multiple sinks + if (vtx.hasMultipleSinks()) m_os << '*'; + } + +public: + DfgPatternString(std::ostream& os, const DfgVertex& vtx, uint32_t depth) + : m_os{os} { + recordMultiplicityAndDepth(vtx, depth); + render(vtx, depth, false); + using Pair = std::pair; + std::vector vertices; + for (const auto& pair : m_multiplicityAndDepth) { + if (pair.second.first == 1) continue; + vertices.emplace_back(internVertex(*pair.first), pair.first); + } + std::sort(vertices.begin(), vertices.end(), [](const Pair& a, const Pair& b) { // + return a.first < b.first; + }); + for (const Pair& pair : vertices) { + m_os << " | " << pair.first << " is "; + render(*pair.second, m_multiplicityAndDepth.at(pair.second).second); + } + } +}; + +std::string DfgVertex::patternString(uint32_t depth) const { + std::ostringstream oss; + DfgPatternString{oss, *this, depth}; + return oss.str(); +} + //------------------------------------------------------------------------------ // DfgVisitor diff --git a/src/V3Dfg.h b/src/V3Dfg.h index fce033f9c..95320daad 100644 --- a/src/V3Dfg.h +++ b/src/V3Dfg.h @@ -352,6 +352,9 @@ public: // Human-readable name for source operand with given index for debugging virtual std::string srcName(size_t idx) const = 0; + + // S-expression inspired dump of vertex and operands for debugging + std::string patternString(uint32_t depth = 0) const; }; // DfgVertex visitor @@ -812,9 +815,11 @@ void DfgEdge::relinkSrcp(DfgVertex* srcp) { bool DfgVertex::isCheaperThanLoad() const { // Constants if (is()) return true; + // Variables + if (is()) return true; // Array sels are just address computation if (is()) return true; - // Small constant select from variable + // Small select from variable if (const DfgSel* const selp = cast()) { if (!selp->fromp()->is()) return false; if (selp->fromp()->width() <= VL_QUADSIZE) return true; @@ -825,20 +830,32 @@ bool DfgVertex::isCheaperThanLoad() const { // Zero extend of a cheap vertex - Extend(_) was converted to Concat(0, _) if (const DfgConcat* const catp = cast()) { if (catp->width() > VL_QUADSIZE) return false; - const DfgConst* const lCatp = catp->lhsp()->cast(); - if (!lCatp) return false; - if (!lCatp->isZero()) return false; + const DfgConst* const lConstp = catp->lhsp()->cast(); + if (!lConstp || !lConstp->isZero()) return false; return catp->rhsp()->isCheaperThanLoad(); } - // Reduction of a cheap vertex - if (const DfgRedOr* const redOrp = cast()) { - return redOrp->srcp()->isCheaperThanLoad(); + // Reduction of a narrow cheap vertex + if (is() // + || is() // + || is()) { + const DfgVertex* const srcp = as()->srcp(); + return srcp->width() <= VL_QUADSIZE && srcp->isCheaperThanLoad(); } - if (const DfgRedAnd* const redAndp = cast()) { - return redAndp->srcp()->isCheaperThanLoad(); - } - if (const DfgRedXor* const redXorp = cast()) { - return redXorp->srcp()->isCheaperThanLoad(); + // Comparisons of a narrow cheap vertex with constant + if (is() // + || is() // + || is() // + || is() // + || is() // + || is() // + || is() // + || is() // + || is() // + || is()) { + const DfgVertexBinary* const binp = as(); + const DfgVertex* const lhsp = binp->inputp(0); + const DfgVertex* const rhsp = binp->inputp(1); + return lhsp->width() <= VL_QUADSIZE && lhsp->is() && rhsp->isCheaperThanLoad(); } // Otherwise probably not return false; diff --git a/src/V3DfgCse.cpp b/src/V3DfgCse.cpp index a129e8327..5e4d1b93b 100644 --- a/src/V3DfgCse.cpp +++ b/src/V3DfgCse.cpp @@ -78,7 +78,6 @@ class V3DfgCse final { case VDfgType::Add: case VDfgType::And: case VDfgType::ArraySel: - case VDfgType::BufIf1: case VDfgType::Concat: case VDfgType::Cond: case VDfgType::CountOnes: @@ -125,11 +124,6 @@ class V3DfgCse final { case VDfgType::ShiftRS: case VDfgType::StreamL: case VDfgType::StreamR: - case VDfgType::SAnd: - case VDfgType::SIntersect: - case VDfgType::SOr: - case VDfgType::SThroughout: - case VDfgType::SWithin: case VDfgType::Sub: case VDfgType::Xor: return V3Hash{}; } @@ -206,7 +200,6 @@ class V3DfgCse final { case VDfgType::Add: case VDfgType::And: case VDfgType::ArraySel: - case VDfgType::BufIf1: case VDfgType::Concat: case VDfgType::Cond: case VDfgType::CountOnes: @@ -252,11 +245,6 @@ class V3DfgCse final { case VDfgType::ShiftR: case VDfgType::ShiftRS: case VDfgType::StreamL: - case VDfgType::SAnd: - case VDfgType::SIntersect: - case VDfgType::SOr: - case VDfgType::SThroughout: - case VDfgType::SWithin: case VDfgType::StreamR: case VDfgType::Sub: case VDfgType::Xor: return true; diff --git a/src/V3DfgDataType.h b/src/V3DfgDataType.h index 729b4c0b9..5af232b84 100644 --- a/src/V3DfgDataType.h +++ b/src/V3DfgDataType.h @@ -125,6 +125,8 @@ public: // Thanks to the interning, equality is identity bool operator==(const DfgDataType& that) const { return this == &that; } bool operator!=(const DfgDataType& that) const { return this != &that; } + // Similarly for hash + V3Hash hash() const { return V3Hash{this}; } // Type of elements, for arrays only const DfgDataType& elemDtype() const { @@ -132,13 +134,6 @@ public: return *m_elemDtypep; } - V3Hash hash() const { - V3Hash hash{static_cast(m_kind)}; - hash += m_size; - if (m_elemDtypep) hash += m_elemDtypep->hash(); - return hash; - } - //----------------------------------------------------------------------- // Static factory and management functions diff --git a/src/V3DfgDumpPatterns.cpp b/src/V3DfgDumpPatterns.cpp index be7e765c3..18611f180 100644 --- a/src/V3DfgDumpPatterns.cpp +++ b/src/V3DfgDumpPatterns.cpp @@ -21,133 +21,27 @@ #include "V3DfgPasses.h" #include "V3File.h" -#include -#include #include class V3DfgPatternStats final { static constexpr uint32_t MIN_PATTERN_DEPTH = 1; static constexpr uint32_t MAX_PATTERN_DEPTH = 4; - std::map m_internedConsts; // Interned constants - std::map m_internedSelLsbs; // Interned lsb value for selects - std::map m_internedWordWidths; // Interned widths - std::map m_internedWideWidths; // Interned widths - std::map m_internedVertices; // Interned vertices - // Maps from pattern to the number of times it appears, for each pattern depth std::vector> m_patterCounts{MAX_PATTERN_DEPTH + 1}; - static std::string toLetters(size_t value, bool lowerCase = false) { - const char base = lowerCase ? 'a' : 'A'; - std::string s; - do { s += static_cast(base + value % 26); } while (value /= 26); - return s; - } - - const std::string& internConst(const DfgConst& vtx) { - const auto pair = m_internedConsts.emplace(vtx.num().ascii(false), "c"); - if (pair.second) pair.first->second += toLetters(m_internedConsts.size() - 1); - return pair.first->second; - } - - const std::string& internSelLsb(uint32_t value) { - const auto pair = m_internedSelLsbs.emplace(value, ""); - if (pair.second) pair.first->second += toLetters(m_internedSelLsbs.size() - 1); - return pair.first->second; - } - - const std::string& internWordWidth(uint32_t value) { - const auto pair = m_internedWordWidths.emplace(value, ""); - if (pair.second) pair.first->second += toLetters(m_internedWordWidths.size() - 1, true); - return pair.first->second; - } - - const std::string& internWideWidth(uint32_t value) { - const auto pair = m_internedWideWidths.emplace(value, ""); - if (pair.second) pair.first->second += toLetters(m_internedWideWidths.size() - 1); - return pair.first->second; - } - - const std::string& internVertex(const DfgVertex& vtx) { - const auto pair = m_internedVertices.emplace(&vtx, ""); - if (pair.second) pair.first->second += toLetters(m_internedVertices.size() - 1); - return pair.first->second; - } - - // Render the vertx into ss, and return true if the recursion reached the given depth, - // meaning an S-expression with that nesting level has been rendered. - bool render(std::ostringstream& ss, const DfgVertex& vtx, uint32_t depth) { - bool deep = depth == 0; - - if (const DfgConst* const constp = vtx.cast()) { - // Base case 1: constant - if (constp->isZero()) { - ss << "'0"; - } else if (constp->isOnes()) { - ss << "'1"; - } else { - ss << internConst(*constp); - } - } else if (depth == 0) { - // Base case 2: deep vertex - ss << "_"; - } else { - // Recursively print an S-expression for the vertex - - // S-expression begin - ss << '('; - // Name - ss << vtx.typeName(); - // Specials - if (const DfgSel* const selp = vtx.cast()) { - ss << '@'; - if (selp->lsb() == 0) { - ss << '0'; - } else { - ss << internSelLsb(selp->lsb()); - } - } - // Operands - vtx.foreachSource([&](const DfgVertex& src) { - ss << ' '; - if (render(ss, src, depth - 1)) deep = true; - return false; - }); - // S-expression end - ss << ')'; - } - - // Annotate identity - ss << ":" << internVertex(vtx); - // Mark it if it has multiple sinks - if (vtx.hasMultipleSinks()) ss << '*'; - // Annotate type - ss << '/'; - if (!vtx.dtype().isPacked()) { - vtx.dtype().astDtypep()->dumpSmall(ss); - } else { - const uint32_t width = vtx.size(); - if (width == 1) { - ss << '1'; - } else if (width <= VL_QUADSIZE) { - ss << internWordWidth(width); - } else { - ss << internWideWidth(width); - } - } - - // Done - return deep; - } - void dump(std::ostream& os) { using Line = std::pair; for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) { os << "DFG patterns with depth " << i << '\n'; // Pick up pattern accumulators with given depth - const auto& patternCounts = m_patterCounts[i]; + auto& patternCounts = m_patterCounts[i]; + + // Remove patterns also present at shallower depths + for (uint32_t j = MIN_PATTERN_DEPTH; j < i; ++j) { + for (const auto& pair : m_patterCounts[j]) patternCounts.erase(pair.first); + } // Sort patterns, first by descending frequency, then lexically std::vector lines; @@ -173,8 +67,8 @@ public: V3DfgPatternStats() = default; ~V3DfgPatternStats() { // File to dump to - const std::string filename = v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix() - + "__stats_dfg_patterns.txt"; + const std::string filename + = v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix() + "__dfg_patterns.txt"; // Open, write, close const std::unique_ptr ofp{V3File::new_ofstream(filename)}; if (ofp->fail()) v3fatal("Can't write file: " << filename); @@ -184,13 +78,7 @@ public: void accumulate(const DfgGraph& dfg) { dfg.forEachVertex([&](const DfgVertex& vtx) { for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) { - std::ostringstream ss; - if (render(ss, vtx, i)) m_patterCounts[i][ss.str()] += 1; - m_internedConsts.clear(); - m_internedSelLsbs.clear(); - m_internedWordWidths.clear(); - m_internedWideWidths.clear(); - m_internedVertices.clear(); + m_patterCounts[i][vtx.patternString(i)] += 1; } }); } diff --git a/src/V3DfgOptimizer.cpp b/src/V3DfgOptimizer.cpp index 7bca7d2d4..b2fa0f9e8 100644 --- a/src/V3DfgOptimizer.cpp +++ b/src/V3DfgOptimizer.cpp @@ -154,9 +154,9 @@ class DataflowOptimize final { for (auto& cp : acyclicComps) V3DfgPasses::peephole(*cp, m_ctx.m_peepholeContext); endOfStage("peephole", dfg, acyclicComps); // Accumulate patterns for reporting - if (v3Global.opt.stats()) { + if (v3Global.opt.dumpDfgPatterns()) { V3DfgPasses::dumpPatterns(acyclicComps); - endOfStage("patterns"); + endOfStage("dumpPatterns"); } for (auto& cp : acyclicComps) V3DfgPasses::pushDownSels(*cp, m_ctx.m_pushDownSelsContext); endOfStage("pushDownSels", dfg, acyclicComps); diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index 62ad31269..f2e4b9ce0 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -208,6 +208,9 @@ class V3DfgPeephole final : public DfgVisitor { size_t m_currentGeneration = 0; // Current generation number size_t m_lastId = 0; // Last unique vertex ID assigned size_t m_nTemps = 0; // Number of temporary variables created + // Scope for transient temporariy variables cerated in this pass. They should all be + // eliminated wihtin this pass, so anything should be ok, pick the top scope as easy to find. + AstScope* const m_tmpScopep = v3Global.rootp()->topScopep()->scopep(); // STATIC STATE static V3DebugBisect s_debugBisect; // Debug aid @@ -404,6 +407,12 @@ class V3DfgPeephole final : public DfgVisitor { return make(examplep->fileline(), examplep->dtype(), operands...); } + // Replicate 'bitp' to 'vtxp->width()' bits + DfgVertex* replicate(DfgVertex* vtxp, DfgVertex* bitp) { + if (vtxp->dtype() == m_bitDType) return bitp; + return make(vtxp, bitp, makeI32(vtxp->fileline(), vtxp->width())); + } + // Check two vertex are the same, or the same constant value static bool isSame(const DfgVertex* ap, const DfgVertex* bp) { if (ap == bp) return true; @@ -561,15 +570,25 @@ class V3DfgPeephole final : public DfgVisitor { } } - // Attempt to reuse associative binary expressions if hey already exist, e.g.: - // '(a OP (b OP c))' -> '(a OP b) OP c', iff '(a OP b)' already exists, or - // '(a OP c) OP b' iff '(a OP c)' already exists and the vertex is commutative. - // Only do this is 'b OP c' has a single use and can subsequently be removed, - // otherwise there is no improvement. if (rSamep && !rSamep->hasMultipleSinks()) { DfgVertex* const rlVtxp = rSamep->lhsp(); DfgVertex* const rrVtxp = rSamep->rhsp(); + if VL_CONSTEXPR_CXX17 (IsCommutative::value) { + if (!lhsp->hasMultipleSinks() && rlVtxp->hasMultipleSinks()) { + APPLYING(ROTATE_ASSOC_COMM_MULTIUSE) { + replace(make(vtxp, rlVtxp, make(vtxp, lhsp, rrVtxp))); + return true; + } + } + } + + // Attempt to reuse associative binary expressions if hey already exist, e.g.: + // '(a OP (b OP c))' -> '(a OP b) OP c', iff '(a OP b)' already exists, or + // '(a OP c) OP b' iff '(a OP c)' already exists and the vertex is commutative. + // Only do this if 'b OP c' has a single use and can subsequently be removed, + // otherwise there is no improvement. + // '(a OP (b OP c))' -> '(a OP b) OP c' if (Vertex* const existingp = m_cache.get(resultDType(lhsp, rlVtxp), lhsp, rlVtxp)) { @@ -700,10 +719,10 @@ class V3DfgPeephole final : public DfgVisitor { tryPushBitwiseOpThroughConcat(Vertex* const vtxp, DfgConst* constp, DfgConcat* concatp) { FileLine* const flp = vtxp->fileline(); - // If at least one of the sides of the Concat constant, or width 1 (i.e.: can be - // further simplified), then push the Vertex past the Concat - if (concatp->lhsp()->is() || concatp->rhsp()->is() // - || concatp->lhsp()->dtype() == m_bitDType || concatp->rhsp()->dtype() == m_bitDType) { + // If at least one of the sides of the Concat constant, then push Vertex past Concat + DfgConst* const catLConstp = concatp->lhsp()->cast(); + DfgConst* const catRConstp = concatp->rhsp()->cast(); + if (catLConstp || catRConstp) { APPLYING(PUSH_BITWISE_OP_THROUGH_CONCAT) { const uint32_t width = concatp->width(); const DfgDataType& lDtype = concatp->lhsp()->dtype(); @@ -712,14 +731,30 @@ class V3DfgPeephole final : public DfgVisitor { const uint32_t rWidth = rDtype.size(); // The new Lhs vertex - DfgConst* const newLhsConstp = makeZero(constp->fileline(), lWidth); - newLhsConstp->num().opSel(constp->num(), width - 1, rWidth); - Vertex* const newLhsp = make(flp, lDtype, newLhsConstp, concatp->lhsp()); + DfgVertex* const newLhsp = [&]() -> DfgVertex* { + DfgConst* const newLhsConstp = makeZero(constp->fileline(), lWidth); + if (catLConstp) { + V3Number num{constp->fileline(), static_cast(lWidth), 0u}; + num.opSel(constp->num(), width - 1, rWidth); + foldOp(newLhsConstp->num(), num, catLConstp->num()); + return newLhsConstp; + } + newLhsConstp->num().opSel(constp->num(), width - 1, rWidth); + return make(flp, lDtype, newLhsConstp, concatp->lhsp()); + }(); // The new Rhs vertex - DfgConst* const newRhsConstp = makeZero(constp->fileline(), rWidth); - newRhsConstp->num().opSel(constp->num(), rWidth - 1, 0); - Vertex* const newRhsp = make(flp, rDtype, newRhsConstp, concatp->rhsp()); + DfgVertex* const newRhsp = [&]() -> DfgVertex* { + DfgConst* const newRhsConstp = makeZero(constp->fileline(), rWidth); + if (catRConstp) { + V3Number num{constp->fileline(), static_cast(rWidth), 0u}; + num.opSel(constp->num(), rWidth - 1, 0); + foldOp(newRhsConstp->num(), num, catRConstp->num()); + return newRhsConstp; + } + newRhsConstp->num().opSel(constp->num(), rWidth - 1, 0); + return make(flp, rDtype, newRhsConstp, concatp->rhsp()); + }(); // Replace this vertex replace(make(concatp, newLhsp, newRhsp)); @@ -794,6 +829,46 @@ class V3DfgPeephole final : public DfgVisitor { return false; } + template + VL_ATTR_WARN_UNUSED_RESULT bool tryPushBitwiseOpThrougSel(Bitwise* const vtxp) { + DfgVertex* const lhsp = vtxp->lhsp(); + DfgVertex* const rhsp = vtxp->rhsp(); + + if (DfgSel* const lSelp = lhsp->cast()) { + DfgSel* rSelp = nullptr; + DfgVertex* extrap = nullptr; + if (DfgSel* const selp = rhsp->cast()) { + rSelp = selp; + } else if (Bitwise* const bitwisep = rhsp->cast()) { + if (DfgSel* const rlSelp = bitwisep->lhsp()->template cast()) { + rSelp = rlSelp; + extrap = bitwisep->rhsp(); + } else if (DfgSel* const rrSelp = bitwisep->rhsp()->template cast()) { + rSelp = rrSelp; + extrap = bitwisep->lhsp(); + } + } + if (rSelp) { + DfgVertex* const lFromp = lSelp->fromp(); + DfgVertex* const rFromp = rSelp->fromp(); + if (lFromp->dtype() == rFromp->dtype() && lFromp->width() <= VL_QUADSIZE // + && lSelp->lsb() == rSelp->lsb()) { + APPLYING(PUSH_BITWISE_THROUGH_SEL) { + Bitwise* const bwp + = make(vtxp->fileline(), lSelp->fromp()->dtype(), + lSelp->fromp(), rSelp->fromp()); + DfgVertex* resp = make(vtxp, bwp, lSelp->lsb()); + if (extrap) resp = make(vtxp, resp, extrap); + replace(resp); + return true; + } + } + } + } + + return false; + } + template VL_ATTR_WARN_UNUSED_RESULT bool tryReplaceBitwiseWithReduction(Bitwise* vtxp) { UASSERT_OBJ(vtxp->width() == 1, vtxp, "Width must be 1"); @@ -803,13 +878,15 @@ class V3DfgPeephole final : public DfgVisitor { DfgVertex* const rhsp = vtxp->rhsp(); if (DfgSel* const lSelp = lhsp->template cast()) { - DfgSel* rSelp = rhsp->template cast(); + DfgSel* rSelp = nullptr; DfgVertex* extrap = nullptr; - if (!rSelp) { - if (Bitwise* const rBitwisep = rhsp->template cast()) { - rSelp = rBitwisep->lhsp()->template cast(); - extrap = rBitwisep->rhsp(); - } + if (DfgSel* const selp = rhsp->template cast()) { + rSelp = selp; + } else if (Bitwise* const rBitwisep = rhsp->template cast()) { + rSelp = rBitwisep->lhsp()->template cast(); + extrap = rBitwisep->rhsp(); + } else if (Reduction* const rRedp = rhsp->template cast()) { + rSelp = rRedp->srcp()->template cast(); } if (rSelp) { uint32_t lsb = 0; @@ -1021,6 +1098,94 @@ class V3DfgPeephole final : public DfgVisitor { return {nullptr, 0, 0}; } + // The following patterns all unwind a Sel throgh it's source and replace it with + // another single Sel. Doing this one at a time can take a long time with nested + // concatenations/selects/etc, so instead unwind as much as possible in one go. + std::pair unwindSel(DfgVertex* fromp, uint32_t lsb, + const uint32_t width) { + while (true) { + const uint32_t msb = lsb + width - 1; + + // Sel from Concat + if (DfgConcat* const concatp = fromp->cast()) { + DfgVertex* const lhsp = concatp->lhsp(); + DfgVertex* const rhsp = concatp->rhsp(); + + if (msb < rhsp->width()) { + // If the select is entirely from rhs, then replace with sel from rhs + APPLYING(REMOVE_SEL_FROM_RHS_OF_CONCAT) { + fromp = rhsp; + continue; + } + } else if (lsb >= rhsp->width()) { + // If the select is entirely from the lhs, then replace with sel from lhs + APPLYING(REMOVE_SEL_FROM_LHS_OF_CONCAT) { + fromp = lhsp; + lsb -= rhsp->width(); + continue; + } + } + } + + if (DfgReplicate* const repp = fromp->cast()) { + // If the Sel is wholly into the source of the Replicate, push the Sel through + // the Replicate and apply it directly to the source of the Replicate. + const uint32_t srcWidth = repp->srcp()->width(); + if (width <= srcWidth) { + const uint32_t newLsb = lsb % srcWidth; + const uint32_t newMsb = newLsb + width - 1; + if (newMsb < srcWidth) { + APPLYING(PUSH_SEL_THROUGH_REPLICATE) { + fromp = repp->srcp(); + lsb = newLsb; + continue; + } + } + } + } + + // Sel from Sel + if (DfgSel* const selp = fromp->cast()) { + APPLYING(REPLACE_SEL_FROM_SEL) { + // Select from the source of the source Sel with adjusted LSB + fromp = selp->fromp(); + lsb += selp->lsb(); + continue; + } + } + + // Sel from a partial variable (including narrowed vertex) + if (DfgVarPacked* const varp = fromp->cast()) { + if (varp->srcp() && !varp->isVolatile()) { + // Must be a splice, otherwise it would have been inlined + DfgSplicePacked* splicep = varp->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) { + APPLYING(PUSH_SEL_THROUGH_SPLICE) { + fromp = driverp; + lsb -= driverLsb; + continue; + } + } + } + } + + // No patterns matched, stop + break; + } + return {fromp, lsb}; + } + // VISIT methods void visit(DfgVertex*) override {} @@ -1148,38 +1313,12 @@ class V3DfgPeephole final : public DfgVisitor { } } - // Sel from Concat - if (DfgConcat* const concatp = fromp->cast()) { - DfgVertex* const lhsp = concatp->lhsp(); - DfgVertex* const rhsp = concatp->rhsp(); - - if (msb < rhsp->width()) { - // If the select is entirely from rhs, then replace with sel from rhs - APPLYING(REMOVE_SEL_FROM_RHS_OF_CONCAT) { // - replace(make(vtxp, rhsp, vtxp->lsb())); - return; - } - } else if (lsb >= rhsp->width()) { - // If the select is entirely from the lhs, then replace with sel from lhs - APPLYING(REMOVE_SEL_FROM_LHS_OF_CONCAT) { - replace(make(vtxp, lhsp, lsb - rhsp->width())); - return; - } - } - } - - if (DfgReplicate* const repp = fromp->cast()) { - // If the Sel is wholly into the source of the Replicate, push the Sel through the - // Replicate and apply it directly to the source of the Replicate. - const uint32_t srcWidth = repp->srcp()->width(); - if (width <= srcWidth) { - const uint32_t newLsb = lsb % srcWidth; - if (newLsb + width <= srcWidth) { - APPLYING(PUSH_SEL_THROUGH_REPLICATE) { - replace(make(vtxp, repp->srcp(), newLsb)); - return; - } - } + // Unwind through bit packing in one go + { + const auto res = unwindSel(fromp, lsb, width); + if (res.first != fromp || res.second != lsb) { + replace(make(vtxp, res.first, res.second)); + return; } } @@ -1197,15 +1336,6 @@ class V3DfgPeephole final : public DfgVisitor { } } - // Sel from Sel - if (DfgSel* const selp = fromp->cast()) { - APPLYING(REPLACE_SEL_FROM_SEL) { - // Select from the source of the source Sel with adjusted LSB - replace(make(vtxp, selp->fromp(), lsb + selp->lsb())); - return; - } - } - // Sel from Cond if (DfgCond* const condp = fromp->cast()) { if (!condp->hasMultipleSinks()) { @@ -1239,31 +1369,6 @@ class V3DfgPeephole final : public DfgVisitor { } } } - - // Sel from a partial variable (including narrowed vertex) - if (DfgVarPacked* const varp = fromp->cast()) { - if (varp->srcp() && !varp->isVolatile()) { - // Must be a splice, otherwise it would have been inlined - DfgSplicePacked* splicep = varp->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) { - APPLYING(PUSH_SEL_THROUGH_SPLICE) { - replace(make(vtxp, driverp, lsb - driverLsb)); - return; - } - } - } - } } void visit(DfgMux* const vtxp) override { @@ -1349,18 +1454,27 @@ class V3DfgPeephole final : public DfgVisitor { if (tryPushBitwiseOpThroughReductions(vtxp)) return; - if (DfgNot* const lhsNotp = lhsp->cast()) { + if (tryPushBitwiseOpThrougSel(vtxp)) return; + + { + DfgNot* const lNotp = lhsp->cast(); + DfgNot* const rNotp = rhsp->cast(); // ~A & A is all zeroes - if (lhsNotp->srcp() == rhsp) { + if ((lNotp && isSame(lNotp->srcp(), rhsp)) || (rNotp && isSame(lhsp, rNotp->srcp()))) { APPLYING(REPLACE_CONTRADICTORY_AND) { replace(makeZero(flp, vtxp->width())); return; } } - // ~A & (A & _) or ~A & (_ & A) is all zeroes - if (DfgAnd* const rhsAndp = rhsp->cast()) { - if (lhsNotp->srcp() == rhsAndp->lhsp() || lhsNotp->srcp() == rhsAndp->rhsp()) { + if (DfgAnd* const rSamep = rhsp->cast()) { + DfgNot* const rlNotp = rSamep->lhsp()->cast(); + DfgNot* const rrNotp = rSamep->rhsp()->cast(); + // ~A & (A & _) or ~A & (_ & A) is all zeroes + if ((lNotp && isSame(lNotp->srcp(), rSamep->lhsp())) + || (lNotp && isSame(lNotp->srcp(), rSamep->rhsp())) + || (rlNotp && isSame(lhsp, rlNotp->srcp())) + || (rrNotp && isSame(lhsp, rrNotp->srcp()))) { APPLYING(REPLACE_CONTRADICTORY_AND_3) { replace(makeZero(flp, vtxp->width())); return; @@ -1463,24 +1577,29 @@ class V3DfgPeephole final : public DfgVisitor { if (tryPushBitwiseOpThroughReductions(vtxp)) return; - if (DfgNot* const lhsNotp = lhsp->cast()) { + if (tryPushBitwiseOpThrougSel(vtxp)) return; + + { + DfgNot* const lNotp = lhsp->cast(); + DfgNot* const rNotp = rhsp->cast(); // ~A | A is all ones - if (lhsNotp->srcp() == rhsp) { + if ((lNotp && isSame(lNotp->srcp(), rhsp)) || (rNotp && isSame(lhsp, rNotp->srcp()))) { APPLYING(REPLACE_TAUTOLOGICAL_OR) { - DfgConst* const resp = makeZero(flp, vtxp->width()); - resp->num().setAllBits1(); - replace(resp); + replace(makeOnes(flp, vtxp->width())); return; } } - // ~A | (A | _) or ~A | (_ | A) is all ones - if (DfgOr* const rhsOrp = rhsp->cast()) { - if (lhsNotp->srcp() == rhsOrp->lhsp() || lhsNotp->srcp() == rhsOrp->rhsp()) { + if (DfgOr* const rSamep = rhsp->cast()) { + DfgNot* const rlNotp = rSamep->lhsp()->cast(); + DfgNot* const rrNotp = rSamep->rhsp()->cast(); + // ~A | (A | _) or ~A | (_ | A) is all ones + if ((lNotp && isSame(lNotp->srcp(), rSamep->lhsp())) + || (lNotp && isSame(lNotp->srcp(), rSamep->rhsp())) + || (rlNotp && isSame(lhsp, rlNotp->srcp())) + || (rrNotp && isSame(lhsp, rrNotp->srcp()))) { APPLYING(REPLACE_TAUTOLOGICAL_OR_3) { - DfgConst* const resp = makeZero(flp, vtxp->width()); - resp->num().setAllBits1(); - replace(resp); + replace(makeOnes(flp, vtxp->width())); return; } } @@ -1525,6 +1644,8 @@ class V3DfgPeephole final : public DfgVisitor { if (tryPushBitwiseOpThroughReductions(vtxp)) return; + if (tryPushBitwiseOpThrougSel(vtxp)) return; + if (vtxp->dtype() == m_bitDType) { if (tryReplaceBitwiseWithReduction(vtxp)) return; } @@ -1847,10 +1968,9 @@ class V3DfgPeephole final : public DfgVisitor { DfgSplicePacked* const sp = new DfgSplicePacked{m_dfg, flp, vtxp->dtype()}; m_vInfo[sp].m_id = ++m_lastId; sp->addDriver(catp, lsb, flp); - DfgVertex::ScopeCache scopeCache; - AstScope* const scopep = vtxp->scopep(scopeCache, true); const std::string name = m_dfg.makeUniqueName("PeepholeNarrow", m_nTemps++); - DfgVertexVar* const varp = m_dfg.makeNewVar(flp, name, vtxp->dtype(), scopep); + DfgVertexVar* const varp + = m_dfg.makeNewVar(flp, name, vtxp->dtype(), m_tmpScopep); varp->tmpForp(varp->vscp()); m_vInfo[varp].m_id = ++m_lastId; varp->vscp()->varp()->isInternal(true); @@ -2511,53 +2631,63 @@ class V3DfgPeephole final : public DfgVisitor { } if (vtxp->dtype() == m_bitDType) { - if (isZero(thenp)) { // a ? 0 : b becomes ~a & b - APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ZERO) { - replace(make(vtxp, make(vtxp, condp), elsep)); - return; - } - } - if (thenp == condp) { // a ? a : b becomes a | b + if (isSame(condp, thenp)) { // a ? a : b becomes a | b APPLYING(REPLACE_COND_WITH_THEN_BRANCH_COND) { replace(make(vtxp, condp, elsep)); return; } } - if (elsep == condp) { // a ? b : a becomes a & b + if (isSame(condp, elsep)) { // a ? b : a becomes a & b APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_COND) { replace(make(vtxp, condp, thenp)); return; } } + } + + if (vtxp->width() <= VL_QUADSIZE) { + if (isZero(thenp)) { // a ? 0 : b becomes ~a & b + APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ZERO) { + DfgVertex* const maskp = replicate(vtxp, make(condp, condp)); + replace(make(vtxp, maskp, elsep)); + return; + } + } if (isOnes(thenp)) { // a ? 1 : b becomes a | b APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ONES) { - replace(make(vtxp, condp, elsep)); + DfgVertex* const maskp = replicate(vtxp, condp); + replace(make(vtxp, maskp, elsep)); return; } } if (isZero(elsep)) { // a ? b : 0 becomes a & b APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_ZERO) { - replace(make(vtxp, condp, thenp)); + DfgVertex* const maskp = replicate(vtxp, condp); + replace(make(vtxp, maskp, thenp)); return; } } if (isOnes(elsep)) { // a ? b : 1 becomes ~a | b APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_ONES) { - replace(make(vtxp, make(vtxp, condp), thenp)); + DfgVertex* const maskp = replicate(vtxp, make(condp, condp)); + replace(make(vtxp, maskp, thenp)); return; } } + if (DfgOr* const tOrp = thenp->cast()) { if (isSame(tOrp->lhsp(), elsep)) { // a ? b | c : b becomes b | (a & c) APPLYING(REPLACE_COND_THEN_OR_LHS) { - DfgAnd* const andp = make(vtxp, condp, tOrp->rhsp()); + DfgVertex* const maskp = replicate(vtxp, condp); + DfgAnd* const andp = make(vtxp, maskp, tOrp->rhsp()); replace(make(vtxp, tOrp->lhsp(), andp)); return; } } if (isSame(tOrp->rhsp(), elsep)) { // a ? b | c : c becomes c | (a & b) APPLYING(REPLACE_COND_THEN_OR_RHS) { - DfgAnd* const andp = make(vtxp, condp, tOrp->lhsp()); + DfgVertex* const maskp = replicate(vtxp, condp); + DfgAnd* const andp = make(vtxp, maskp, tOrp->lhsp()); replace(make(vtxp, tOrp->rhsp(), andp)); return; } @@ -2590,6 +2720,33 @@ class V3DfgPeephole final : public DfgVisitor { } } } + + 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; + } + } + } + } + } + } + } } if (isEqOne(thenp) && isZero(elsep)) { diff --git a/src/V3DfgPeepholePatterns.h b/src/V3DfgPeepholePatterns.h index 0a8969592..05df94530 100644 --- a/src/V3DfgPeepholePatterns.h +++ b/src/V3DfgPeepholePatterns.h @@ -51,6 +51,7 @@ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PULL_NOTS_THROUGH_COND) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_OP_THROUGH_CONCAT) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_THROUGH_REDUCTION) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_THROUGH_SEL) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_COMMUTATIVE_BINARY_THROUGH_COND) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_COMPARE_OP_THROUGH_CONCAT) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_CONCAT_THROUGH_COND_LHS) \ @@ -110,6 +111,7 @@ _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) \ @@ -160,6 +162,7 @@ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REUSE_ASSOC_BINARY_LHS_WITH_LHS_OF_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REUSE_ASSOC_BINARY_LHS_WITH_RHS_OF_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, RIGHT_LEANING_ASSOC) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, ROTATE_ASSOC_COMM_MULTIUSE) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_COND_WITH_NEQ_CONDITION) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_COND_WITH_NOT_CONDITION) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_SIDES_IN_BINARY) diff --git a/src/V3DfgRegularize.cpp b/src/V3DfgRegularize.cpp index 622bb4648..588dbd52c 100644 --- a/src/V3DfgRegularize.cpp +++ b/src/V3DfgRegularize.cpp @@ -195,24 +195,35 @@ class DfgRegularize final { // Scope cache for below DfgVertex::ScopeCache scopeCache; - // Ensure intermediate values used multiple times are written to variables + // Build map from fanout to list of vertices with that fanout + std::vector> fanout2Vtxps; for (DfgVertex& vtx : m_dfg.opVertices()) { // LValue vertices feed into variables eventually and need no temporaries if (vtx.is()) continue; if (vtx.is()) continue; + // Add to map + const uint32_t fanout = vtx.fanout(); + if (!fanout) continue; + if (fanout >= fanout2Vtxps.size()) fanout2Vtxps.resize(2 * fanout); + fanout2Vtxps[fanout].push_back(&vtx); + } + if (fanout2Vtxps.empty()) return; - if (!needsTemporary(vtx, vtx)) continue; - - // Need to create an intermediate variable - ++m_ctx.m_temporariesIntroduced; - const std::string name = m_dfg.makeUniqueName("Regularize", m_nTmps); - FileLine* const flp = vtx.fileline(); - AstScope* const scopep = vtx.scopep(scopeCache); - DfgVertexVar* const newp = m_dfg.makeNewVar(flp, name, vtx.dtype(), scopep); - ++m_nTmps; - // Replace vertex with the variable, make it drive the variable - vtx.replaceWith(newp); - newp->srcp(&vtx); + // Ensure intermediate values used multiple times are written to variables + for (size_t fanout = fanout2Vtxps.size() - 1; fanout > 0; --fanout) { + for (DfgVertex* const vtxp : fanout2Vtxps[fanout]) { + if (!needsTemporary(*vtxp, *vtxp)) continue; + // Need to create an intermediate variable + ++m_ctx.m_temporariesIntroduced; + const std::string name = m_dfg.makeUniqueName("Regularize", m_nTmps); + FileLine* const flp = vtxp->fileline(); + AstScope* const scopep = vtxp->scopep(scopeCache); + DfgVertexVar* const newp = m_dfg.makeNewVar(flp, name, vtxp->dtype(), scopep); + ++m_nTmps; + // Replace vertex with the variable, make it drive the variable + vtxp->replaceWith(newp); + newp->srcp(vtxp); + } } } diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index d9b6bb9e3..1427f096b 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -826,6 +826,14 @@ public: putsQuoted(VIdProtect::protectWordsIf(nodep->comment(), nodep->protect())); puts(", "); putsQuoted(nodep->linescov()); + puts(", "); + putsQuoted(VIdProtect::protectWordsIf(nodep->fsmVar(), nodep->protect())); + puts(", "); + putsQuoted(VIdProtect::protectWordsIf(nodep->fsmFrom(), nodep->protect())); + puts(", "); + putsQuoted(VIdProtect::protectWordsIf(nodep->fsmTo(), nodep->protect())); + puts(", "); + putsQuoted(VIdProtect::protectWordsIf(nodep->fsmTag(), nodep->protect())); puts(");\n"); } void visit(AstCoverToggleDecl* nodep) override { diff --git a/src/V3EmitCHeaders.cpp b/src/V3EmitCHeaders.cpp index 62787c757..7947bb070 100644 --- a/src/V3EmitCHeaders.cpp +++ b/src/V3EmitCHeaders.cpp @@ -197,7 +197,9 @@ class EmitCHeader final : public EmitCConstInit { puts(v3Global.opt.threads() > 1 ? "std::atomic" : "uint32_t"); puts("* countp, bool enable, const char* filenamep, int lineno, int column,\n"); puts("const char* hierp, const char* pagep, const char* commentp, const char* " - "linescovp);\n"); + "linescovp,\n"); + puts("const char* fsmVarp, const char* fsmFromp, const char* fsmTop, const char* " + "fsmTagp);\n"); } if (v3Global.opt.coverageToggle() && !VN_IS(modp, Class)) { @@ -663,6 +665,7 @@ class EmitCHeader final : public EmitCConstInit { if (v3Global.opt.coverage()) puts("#include \"verilated_cov.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"); std::set cuse_set; auto add_to_cuse_set = [&](string s) { cuse_set.insert(s); }; diff --git a/src/V3EmitCImp.cpp b/src/V3EmitCImp.cpp index eb7eb4718..ad955cd3b 100644 --- a/src/V3EmitCImp.cpp +++ b/src/V3EmitCImp.cpp @@ -180,7 +180,9 @@ class EmitCImp final : public EmitCFunc { puts(v3Global.opt.threads() > 1 ? "std::atomic" : "uint32_t"); puts("* countp, bool enable, const char* filenamep, int lineno, int column,\n"); puts("const char* hierp, const char* pagep, const char* commentp, const char* " - "linescovp) {\n"); + "linescovp,\n"); + puts("const char* fsmVarp, const char* fsmFromp, const char* fsmTop, const char* " + "fsmTagp) {\n"); if (v3Global.opt.threads() > 1) { puts("assert(sizeof(uint32_t) == sizeof(std::atomic));\n"); puts("uint32_t* count32p = reinterpret_cast(countp);\n"); @@ -198,10 +200,14 @@ class EmitCImp final : public EmitCFunc { puts(" \"filename\",filenamep,"); puts(" \"lineno\",lineno,"); puts(" \"column\",column,\n"); - puts("\"hier\",fullhier,"); + puts("\"hier\",fullhier.c_str(),"); puts(" \"page\",pagep,"); puts(" \"comment\",commentp,"); - puts(" (linescovp[0] ? \"linescov\" : \"\"), linescovp);\n"); + puts(" (linescovp[0] ? \"linescov\" : \"\"), linescovp,"); + puts(" (fsmVarp[0] ? \"fsm_var\" : \"\"), fsmVarp,"); + puts(" (fsmFromp[0] ? \"fsm_from\" : \"\"), fsmFromp,"); + puts(" (fsmTop[0] ? \"fsm_to\" : \"\"), fsmTop,"); + puts(" (fsmTagp[0] ? \"fsm_tag\" : \"\"), fsmTagp);\n"); puts("}\n"); } if (v3Global.opt.coverageToggle()) { @@ -237,7 +243,7 @@ class EmitCImp final : public EmitCFunc { puts(" \"filename\",filenamep,"); puts(" \"lineno\",lineno,"); puts(" \"column\",column,\n"); - puts("\"hier\",fullhier,"); + puts("\"hier\",fullhier.c_str(),"); puts(" \"page\",pagep,"); puts(" \"comment\",commentWithIndex.c_str(),"); puts(" \"\", \"\");\n"); // linescov argument, but in toggle coverage it is always diff --git a/src/V3Error.h b/src/V3Error.h index c5f49dfd4..07feb51df 100644 --- a/src/V3Error.h +++ b/src/V3Error.h @@ -106,6 +106,7 @@ public: ENUMITEMWIDTH, // Error: enum item width mismatch ENUMVALUE, // Error: enum type needs explicit cast EOFNEWLINE, // End-of-file missing newline + FSMMULTI, // Multiple FSM candidates in one always block FUNCTIMECTL, // Functions cannot have timing/delay/wait FUTURE, // Feature is under development and not yet supported GENCLK, // Generated Clock. Historical, never issued. @@ -223,14 +224,14 @@ public: "BSSPACE", "CASEINCOMPLETE", "CASEOVERLAP", "CASEWITHX", "CASEX", "CASTCONST", "CDCRSTLOGIC", "CLKDATA", "CMPCONST", "COLONPLUS", "COMBDLY", "CONSTRAINTIGN", "CONTASSREG", "COVERIGN", "DECLFILENAME", "DEFOVERRIDE", "DEFPARAM", "DEPRECATED", - "ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH", "ENUMVALUE", "EOFNEWLINE", "FUNCTIMECTL", - "FUTURE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "HIERPARAM", "IFDEPTH", "IGNOREDRETURN", - "IMPERFECTSCH", "IMPLICIT", "IMPLICITSTATIC", "IMPORTSTAR", "IMPURE", "INCABSPATH", - "INFINITELOOP", "INITIALDLY", "INSECURE", "INSIDETRUE", "LATCH", "LITENDIAN", - "MINTYPMAXDLY", "MISINDENT", "MODDUP", "MODMISSING", "MULTIDRIVEN", "MULTITOP", - "NEWERSTD", "NOEFFECT", "NOLATCH", "NONSTD", "NORETURN", "NULLPORT", "PARAMNODEFAULT", - "PINCONNECTEMPTY", "PINMISSING", "PINNOCONNECT", "PINNOTFOUND", "PKGNODECL", - "PREPROCZERO", "PROCASSINIT", "PROCASSWIRE", "PROFOUTOFDATE", "PROTECTED", + "ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH", "ENUMVALUE", "EOFNEWLINE", "FSMMULTI", + "FUNCTIMECTL", "FUTURE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "HIERPARAM", "IFDEPTH", + "IGNOREDRETURN", "IMPERFECTSCH", "IMPLICIT", "IMPLICITSTATIC", "IMPORTSTAR", "IMPURE", + "INCABSPATH", "INFINITELOOP", "INITIALDLY", "INSECURE", "INSIDETRUE", "LATCH", + "LITENDIAN", "MINTYPMAXDLY", "MISINDENT", "MODDUP", "MODMISSING", "MULTIDRIVEN", + "MULTITOP", "NEWERSTD", "NOEFFECT", "NOLATCH", "NONSTD", "NORETURN", "NULLPORT", + "PARAMNODEFAULT", "PINCONNECTEMPTY", "PINMISSING", "PINNOCONNECT", "PINNOTFOUND", + "PKGNODECL", "PREPROCZERO", "PROCASSINIT", "PROCASSWIRE", "PROFOUTOFDATE", "PROTECTED", "PROTOTYPEMIS", "RANDC", "REALCVT", "REDEFMACRO", "RISEFALLDLY", "SELRANGE", "SHORTREAL", "SIDEEFFECT", "SPECIFYIGN", "SPLITVAR", "STATICVAR", "STMTDLY", "SUPERNFIRST", "SYMRSVDWORD", "SYNCASYNCNET", "TICKCOUNT", "TIMESCALEMOD", "UNDRIVEN", diff --git a/src/V3Force.cpp b/src/V3Force.cpp index 727494130..c4a5f5fe0 100644 --- a/src/V3Force.cpp +++ b/src/V3Force.cpp @@ -1,6 +1,6 @@ // -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* -// DESCRIPTION: Verilator: Covert forceable signals, process force/release +// DESCRIPTION: Verilator: Convert forceable signals, process force/release // // Code available from: https://verilator.org // @@ -15,31 +15,21 @@ //************************************************************************* // V3Force's Transformations: // -// For each forceable net with name "": -// add 3 extra signals: -// - __VforceRd: a net with same type as signal -// - __VforceEn: a var with same type as signal, which is the bitwise force enable -// - __VforceVal: a var with same type as signal, which is the forced value -// add an initial statement: -// initial __VforceEn = 0; -// add a continuous assignment: -// assign __VforceRd = __VforceEn ? __VforceVal : ; -// replace all READ references to with a read reference to _VforceRd +// For each forceable var/net "": +// - Create __VforceVec (VlForceVec) to track active force ranges +// - Create __VforceRHS vars to hold RHS shadow values +// - Add continuous assignments: __VforceRHS = RHS // -// Replace each AstAssignForce with 3 assignments: -// - __VforceEn = 1 -// - __VforceVal = -// - __VforceRd = +// For each `force [range] = ` with ID: +// - __VforceVec.addForce(lsb, msb, &__VforceRHS, rhsLsb) // -// Replace each AstRelease with 1 or 2 assignments: -// - __VforceEn = 0 -// - __VforceRd = // iff lhs is a net +// For each `release [range]`: +// - If not continuously driven: = VlForceVec::read(, __VforceVec) +// - __VforceVec.release(lsb, msb) // -// After each WRITE of forced LHS -// reevaluate __VforceRd to support immediate force/release +// For each read of : +// - Replace with: VlForceVec::read(, __VforceVec) // -// After each WRITE of forced RHS -// reevaluate __VforceVal to support VarRef rollback after release //************************************************************************* #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT @@ -51,522 +41,565 @@ VL_DEFINE_DEBUG_FUNCTIONS; -//###################################################################### -// Convert force/release statements and signals marked 'forceable' - class ForceState final { - constexpr static int ELEMENTS_MAX = 1000; - // TYPES - struct ForceComponentsVar final { - AstVar* const m_rdVarp; // New variable to replace read references with - AstVar* const m_valVarp; // Forced value - AstVar* const m_enVarp; // Force enabled signal - explicit ForceComponentsVar(AstVar* varp) - : m_rdVarp{new AstVar{varp->fileline(), VVarType::WIRE, varp->name() + "__VforceRd", - varp->dtypep()}} - , m_valVarp{new AstVar{varp->fileline(), VVarType::VAR, varp->name() + "__VforceVal", - varp->dtypep()}} - , m_enVarp{new AstVar{varp->fileline(), VVarType::VAR, varp->name() + "__VforceEn", - getEnVarpDTypep(varp)}} { - m_rdVarp->addNext(m_enVarp); - m_rdVarp->addNext(m_valVarp); - varp->addNextHere(m_rdVarp); +public: + struct ForceRange VL_NOT_FINAL { + int m_rangeLsb = 0; // VlForceVec range: bit index or array element index + int m_rangeMsb = 0; + int m_padLsb = 0; // Bit positions for RHS padding + int m_padMsb = 0; + }; + + struct ForceInfo final : ForceRange { + // MEMBERS + int m_forceId = 0; // Unique (per signal) variable of this force assignment + bool m_hasArraySel = false; // If this has an array select on LHS + AstVarScope* m_rhsVarVscp = nullptr; // Scope of the var containing RHSID + AstNodeExpr* m_rhsExprp = nullptr; // Expression on RHS of this force assignment + + ForceInfo() = default; + ForceInfo(int rangeLsb, int rangeMsb, int padLsb, int padMsb, int forceId, + bool hasArraySel, AstVarScope* rhsVarVscp, AstNodeExpr* rhsExprp) + : m_forceId{forceId} + , m_hasArraySel{hasArraySel} + , m_rhsVarVscp{rhsVarVscp} + , m_rhsExprp{rhsExprp} { + m_rangeLsb = rangeLsb; + m_rangeMsb = rangeMsb; + m_padLsb = padLsb; + m_padMsb = padMsb; } }; -public: - struct ForceComponentsVarScope final { - AstVarScope* const m_rdVscp; // New variable to replace read references with - AstVarScope* const m_valVscp; // Forced value - AstVarScope* const m_enVscp; // Force enabled signal - V3UniqueNames m_iterNames; // Names for loop iteration variables - explicit ForceComponentsVarScope(AstVarScope* vscp, ForceComponentsVar& fcv) - : m_rdVscp{new AstVarScope{vscp->fileline(), vscp->scopep(), fcv.m_rdVarp}} - , m_valVscp{new AstVarScope{vscp->fileline(), vscp->scopep(), fcv.m_valVarp}} - , m_enVscp{new AstVarScope{vscp->fileline(), vscp->scopep(), fcv.m_enVarp}} - , m_iterNames{"__VForceIter"} { - m_rdVscp->addNext(m_enVscp); - m_rdVscp->addNext(m_valVscp); - vscp->addNextHere(m_rdVscp); + struct VarForceInfo final { + AstVarScope* m_forceVecVscp = nullptr; + AstVarScope* m_forceRdVscp = nullptr; + AstVarScope* m_forceEnVscp = nullptr; + AstVarScope* m_forceValVscp = nullptr; + AstVarScope* m_varVscp = nullptr; + AstScope* m_scopep = nullptr; + std::unordered_map m_forces; + std::unordered_map m_forcePathToIndex; + int m_nextForcePathIndex = 1; // Start at 1 so 0 can be the base path (whole signal) - FileLine* const flp = vscp->fileline(); - - // Add initialization of the enable signal - AstActive* const activeInitp = new AstActive{ - flp, "force-init", new AstSenTree{flp, new AstSenItem{flp, AstSenItem::Static{}}}}; - activeInitp->senTreeStorep(activeInitp->sentreep()); - vscp->scopep()->addBlocksp(activeInitp); - - // Create statements that update __Rd variable. - // These nodes will be copied and used also for __En initialization - AstVarRef* const rdRefp = new AstVarRef{flp, m_rdVscp, VAccess::WRITE}; - std::vector assigns; - AstNodeStmt* const rdUpdateStmtsp - = getForcedUpdateStmtsRecursep(rdRefp, vscp, rdRefp, assigns); - - // To use these statements for __En initialization, replace references to __Rd with - // ones to __En and replace assignments RHS with 0 - AstNodeStmt* const enInitStmtsp = rdUpdateStmtsp->cloneTree(true); - for (size_t i = 0; i < assigns.size(); i++) { - // Save copies, because clonep() works only after the last cloneTree - assigns[i] = assigns[i]->clonep(); + private: + static void buildForcePathKeyRecurse(AstNodeExpr* nodep, string& out) { + if (VN_IS(nodep, VarRef)) return; + if (const AstSel* const selp = VN_CAST(nodep, Sel)) { + buildForcePathKeyRecurse(selp->fromp(), out); + out += "|S" + cvtToStr(selp->lsbConst()) + ":" + cvtToStr(selp->widthConst()); + return; } - for (AstAssign* const assignp : assigns) { - AstVarRef* const lhsVarRefp - = VN_AS(AstNodeVarRef::varRefLValueRecurse(assignp->lhsp()), VarRef); - lhsVarRefp->replaceWith(new AstVarRef{flp, m_enVscp, VAccess::WRITE}); - lhsVarRefp->deleteTree(); - assignp->rhsp()->unlinkFrBack()->deleteTree(); - const V3Number zero{m_enVscp, assignp->lhsp()->dtypep()->width()}; - assignp->rhsp(new AstConst{flp, zero}); - } - activeInitp->addStmtsp(new AstInitial{flp, enInitStmtsp}); - { // Add the combinational override - // Explicitly list dependencies for update. - // Note: rdVscp is also needed to retrigger assignment for the first time. - AstSenItem* const itemsp = new AstSenItem{ - flp, VEdgeType::ET_CHANGED, new AstVarRef{flp, m_rdVscp, VAccess::READ}}; - itemsp->addNext(new AstSenItem{flp, VEdgeType::ET_CHANGED, - new AstVarRef{flp, m_valVscp, VAccess::READ}}); - itemsp->addNext(new AstSenItem{flp, VEdgeType::ET_CHANGED, - new AstVarRef{flp, m_enVscp, VAccess::READ}}); - AstVarRef* const origp = new AstVarRef{flp, vscp, VAccess::READ}; - ForceState::markNonReplaceable(origp); - itemsp->addNext(new AstSenItem{flp, VEdgeType::ET_CHANGED, origp}); - AstActive* const activep - = new AstActive{flp, "force-update", new AstSenTree{flp, itemsp}}; - activep->senTreeStorep(activep->sentreep()); - - activep->addStmtsp( - new AstAlways{flp, VAlwaysKwd::ALWAYS, nullptr, rdUpdateStmtsp}); - vscp->scopep()->addBlocksp(activep); + if (AstStructSel* const selp = VN_CAST(nodep, StructSel)) { + AstNodeExpr* const fromp = selp->fromp(); + buildForcePathKeyRecurse(fromp, out); + out += "|T" + selp->name(); + return; } + nodep->v3fatalSrc("Unsupported: opaque force path selector " + << nodep->prettyTypeName()); } - AstNodeStmt* getForcedUpdateStmtsRecursep(AstNodeExpr* const lhsp, AstVarScope* const vscp, - AstVarRef* const lhsVarRefp, - std::vector& assigns) { - // Create stataments that update values of __Rd variable. - // lhsp is either a reference to that variable or ArraySel or MemberSel on it. - // lhsVarRefp is a reference to that variable in lhsp subtree. - // assigns is a vector to which all assignments to __Rd are added. - FileLine* const flp = lhsp->fileline(); - const AstNodeDType* const lhsDtypep = lhsp->dtypep()->skipRefp(); - if (lhsDtypep->isIntegralOrPacked() || VN_IS(lhsDtypep, BasicDType)) { - AstAssign* const assignp - = new AstAssign{flp, lhsp, forcedUpdate(vscp, lhsp, lhsVarRefp)}; - assigns.push_back(assignp); - return assignp; - } else if (const AstStructDType* const structDtypep - = VN_CAST(lhsDtypep, StructDType)) { - AstNodeStmt* stmtsp = nullptr; - bool firstIter = true; - for (AstMemberDType* mdtp = structDtypep->membersp(); mdtp; - mdtp = VN_AS(mdtp->nextp(), MemberDType)) { - AstNodeExpr* const lhsCopyp = firstIter ? lhsp : lhsp->cloneTreePure(false); - AstVarRef* const lhsVarRefCopyp - = firstIter ? lhsVarRefp : lhsVarRefp->clonep(); - AstStructSel* const structSelp = new AstStructSel{flp, lhsCopyp, mdtp->name()}; - structSelp->dtypep(mdtp); - AstNodeStmt* const memberStmtp - = getForcedUpdateStmtsRecursep(structSelp, vscp, lhsVarRefCopyp, assigns); - stmtsp = firstIter ? memberStmtp : stmtsp->addNext(memberStmtp); - firstIter = false; - } - return stmtsp; - } else if (const AstUnpackArrayDType* const arrayDtypep - = VN_CAST(lhsDtypep, UnpackArrayDType)) { - AstVar* const loopVarp - = new AstVar{flp, VVarType::MODULETEMP, - m_iterNames.get(m_rdVscp->varp()->name()), VFlagBitPacked{}, 32}; - m_rdVscp->varp()->addNext(loopVarp); - AstVarScope* const loopVarScopep - = new AstVarScope{flp, m_rdVscp->scopep(), loopVarp}; - m_rdVscp->addNext(loopVarScopep); - AstVarRef* const readRefp = new AstVarRef{flp, loopVarScopep, VAccess::READ}; - AstNodeStmt* const currInitp = new AstAssign{ - flp, new AstVarRef{flp, loopVarScopep, VAccess::WRITE}, new AstConst{flp, 0}}; - AstLoop* const currWhilep = new AstLoop{flp}; - currInitp->addNextHere(currWhilep); - AstLoopTest* const loopTestp = new AstLoopTest{ - flp, currWhilep, - new AstNeq{ - flp, readRefp, - new AstConst{flp, static_cast(arrayDtypep->elementsConst())}}}; - currWhilep->addStmtsp(loopTestp); - AstArraySel* const lhsSelp - = new AstArraySel{flp, lhsp, readRefp->cloneTree(false)}; - AstNodeStmt* const loopBodyp - = getForcedUpdateStmtsRecursep(lhsSelp, vscp, lhsVarRefp, assigns); - currWhilep->addStmtsp(loopBodyp); - AstAssign* const currIncrp = new AstAssign{ - flp, new AstVarRef{flp, loopVarScopep, VAccess::WRITE}, - new AstAdd{flp, readRefp->cloneTree(false), new AstConst{flp, 1}}}; - currWhilep->addStmtsp(currIncrp); - return currInitp; - } else { - lhsDtypep->v3fatalSrc("Unhandled type"); - return nullptr; // LCOV_EXCL_LINE - } + + static string forcePathKey(AstNodeExpr* nodep) { + string out; + buildForcePathKeyRecurse(nodep, out); + return out; } - static AstNodeExpr* wrapIntoExprp(AstVarRef* const refp, AstNodeExpr* const exprp, - AstVarRef* const varRefToReplacep) { - // Return a copy of exprp in which varRefToReplacep is replaced with refp - if (exprp == varRefToReplacep) { - return refp; - } else { - AstNodeExpr* const copiedExprp = exprp->cloneTreePure(false); - AstNode* const oldRefp = varRefToReplacep->clonep(); - varRefToReplacep->clonep()->replaceWith(refp); - oldRefp->deleteTree(); - return copiedExprp; - } + + public: + int getOrCreateForcePathIndex(AstNodeExpr* nodep) { + const auto pair + = m_forcePathToIndex.emplace(forcePathKey(nodep), m_nextForcePathIndex); + if (pair.second) ++m_nextForcePathIndex; + return pair.first->second; } - AstNodeExpr* forcedUpdate(AstVarScope* const vscp, AstNodeExpr* exprp = nullptr, - AstVarRef* const varRefToReplacep = nullptr) const { - FileLine* const flp = vscp->fileline(); - AstVarRef* origRefp = new AstVarRef{flp, vscp, VAccess::READ}; - ForceState::markNonReplaceable(origRefp); - AstNodeExpr* const origExprp = wrapIntoExprp(origRefp, exprp, varRefToReplacep); - AstNodeExpr* const enExprp = wrapIntoExprp(new AstVarRef{flp, m_enVscp, VAccess::READ}, - exprp, varRefToReplacep); - AstNodeExpr* const valExprp = wrapIntoExprp( - new AstVarRef{flp, m_valVscp, VAccess::READ}, exprp, varRefToReplacep); - if (ForceState::isRangedDType(vscp)) { - return new AstOr{ - flp, new AstAnd{flp, enExprp, valExprp}, - new AstAnd{flp, new AstNot{flp, enExprp->cloneTreePure(false)}, origExprp}}; - } - return new AstCond{flp, enExprp, valExprp, origExprp}; + + int findForcePathIndex(AstNodeExpr* nodep) const { + const auto it = m_forcePathToIndex.find(forcePathKey(nodep)); + return it != m_forcePathToIndex.end() ? it->second : -1; } }; + struct ArraySelInfo final { + std::vector m_sels; + bool m_hasBitSel = false; + }; + + struct ForceRangeInfo final : ForceRange { + bool m_hasArraySel = false; + ArraySelInfo m_arrayInfo; + }; + private: // NODE STATE - // AstNodeDType::user1p -> AstNodeDType*, dtype created for __En variables - // AstVar::user1p -> ForceComponentsVar* instance (via m_forceComponentsVar) - // AstVarScope::user1p -> ForceComponentsVarScope* instance (via m_forceComponentsVarScope) - // AstVarRef::user2 -> Flag indicating not to replace reference - // AstAssign::user2 -> Flag indicating that assignment was created for AstRelease - // AstVarScope::user3p -> AstAssign*, the assignment __VforceVal = + // AstVarRef::user1 -> Flag indicating not to replace reference + // AstAssignForce::user2 -> true if force is synthetic (externally forceable) const VNUser1InUse m_user1InUse; const VNUser2InUse m_user2InUse; - const VNUser3InUse m_user3InUse; - AstUser1Allocator m_forceComponentsVar; - AstUser1Allocator m_forceComponentsVarScope; - std::unordered_map, std::vector>> - m_valVscps; - // `valVscp` force components of a forced RHS - static size_t checkIfDTypeSupportedRecurse(const AstNodeDType* const dtypep, - const AstVar* const varp) { - // Checks if force stmt is supported on all subtypes - // and returns number of unpacked elements - const AstNodeDType* const dtp = dtypep->skipRefp(); - if (const AstUnpackArrayDType* const udtp = VN_CAST(dtp, UnpackArrayDType)) { - const size_t elemsInSubDType = checkIfDTypeSupportedRecurse(udtp->subDTypep(), varp); - return udtp->elementsConst() * elemsInSubDType; - } else if (const AstStructDType* const sdtp = VN_CAST(dtp, StructDType)) { - size_t elemCount = 0; - for (const AstMemberDType* mdtp = sdtp->membersp(); mdtp; - mdtp = VN_AS(mdtp->nextp(), MemberDType)) { - elemCount += checkIfDTypeSupportedRecurse(mdtp->subDTypep(), varp); - } - return elemCount; - } else if (const AstBasicDType* const bdtp = VN_CAST(dtp, BasicDType)) { - if (bdtp->isString() || bdtp->isEvent() || bdtp->keyword() == VBasicDTypeKwd::CHANDLE - || bdtp->keyword() == VBasicDTypeKwd::TIME) { - varp->v3warn(E_UNSUPPORTED, "Forcing variable of unsupported type: " - << varp->dtypep()->prettyTypeName()); - } - return 1; - } else if (!dtp->isIntegralOrPacked()) { - varp->v3warn(E_UNSUPPORTED, "Forcing variable of unsupported type: " - << varp->dtypep()->prettyTypeName()); - return 1; - } else { - // All packed types are supported - return 1; - } - } - static AstNodeDType* getEnVarpDTypep(AstVar* const varp) { - AstNodeDType* const origDTypep = varp->dtypep()->skipRefp(); - if (origDTypep->user1p()) return VN_AS(origDTypep->user1p(), NodeDType); - const size_t unpackElemNum = checkIfDTypeSupportedRecurse(origDTypep, varp); - if (unpackElemNum > ELEMENTS_MAX) { - varp->v3warn(E_UNSUPPORTED, "Unsupported: Force of variable with " - ">= " - << ELEMENTS_MAX << " unpacked elements"); - return origDTypep; - } - return getEnVarpDTypeRecursep(varp, origDTypep); - } - static AstNodeDType* getEnVarpDTypeRecursep(AstVar* const varp, AstNodeDType* const dtypep) { - if (dtypep->user1p()) return VN_AS(dtypep->user1p(), NodeDType); - if (AstNodeArrayDType* const arrp = VN_CAST(dtypep, NodeArrayDType)) { - AstNodeDType* const subDTypep = arrp->subDTypep()->skipRefp(); - AstNodeDType* const enSubDTypep = getEnVarpDTypeRecursep(varp, subDTypep); - if (subDTypep != enSubDTypep) { - AstNodeArrayDType* enArrp; - if (VN_IS(arrp, UnpackArrayDType)) { - enArrp = new AstUnpackArrayDType{arrp->fileline(), enSubDTypep, - arrp->rangep()->cloneTree(false)}; - } else if (VN_IS(arrp, PackArrayDType)) { - enArrp = new AstPackArrayDType{arrp->fileline(), enSubDTypep, - arrp->rangep()->cloneTree(false)}; - } else { - varp->v3fatalSrc("Unsupported: Force of variable of unhandled data type"); - return dtypep; - } - dtypep->user1p(enArrp); - v3Global.rootp()->typeTablep()->addTypesp(enArrp); - return enArrp; - } else { - dtypep->user1p(dtypep); - return dtypep; - } - } else if (AstBasicDType* const basicp = VN_CAST(dtypep, BasicDType)) { - if (basicp->isBit()) { - dtypep->user1p(dtypep); - return dtypep; - } else { - AstNodeDType* const bitDtp = varp->findBitRangeDType( - basicp->declRange(), basicp->elements(), VSigning::UNSIGNED); - dtypep->user1p(bitDtp); - return bitDtp; - } - } else if (AstNodeUOrStructDType* const structp = VN_CAST(dtypep, NodeUOrStructDType)) { - std::vector enMemberDTypes; - bool changed = false; - for (AstMemberDType* mdtp = structp->membersp(); mdtp; - mdtp = VN_AS(mdtp->nextp(), MemberDType)) { - AstNodeDType* const subMdtp = mdtp->subDTypep()->skipRefp(); - AstNodeDType* const enSubMdtp = getEnVarpDTypeRecursep(varp, subMdtp); - if (subMdtp != enSubMdtp) { - changed = true; - AstMemberDType* const enMdtp - = new AstMemberDType{mdtp->fileline(), mdtp->name(), enSubMdtp}; - enMdtp->dtypep(enSubMdtp); - enMemberDTypes.push_back(enMdtp); - } else { - enMemberDTypes.push_back(mdtp->cloneTreePure(false)); - } - } - if (changed) { - const bool packed = structp->packed(); - AstNodeUOrStructDType* enStructp; - if (VN_IS(structp, StructDType)) { - enStructp = new AstStructDType{structp->fileline(), - packed ? VSigning::SIGNED : VSigning::NOSIGN}; - } else if (VN_IS(structp, UnionDType) && packed) { - const AstUnionDType* const unionp = VN_AS(structp, UnionDType); - enStructp = new AstUnionDType{unionp->fileline(), unionp->isSoft(), - unionp->isTagged(), VSigning::SIGNED}; - } else { - varp->v3fatalSrc("Unsupported: Force of variable of unhandled data type"); - return dtypep; - } - int width = 0; - if (packed) { - for (const auto& memberp : enMemberDTypes) { - enStructp->addMembersp(memberp); - const int memberWidth = memberp->width(); - if (VN_IS(structp, StructDType)) { - width += memberWidth; - } else { - width = std::max(width, memberWidth); - } - } - } else { - for (const auto& memberp : enMemberDTypes) enStructp->addMembersp(memberp); - width = 1; - } - v3Global.rootp()->typeTablep()->addTypesp(enStructp); - enStructp->name(structp->name() + "__VforceEn_t"); - enStructp->dtypep(enStructp); - enStructp->widthForce(width, width); - enStructp->classOrPackagep(structp->classOrPackagep()); - dtypep->user1p(enStructp); - AstTypedef* const typedefp - = new AstTypedef{enStructp->fileline(), enStructp->name(), enStructp, - VN_IS(enStructp->classOrPackagep(), Class)}; - varp->addNextHere(typedefp); - return enStructp; - } else { - for (const auto& memberp : enMemberDTypes) memberp->deleteTree(); - dtypep->user1p(dtypep); - return dtypep; - } - } - varp->v3fatalSrc("Unsupported: Force of variable of unhandled data type"); - return dtypep; - } + std::unordered_map m_varInfo; public: - // CONSTRUCTORS ForceState() = default; VL_UNCOPYABLE(ForceState); // STATIC METHODS - static bool isRangedDType(const AstNode* const nodep) { - // If ranged we need a multibit enable to support bit-by-bit part-select forces, - // otherwise forcing a real or other opaque dtype and need a single bit enable. + static AstConst* makeZeroConst(AstNode* nodep, int width) { + V3Number zero{nodep, width}; + zero.setAllBits0(); + return new AstConst{nodep->fileline(), zero}; + } + + static AstConst* makeConst32(FileLine* flp, int value) { + return new AstConst{flp, AstConst::WidthedValue{}, 32, static_cast(value)}; + } + + static AstConst* makeRangeMaskConst(AstNode* nodep, int width, int lsb, int msb) { + V3Number mask{nodep, width}; + mask.setAllBits0(); + for (int bit = lsb; bit <= msb; ++bit) mask.setBit(bit, 1); + return new AstConst{nodep->fileline(), mask}; + } + + static AstNodeExpr* zeroPadToBaseWidth(AstNodeExpr* exprp, int baseWidth, int padLsb, + int padMsb) { + if (baseWidth <= 0) return exprp; + const int lowPad = padLsb; + const int highPad = baseWidth - (padMsb + 1); + if (lowPad > 0) { + exprp = new AstConcat{exprp->fileline(), exprp, makeZeroConst(exprp, lowPad)}; + } + if (highPad > 0) { + exprp = new AstConcat{exprp->fileline(), makeZeroConst(exprp, highPad), exprp}; + } + return exprp; + } + + static bool isUnpackedArrayDType(const AstNodeDType* dtypep) { + return VN_IS(dtypep->skipRefp(), UnpackArrayDType); + } + + static bool isBitwiseDType(AstNode* nodep) { const AstBasicDType* const basicp = nodep->dtypep()->skipRefp()->basicp(); - return basicp && basicp->isRanged(); - } - static bool isNotReplaceable(const AstVarRef* const nodep) { return nodep->user2(); } - static void markNonReplaceable(AstVarRef* const nodep) { nodep->user2SetOnce(); } - - // Get all ValVscps for a VarScope - const std::vector* getValVscps(AstVarRef* const refp) const { - auto it = m_valVscps.find(refp->varScopep()); - if (it != m_valVscps.end()) return &(it->second.second); - return nullptr; + return basicp && !basicp->isDouble() && !basicp->isString() && !basicp->isOpaque(); } - // Add a ValVscp for a VarScope - void addValVscp(AstVarRef* const refp, AstVarScope* const valVscp) { - if (m_valVscps[refp->varScopep()].first.find(valVscp) - != m_valVscps[refp->varScopep()].first.end()) - return; - m_valVscps[refp->varScopep()].first.emplace(valVscp); - m_valVscps[refp->varScopep()].second.push_back(valVscp); + static AstNodeExpr* castToNodeDType(AstNodeExpr* exprp, AstNode* dtypeFromp) { + const AstNodeDType* const dtypep = dtypeFromp->dtypep()->skipRefp(); + const AstBasicDType* const basicp = dtypep->basicp(); + if (!basicp || basicp->isDouble() || basicp->isString() || basicp->isOpaque() + || dtypep->isWide() || isUnpackedArrayDType(dtypep)) { + return exprp; + } + return new AstCCast{exprp->fileline(), exprp, dtypeFromp}; } - // METHODS - const ForceComponentsVarScope& getForceComponents(AstVarScope* vscp) { - AstVar* const varp = vscp->varp(); - return m_forceComponentsVarScope(vscp, vscp, m_forceComponentsVar(varp, varp)); + static bool isNotReplaceable(const AstVarRef* const nodep) { return nodep->user1(); } + static void markNonReplaceable(AstVarRef* const nodep) { nodep->user1SetOnce(); } + + static bool isOpaquePathSelector(const AstNode* nodep) { + return VN_IS(nodep, Sel) || VN_IS(nodep, NodeSel) || VN_IS(nodep, StructSel); } - ForceComponentsVarScope* tryGetForceComponents(AstVarRef* nodep) const { - return m_forceComponentsVarScope.tryGet(nodep->varScopep()); + + static bool isOutermostOpaquePathSelector(const AstNode* nodep) { + if (!isOpaquePathSelector(nodep)) return false; + const AstNode* const backp = nodep->backp(); + return !backp || !isOpaquePathSelector(backp); } - void setValVscpAssign(AstVarScope* valVscp, AstAssign* rhsExpr) { valVscp->user3p(rhsExpr); } - AstAssign* getValVscpAssign(AstVarScope* valVscp) const { - return VN_CAST(valVscp->user3p(), Assign); + + static AstVarRef* getOneVarRef(AstNodeExpr* forceStmtp) { + AstNode* const basep = AstArraySel::baseFromp(forceStmtp, true); + if (AstSampled* sampledp = VN_CAST(basep, Sampled)) + if (AstNodeExpr* exprp = VN_CAST(sampledp->exprp(), NodeExpr)) + return getOneVarRef(exprp); + AstVarRef* const varRefp = VN_CAST(basep, VarRef); + UASSERT_OBJ(varRefp, forceStmtp, "`force` assignment has no VarRef on LHS"); + return varRefp; + } + + ForceRangeInfo getForceRangeInfo(AstNodeExpr* lhsp, AstVar* varp, + bool requireConstRangeSelect) { + ForceRangeInfo info; + info.m_arrayInfo = getArraySelInfo(lhsp); + info.m_hasArraySel = !info.m_arrayInfo.m_sels.empty(); + + info.m_padMsb = isBitwiseDType(varp) ? (varp->width() - 1) : 0; + + if (const AstSel* const outerSelp = VN_CAST(lhsp, Sel)) { + int totalLsb = 0; + for (AstNodeExpr* curp = lhsp; const AstSel* const selp = VN_CAST(curp, Sel); + curp = selp->fromp()) { + if (requireConstRangeSelect) { + UASSERT_OBJ(VN_IS(selp->lsbp(), Const), lhsp, + "Unsupported: force on non-const range select"); + } + totalLsb += selp->lsbConst(); + } + info.m_padLsb = totalLsb; + info.m_padMsb = totalLsb + outerSelp->widthConst() - 1; + } + + info.m_rangeLsb = info.m_padLsb; + info.m_rangeMsb = info.m_padMsb; + if (info.m_hasArraySel) { + // Unpacked array selections are tracked as a single flattened element index + // inside VlForceVec, regardless of how many unpacked dimensions the source uses. + std::vector indices; + indices.reserve(info.m_arrayInfo.m_sels.size()); + for (AstArraySel* const selp : info.m_arrayInfo.m_sels) { + UASSERT_OBJ(VN_IS(selp->bitp(), Const), selp, + "Unsupported: force on non-constant array select"); + indices.push_back(VN_AS(selp->bitp(), Const)->toSInt()); + } + info.m_rangeLsb = flattenIndex(indices, arraySelDimSizes(info.m_arrayInfo)); + + info.m_rangeMsb = info.m_rangeLsb; + } else if (isUnpackedArrayDType(varp->dtypep())) { + lhsp->v3fatalSrc("Whole unpacked-array force/release should have been lowered via " + "element selections"); + } + if (!isBitwiseDType(varp) && !info.m_hasArraySel && !VN_IS(lhsp, VarRef)) { + // Non-bitwise member/struct paths cannot use a real bit range, so map each distinct + // source path onto a synthetic index in VlForceVec and use that index consistently + // for force, release, and readback. + VarForceInfo& varInfo = getOrCreateVarInfo(varp); + const int index = varInfo.getOrCreateForcePathIndex(lhsp); + info.m_rangeLsb = index; + info.m_rangeMsb = index; + } + return info; + } + + AstNodeExpr* createForceReadCall(const VarForceInfo& varInfo, FileLine* flp, VCMethod method, + AstNodeExpr* originalExprp, AstNode* dtypeFromp, + AstNodeExpr* indexExprp) const { + UASSERT(varInfo.m_forceVecVscp, "No forceVec for forced variable"); + + originalExprp->foreach( + [](AstVarRef* const refp) { ForceState::markNonReplaceable(refp); }); + AstNodeExpr* const origValp = castToNodeDType(originalExprp, dtypeFromp); + + AstCMethodHard* const callp = new AstCMethodHard{ + flp, new AstVarRef{flp, varInfo.m_forceVecVscp, VAccess::READ}, method, origValp}; + if (indexExprp) callp->addPinsp(indexExprp); + callp->dtypeFrom(dtypeFromp); + return callp; + } + + AstNodeStmt* createForceRdUpdateStmt(const VarForceInfo& varInfo) const { + UASSERT(varInfo.m_forceRdVscp, "No forceRd for forced variable"); + UASSERT(varInfo.m_varVscp, "No base var scope for forced variable"); + FileLine* const flp = varInfo.m_varVscp->fileline(); + AstNodeExpr* readExprp = nullptr; + AstVarRef* const baseRefp = new AstVarRef{flp, varInfo.m_varVscp, VAccess::READ}; + markNonReplaceable(baseRefp); + AstNodeExpr* const enRefp = new AstVarRef{flp, varInfo.m_forceEnVscp, VAccess::READ}; + AstNodeExpr* const valRefp = new AstVarRef{flp, varInfo.m_forceValVscp, VAccess::READ}; + if (isBitwiseDType(varInfo.m_varVscp->varp())) { + readExprp = new AstOr{ + flp, new AstAnd{flp, enRefp, valRefp}, + new AstAnd{flp, new AstNot{flp, enRefp->cloneTreePure(false)}, baseRefp}}; + } else { + readExprp = new AstCond{flp, enRefp, valRefp, baseRefp}; + } + + return new AstAssign{flp, new AstVarRef{flp, varInfo.m_forceRdVscp, VAccess::WRITE}, + readExprp}; + } + + VarForceInfo& getOrCreateVarInfo(AstVar* varp) { return m_varInfo[varp]; } + + const VarForceInfo* getVarInfo(AstVar* varp) const { + const auto it = m_varInfo.find(varp); + return it != m_varInfo.end() ? &it->second : nullptr; + } + + void addForceAssignment(AstVar* varp, AstVarScope* vscp, AstNodeExpr* rhsExprp, + AstAssignForce* forceStmtp, int rangeLsb, int rangeMsb, int padLsb, + int padMsb, bool hasArraySel) { + v3Global.setUsesForce(); + varp->setForcedByCode(); + + VarForceInfo& info = getOrCreateVarInfo(varp); + if (!info.m_scopep) info.m_scopep = vscp->scopep(); + const int forceId = info.m_forces.size(); + FileLine* const flp = varp->fileline(); + AstScope* const scopep = vscp->scopep(); + // Allocate one force vector per variable, no matter how many individual force + // statements later target slices/elements of that variable. + if (!info.m_forceVecVscp) { + AstCDType* const forceVecDtypep = new AstCDType{flp, "VlForceVec"}; + v3Global.rootp()->typeTablep()->addTypesp(forceVecDtypep); + + AstVar* const forceVecVarp + = new AstVar{flp, VVarType::MEMBER, varp->name() + "__VforceVec", forceVecDtypep}; + forceVecVarp->funcLocal(false); + forceVecVarp->isInternal(true); + varp->addNextHere(forceVecVarp); + info.m_forceVecVscp = new AstVarScope{flp, scopep, forceVecVarp}; + scopep->addVarsp(info.m_forceVecVscp); + } + + info.m_forces.emplace(forceStmtp, ForceInfo{rangeLsb, rangeMsb, padLsb, padMsb, forceId, + hasArraySel, nullptr, rhsExprp}); + + UINFO(3, "Added force ID " << forceId << " for " << varp->name() << " [" << rangeMsb << ":" + << rangeLsb << "]\n"); + } + + static void collectArraySelInfo(AstNodeExpr* exprp, ArraySelInfo& info) { + if (const auto* const selp = VN_CAST(exprp, Sel)) { + info.m_hasBitSel = true; + collectArraySelInfo(selp->fromp(), info); + } else if (auto* const selp = VN_CAST(exprp, ArraySel)) { + collectArraySelInfo(selp->fromp(), info); + info.m_sels.push_back(selp); + } else if (const auto* const memberp = VN_CAST(exprp, StructSel)) { + collectArraySelInfo(memberp->fromp(), info); + } + } + + static ArraySelInfo getArraySelInfo(AstNodeExpr* exprp) { + ArraySelInfo info; + collectArraySelInfo(exprp, info); + return info; + } + + static std::vector arraySelDimSizes(const ArraySelInfo& info) { + std::vector dims; + dims.reserve(info.m_sels.size()); + for (AstArraySel* const selp : info.m_sels) { + AstNodeDType* const dtypep = selp->fromp()->dtypep()->skipRefp(); + const AstUnpackArrayDType* const arrayp = VN_CAST(dtypep, UnpackArrayDType); + UASSERT_OBJ(arrayp, selp, "Array select is not on unpacked array"); + dims.push_back(arrayp->declRange().elements()); + } + return dims; + } + + static int flattenIndex(const std::vector& indices, const std::vector& dimSizes) { + UASSERT(indices.size() == dimSizes.size(), "Array index and dimension size mismatch"); + int index = 0; + int stride = 1; + for (int i = static_cast(indices.size()) - 1; i >= 0; --i) { + index += indices[i] * stride; + stride *= dimSizes[i]; + } + return index; + } + + static AstNodeExpr* buildFlattenIndexExpr(FileLine* flp, const ArraySelInfo& info) { + const std::vector dimSizes = arraySelDimSizes(info); + std::vector constIndices; + constIndices.reserve(info.m_sels.size()); + for (AstArraySel* const selp : info.m_sels) { + constIndices.push_back(VN_AS(selp->bitp(), Const)->toSInt()); + } + return makeConst32(flp, flattenIndex(constIndices, dimSizes)); + } + + static AstNodeExpr* buildRhsDataExpr(FileLine* flp, const ForceInfo& finfo) { + UASSERT(finfo.m_rhsVarVscp, "RHS var scope not assigned"); + return new AstVarRef{flp, finfo.m_rhsVarVscp, VAccess::READ}; + } + + void finalizeRhsVars() { + for (auto& it : m_varInfo) { + AstVar* const varp = it.first; + VarForceInfo& info = it.second; + if (info.m_forces.empty()) continue; + + AstScope* const scopep = info.m_scopep; + UASSERT_OBJ(scopep, varp, "Missing scope for force RHS vars"); + + FileLine* const flp = varp->fileline(); + // Process force entries in stable force-id order. + std::vector forceps; + forceps.reserve(info.m_forces.size()); + for (auto& fit : info.m_forces) forceps.push_back(&fit.second); + std::sort(forceps.begin(), forceps.end(), + [](const ForceInfo* ap, const ForceInfo* bp) { + return ap->m_forceId < bp->m_forceId; + }); + + for (ForceInfo* const finfop : forceps) { + ForceInfo& finfo = *finfop; + UASSERT_OBJ(finfo.m_rhsExprp, varp, "Missing RHS expression for ForceInfo"); + + // Create per-force temporary storage for the captured RHS value. + AstVar* const rhsVarp + = new AstVar{flp, VVarType::VAR, + varp->name() + "__VforceRHS" + std::to_string(finfo.m_forceId), + finfo.m_rhsExprp->dtypep()}; + rhsVarp->noSubst(true); + rhsVarp->sigPublic(true); + rhsVarp->setForcedByCode(); + varp->addNextHere(rhsVarp); + finfo.m_rhsVarVscp = new AstVarScope{flp, scopep, rhsVarp}; + scopep->addVarsp(finfo.m_rhsVarVscp); + + // Build assignments for RHS capture. Public/forceable signals with __VforceRd + // already have an explicit force-read update path, so they do not need the + // forceVec.touch() ordering edge here. + // always_comb begin + // forceRHS[id] = rhsExpr; + // forceVec.touch(); // Only without __VforceRd + // end + AstAssign* const rhsAssignp = new AstAssign{ + flp, new AstVarRef{flp, finfo.m_rhsVarVscp, VAccess::WRITE}, finfo.m_rhsExprp}; + + if (!info.m_forceRdVscp) { + // touch() is intentionally a semantic no-op at runtime: it creates an + // explicit use/ordering edge from the RHS-capture logic to the force vector + // so later optimization/scheduling passes keep this update path connected. + AstCMethodHard* const touchCallp = new AstCMethodHard{ + flp, new AstVarRef{flp, info.m_forceVecVscp, VAccess::WRITE}, + VCMethod::FORCE_TOUCH}; + touchCallp->dtypeSetVoid(); + AstNodeStmt* const touchStmtp = touchCallp->makeStmt(); + rhsAssignp->addNextHere(touchStmtp); + } + + // Run both updates in a combinational always block. + AstAlways* const alwaysp + = new AstAlways{flp, VAlwaysKwd::ALWAYS, nullptr, rhsAssignp}; + AstSenTree* const senTreep + = new AstSenTree{flp, new AstSenItem{flp, AstSenItem::Combo{}}}; + AstActive* const activep = new AstActive{flp, "force-rhs-update", senTreep}; + activep->senTreeStorep(activep->sentreep()); + activep->addStmtsp(alwaysp); + scopep->addBlocksp(activep); + } + + if (info.m_forceRdVscp) { + AstActive* const activeInitp = new AstActive{ + flp, "force-init", + new AstSenTree{flp, new AstSenItem{flp, AstSenItem::Static{}}}}; + activeInitp->senTreeStorep(activeInitp->sentreep()); + AstAssign* const initEnp + = new AstAssign{flp, new AstVarRef{flp, info.m_forceEnVscp, VAccess::WRITE}, + makeZeroConst(varp, info.m_forceEnVscp->width())}; + initEnp->addNextHere(createForceRdUpdateStmt(info)); + activeInitp->addStmtsp(new AstInitial{flp, initEnp}); + scopep->addBlocksp(activeInitp); + + AstSenItem* itemsp = nullptr; + auto addSenItem = [&](AstVarScope* vscp) { + if (!vscp) return; + AstSenItem* const nextp = new AstSenItem{ + flp, VEdgeType::ET_CHANGED, new AstVarRef{flp, vscp, VAccess::READ}}; + if (itemsp) { + itemsp->addNext(nextp); + } else { + itemsp = nextp; + } + }; + addSenItem(info.m_forceEnVscp); + addSenItem(info.m_forceValVscp); + AstVarRef* const origSenRefp = new AstVarRef{flp, info.m_varVscp, VAccess::READ}; + markNonReplaceable(origSenRefp); + AstSenItem* const origItemp + = new AstSenItem{flp, VEdgeType::ET_CHANGED, origSenRefp}; + if (!itemsp) varp->v3fatalSrc("force-rd-update missing force-enable sen item"); + itemsp->addNext(origItemp); + for (ForceInfo* const finfop : forceps) addSenItem(finfop->m_rhsVarVscp); + + AstActive* const activep + = new AstActive{flp, "force-rd-update", new AstSenTree{flp, itemsp}}; + activep->senTreeStorep(activep->sentreep()); + activep->addStmtsp(new AstAlways{flp, VAlwaysKwd::ALWAYS, nullptr, + createForceRdUpdateStmt(info)}); + scopep->addBlocksp(activep); + } + } + } + + const ForceInfo& getForceInfo(AstAssignForce* forceStmtp) const { + AstVar* varp = getOneVarRef(forceStmtp->lhsp())->varp(); + auto it = m_varInfo.find(varp); + UASSERT(it != m_varInfo.end(), "Force info not found for variable"); + auto it2 = it->second.m_forces.find(forceStmtp); + UASSERT(it2 != it->second.m_forces.end(), "Force statement not found"); + return it2->second; + } + + AstNodeExpr* createForceReadExpression(const VarForceInfo& varInfo, + AstVarRef* originalRefp) const { + FileLine* const flp = originalRefp->fileline(); + return createForceReadCall(varInfo, flp, VCMethod::FORCE_READ, + originalRefp->cloneTreePure(false), originalRefp->varp(), + nullptr); + } + + AstNodeExpr* createForceReadIndexExpression(const VarForceInfo& varInfo, + AstNodeExpr* originalExprp, + AstNodeExpr* indexExprp) const { + FileLine* const flp = originalExprp->fileline(); + return createForceReadCall(varInfo, flp, VCMethod::FORCE_READ_INDEX, + originalExprp->cloneTreePure(false), originalExprp, indexExprp); + } + + static AstNodeExpr* rebuildSelPath(AstNodeExpr* pathp, AstNodeExpr* baseExprp) { + if (const AstSel* const selp = VN_CAST(pathp, Sel)) { + AstNodeExpr* const fromp = rebuildSelPath(selp->fromp(), baseExprp); + AstSel* const outp + = new AstSel{selp->fileline(), fromp, selp->lsbConst(), selp->widthConst()}; + outp->dtypeFrom(selp); + return outp; + } + return baseExprp; } }; -class ForceConvertVisitor final : public VNVisitor { - // STATE +//###################################################################### +// ForceDiscoveryVisitor - Discover force statements + +class ForceDiscoveryVisitor final : public VNVisitorConst { ForceState& m_state; - // STATIC METHODS - // Replace each AstNodeVarRef in the given 'nodep' that writes a variable by transforming the - // referenced AstVarScope with the given function. - static void transformWritenVarScopes(AstNode* nodep, - std::function f) { - UASSERT_OBJ(nodep->backp(), nodep, "Must have backp, otherwise will be lost if replaced"); - nodep->foreach([&f](AstNodeVarRef* refp) { - if (refp->access() != VAccess::WRITE) return; - // TODO: this is not strictly speaking safe for some complicated lvalues, eg.: - // 'force foo[a(cnt)] = 1;', where 'cnt' is an out parameter, but it will - // do for now... - refp->replaceWith( - new AstVarRef{refp->fileline(), f(refp->varScopep()), VAccess::WRITE}); - VL_DO_DANGLING(refp->deleteTree(), refp); - }); - } - - // VISITORS - void visit(AstNode* nodep) override { iterateChildren(nodep); } - void visit(AstAssignForce* nodep) override { - // The AstAssignForce node will be removed for sure - FileLine* const flp = nodep->fileline(); - AstNodeExpr* const lhsp = nodep->lhsp(); // The LValue we are forcing - AstNodeExpr* const rhsp = nodep->rhsp(); // The value we are forcing it to - VNRelinker relinker; - nodep->unlinkFrBack(&relinker); - VL_DO_DANGLING(pushDeletep(nodep), nodep); + if (nodep->user2()) return; // External force statements are pre-registered. + UINFO(2, "Discovering force statement: " << nodep << "\n"); - // Set corresponding enable signals to ones - V3Number ones{lhsp, ForceState::isRangedDType(lhsp) ? lhsp->width() : 1}; - ones.setAllBits1(); - AstAssign* const setEnp - = new AstAssign{flp, lhsp->cloneTreePure(false), new AstConst{rhsp->fileline(), ones}}; - transformWritenVarScopes(setEnp->lhsp(), [this](AstVarScope* vscp) { - return m_state.getForceComponents(vscp).m_enVscp; - }); - // Set corresponding value signals to the forced value - AstAssign* const setValp - = new AstAssign{flp, lhsp->cloneTreePure(false), rhsp->cloneTreePure(false)}; - transformWritenVarScopes(setValp->lhsp(), [this, rhsp, setValp](AstVarScope* vscp) { - AstVarScope* const valVscp = m_state.getForceComponents(vscp).m_valVscp; - m_state.setValVscpAssign(valVscp, setValp); - rhsp->foreach([valVscp, this](AstVarRef* refp) { m_state.addValVscp(refp, valVscp); }); - return valVscp; - }); + AstVarRef* const lhsVarRefp = m_state.getOneVarRef(nodep->lhsp()); + AstVar* const forcedVarp = lhsVarRefp->varp(); + UASSERT(forcedVarp, "VarRef missing Varp"); - // Set corresponding read signal directly as well, in case something in the same - // process reads it later - AstAssign* const setRdp = new AstAssign{flp, lhsp->unlinkFrBack(), rhsp->unlinkFrBack()}; - transformWritenVarScopes(setRdp->lhsp(), [this](AstVarScope* vscp) { - return m_state.getForceComponents(vscp).m_rdVscp; - }); + // Resolve force bookkeeping range/padding for the LHS shape. + ForceState::ForceRangeInfo rangeInfo + = m_state.getForceRangeInfo(nodep->lhsp(), forcedVarp, true); - setEnp->addNext(setValp); - setEnp->addNext(setRdp); - relinker.relink(setEnp); - } + // Start from a cloned RHS expression; adjust below for partial bit selects. + const AstSel* const selLhsp = VN_CAST(nodep->lhsp(), Sel); + AstNodeExpr* rhsExprp = nodep->rhsp()->cloneTreePure(false); - void visit(AstRelease* nodep) override { - FileLine* const flp = nodep->fileline(); - AstNodeExpr* const lhsp = nodep->lhsp(); // The LValue we are releasing - // The AstRelease node will be removed for sure - VNRelinker relinker; - nodep->unlinkFrBack(&relinker); - VL_DO_DANGLING(pushDeletep(nodep), nodep); + // For bitwise selects inside arrays, merge updated bits with preserved base bits. + if (rangeInfo.m_hasArraySel && rangeInfo.m_arrayInfo.m_hasBitSel && selLhsp + && ForceState::isBitwiseDType(selLhsp->fromp())) { + AstNodeExpr* const baseExprp = selLhsp->fromp()->cloneTreePure(false); + baseExprp->foreach( + [](AstVarRef* const refp) { ForceState::markNonReplaceable(refp); }); - // Set corresponding enable signals to zero - V3Number zero{lhsp, ForceState::isRangedDType(lhsp) ? lhsp->width() : 1}; - zero.setAllBits0(); - AstAssign* const resetEnp - = new AstAssign{flp, lhsp->cloneTreePure(false), new AstConst{lhsp->fileline(), zero}}; - transformWritenVarScopes(resetEnp->lhsp(), [this](AstVarScope* vscp) { - return m_state.getForceComponents(vscp).m_enVscp; - }); + // Pad the selected value back to full base width before masking/or-ing. + rhsExprp = ForceState::zeroPadToBaseWidth(rhsExprp, selLhsp->fromp()->width(), + rangeInfo.m_padLsb, rangeInfo.m_padMsb); - // IEEE 1800-2023 10.6.2: When released, then if the variable is not driven by a continuous - // assignment and does not currently have an active procedural continuous assignment, the - // variable shall not immediately change value and shall maintain its current value until - // the next procedural assignment to the variable is executed. Releasing a variable that is - // driven by a continuous assignment or currently has an active assign procedural - // continuous assignment shall reestablish that assignment and schedule a reevaluation in - // the continuous assignment's scheduling region. - AstAssign* const resetRdp - = new AstAssign{flp, lhsp->unlinkFrBack(), lhsp->cloneTreePure(false)}; - resetRdp->user2(true); - AstVarRef* const refp = VN_AS(AstNodeVarRef::varRefLValueRecurse(lhsp), VarRef); - AstVarScope* const vscp = refp->varScopep(); - AstVarRef* const rhsRefp = refp->clonep(); - - if (vscp->varp()->isContinuously()) { - AstVarRef* const lhsRefp = new AstVarRef{ - refp->fileline(), m_state.getForceComponents(vscp).m_rdVscp, VAccess::WRITE}; - refp->replaceWith(lhsRefp); - VL_DO_DANGLING(refp->deleteTree(), refp); - rhsRefp->access(VAccess::READ); - ForceState::markNonReplaceable(rhsRefp); - } else { - if (rhsRefp->dtypep()->skipRefp()->isIntegralOrPacked()) { - // In this case var ref can be replaced with expression - rhsRefp->replaceWith(m_state.getForceComponents(vscp).forcedUpdate(vscp)); - VL_DO_DANGLING(rhsRefp->deleteTree(), rhsRefp); - } else { - AstNodeExpr* const origRhsp = resetRdp->rhsp(); - origRhsp->replaceWith( - m_state.getForceComponents(vscp).forcedUpdate(vscp, origRhsp, rhsRefp)); - VL_DO_DANGLING(origRhsp->deleteTree(), origRhsp); - } + // Keep untouched base bits and insert the newly forced bit range. + // rhsExpr = (baseExpr & ~mask(range)) | (zeroPad(force_rhs) & mask(range)); + AstConst* const maskConstp = ForceState::makeRangeMaskConst( + nodep->lhsp(), selLhsp->fromp()->width(), rangeInfo.m_padLsb, rangeInfo.m_padMsb); + AstNodeExpr* const maskedOldp + = new AstAnd{nodep->lhsp()->fileline(), baseExprp, + new AstNot{nodep->lhsp()->fileline(), maskConstp}}; + rhsExprp = new AstOr{nodep->lhsp()->fileline(), maskedOldp, rhsExprp}; } - resetRdp->addNext(resetEnp); - relinker.relink(resetRdp); + m_state.addForceAssignment(forcedVarp, lhsVarRefp->varScopep(), rhsExprp, nodep, + rangeInfo.m_rangeLsb, rangeInfo.m_rangeMsb, rangeInfo.m_padLsb, + rangeInfo.m_padMsb, rangeInfo.m_hasArraySel); } void visit(AstVarScope* nodep) override { - // If this signal is marked externally forceable, create the public force signals if (nodep->varp()->isForceable()) { if (VN_IS(nodep->varp()->dtypeSkipRefp(), UnpackArrayDType)) { nodep->varp()->v3warn( @@ -576,43 +609,327 @@ class ForceConvertVisitor final : public VNVisitor { } const AstBasicDType* const bdtypep = nodep->varp()->basicp(); - const bool strtype = bdtypep && bdtypep->keyword() == VBasicDTypeKwd::STRING; - if (strtype) { + if (bdtypep && bdtypep->keyword() == VBasicDTypeKwd::STRING) { nodep->varp()->v3error( "Forcing strings is not permitted: " << nodep->varp()->name()); } - const ForceState::ForceComponentsVarScope& fc = m_state.getForceComponents(nodep); - fc.m_enVscp->varp()->sigUserRWPublic(true); - fc.m_valVscp->varp()->sigUserRWPublic(true); + // Create per-signal storage for force enable/value state. + AstVar* const varp = nodep->varp(); + FileLine* const flp = varp->fileline(); + AstVar* const rdVarp + = new AstVar{flp, VVarType::WIRE, varp->name() + "__VforceRd", varp->dtypep()}; + rdVarp->noSubst(true); + rdVarp->sigPublic(true); + AstNodeDType* const enDtypep + = ForceState::isBitwiseDType(varp) ? varp->dtypep() : varp->findBitDType(); + AstVar* const enVarp + = new AstVar{flp, VVarType::VAR, varp->name() + "__VforceEn", enDtypep}; + enVarp->sigUserRWPublic(true); + AstVar* const valVarp + = new AstVar{flp, VVarType::VAR, varp->name() + "__VforceVal", varp->dtypep()}; + valVarp->sigUserRWPublic(true); + varp->addNextHere(rdVarp); + varp->addNextHere(enVarp); + varp->addNextHere(valVarp); + AstVarScope* const rdVscp = new AstVarScope{flp, nodep->scopep(), rdVarp}; + AstVarScope* const enVscp = new AstVarScope{flp, nodep->scopep(), enVarp}; + AstVarScope* const valVscp = new AstVarScope{flp, nodep->scopep(), valVarp}; + nodep->scopep()->addVarsp(rdVscp); + nodep->scopep()->addVarsp(enVscp); + nodep->scopep()->addVarsp(valVscp); + + // Register force metadata so later transforms can find these helper vars. + ForceState::VarForceInfo& info = m_state.getOrCreateVarInfo(varp); + info.m_forceRdVscp = rdVscp; + info.m_forceEnVscp = enVscp; + info.m_forceValVscp = valVscp; + info.m_varVscp = nodep; + + // Build an update block triggered by force-enable changes. + AstSenItem* const itemsp = new AstSenItem{flp, VEdgeType::ET_CHANGED, + new AstVarRef{flp, enVscp, VAccess::READ}}; + AstActive* const activep + = new AstActive{flp, "force-update", new AstSenTree{flp, itemsp}}; + activep->senTreeStorep(activep->sentreep()); + + // Build expression selecting forced value when enabled, otherwise original value. + // forceExpr = (isBitwise) ? ((en & val) | (~en & orig)) : (en ? val : orig); + AstVarRef* const refp = new AstVarRef{flp, nodep, VAccess::READ}; + ForceState::markNonReplaceable(refp); + AstVarRef* const enRefp = new AstVarRef{flp, enVscp, VAccess::READ}; + AstVarRef* const valRefp = new AstVarRef{flp, valVscp, VAccess::READ}; + const AstBasicDType* const basicp = varp->dtypep()->skipRefp()->basicp(); + AstNodeExpr* const forceExprp + = basicp && basicp->isRanged() + ? static_cast(new AstOr{ + flp, new AstAnd{flp, enRefp, valRefp}, + new AstAnd{flp, new AstNot{flp, enRefp->cloneTreePure(false)}, refp}}) + : static_cast(new AstCond{flp, enRefp, valRefp, refp}); + AstAssignForce* const forceAssignp + = new AstAssignForce{flp, new AstVarRef{flp, nodep, VAccess::WRITE}, forceExprp}; + forceAssignp->user2(true); + activep->addStmtsp(new AstAlways{flp, VAlwaysKwd::ALWAYS, nullptr, forceAssignp}); + nodep->scopep()->addBlocksp(activep); + + // Clone the RHS for tracking and preserve original var refs as non-replaceable. + AstNodeExpr* const rhsClonep = forceExprp->cloneTreePure(false); + rhsClonep->foreach([varp](AstVarRef* const refp) { + if (refp->varp() == varp) ForceState::markNonReplaceable(refp); + }); + + // Compute full assignment range (including unpacked arrays) for force bookkeeping. + const bool bitwiseVar = ForceState::isBitwiseDType(varp); + const int padMsb = bitwiseVar ? (varp->width() - 1) : 0; + int rangeLsb = 0; + int rangeMsb = padMsb; + if (ForceState::isUnpackedArrayDType(varp->dtypep())) { + nodep->v3fatalSrc("Forceable unpacked arrays should have been rejected earlier"); + } + m_state.addForceAssignment(varp, nodep, rhsClonep, forceAssignp, rangeLsb, rangeMsb, 0, + padMsb, false); } + iterateChildrenConst(nodep); } + void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } + +public: + explicit ForceDiscoveryVisitor(AstNetlist* nodep, ForceState& state) + : m_state{state} { + iterateAndNextConstNull(nodep->modulesp()); + } +}; + +//###################################################################### +// ForceConvertVisitor - Convert force/release statements + +class ForceConvertVisitor final : public VNVisitor { + ForceState& m_state; + + void visit(AstAssignForce* nodep) override { + UINFO(2, "Converting force statement: " << nodep << "\n"); + + AstNodeExpr* const lhsp = nodep->lhsp(); + AstVarRef* const lhsVarRefp = m_state.getOneVarRef(lhsp); + AstVar* const forcedVarp = lhsVarRefp->varp(); + + const ForceState::ForceInfo& info = m_state.getForceInfo(nodep); + const ForceState::VarForceInfo* const varInfo = m_state.getVarInfo(forcedVarp); + UASSERT_OBJ(varInfo && varInfo->m_forceVecVscp, nodep, "Force info not set up"); + + FileLine* const flp = nodep->fileline(); + + // Assign RHS shadow value immediately so force takes effect in the same timestep. + UASSERT_OBJ(info.m_rhsVarVscp, nodep, "No RHS var for forced variable"); + AstAssign* const rhsAssignp + = new AstAssign{flp, new AstVarRef{flp, info.m_rhsVarVscp, VAccess::WRITE}, + nodep->rhsp()->cloneTreePure(false)}; + + AstAssign* valAssignp = nullptr; + AstAssign* enAssignp = nullptr; + const bool bitwiseForcedVar = ForceState::isBitwiseDType(forcedVarp); + // When an externally forceable signal is also forced in (System)Verilog code + // keep the public __VforceEn/__VforceVal signals in sync with the procedural force too. + // Don't do this for array selections; those are represented only in VlForceVec. + if (!nodep->user2() && varInfo->m_forceEnVscp && varInfo->m_forceValVscp + && !info.m_hasArraySel) { + AstNodeExpr* baseExprp = nodep->rhsp()->cloneTreePure(false); + baseExprp->foreach( + [](AstVarRef* const refp) { ForceState::markNonReplaceable(refp); }); + if (bitwiseForcedVar) { + baseExprp = ForceState::zeroPadToBaseWidth(baseExprp, forcedVarp->width(), + info.m_padLsb, info.m_padMsb); + } + if (bitwiseForcedVar) { + // forceVal = (forceVal & ~mask(range)) | (rhs_padded & mask(range)); + // forceEn = forceEn | mask(range); + AstConst* const maskConstp = ForceState::makeRangeMaskConst( + nodep, forcedVarp->width(), info.m_rangeLsb, info.m_rangeMsb); + AstNodeExpr* const valReadp + = new AstVarRef{flp, varInfo->m_forceValVscp, VAccess::READ}; + AstNodeExpr* const valWritep + = new AstVarRef{flp, varInfo->m_forceValVscp, VAccess::WRITE}; + AstNodeExpr* const notMaskp = new AstNot{flp, maskConstp}; + AstNodeExpr* const maskedOldp = new AstAnd{flp, valReadp, notMaskp}; + AstNodeExpr* const newValp = new AstOr{flp, maskedOldp, baseExprp}; + valAssignp = new AstAssign{flp, valWritep, newValp}; + + AstNodeExpr* const enReadp + = new AstVarRef{flp, varInfo->m_forceEnVscp, VAccess::READ}; + AstNodeExpr* const enWritep + = new AstVarRef{flp, varInfo->m_forceEnVscp, VAccess::WRITE}; + AstNodeExpr* const newEnp + = new AstOr{flp, enReadp, maskConstp->cloneTreePure(false)}; + enAssignp = new AstAssign{flp, enWritep, newEnp}; + } else { + AstConst* const oneConstp = ForceState::makeRangeMaskConst(nodep, 1, 0, 0); + AstNodeExpr* const rhsValp = ForceState::castToNodeDType(baseExprp, forcedVarp); + valAssignp = new AstAssign{ + flp, new AstVarRef{flp, varInfo->m_forceValVscp, VAccess::WRITE}, rhsValp}; + enAssignp = new AstAssign{ + flp, new AstVarRef{flp, varInfo->m_forceEnVscp, VAccess::WRITE}, oneConstp}; + } + } + + // Verilog pseudocode: + // forceVec.addForce(range_lsb, range_msb, &forceRHS[id], rhs_lsb); + AstNodeExpr* const rhsDatap = ForceState::buildRhsDataExpr(flp, info); + AstCExpr* const rhsAddrp = new AstCExpr{flp}; + rhsAddrp->add("&("); + rhsAddrp->add(rhsDatap); + rhsAddrp->add(")"); + AstCMethodHard* const addForceCallp = new AstCMethodHard{ + flp, new AstVarRef{flp, varInfo->m_forceVecVscp, VAccess::WRITE}, VCMethod::FORCE_ADD, + ForceState::makeConst32(flp, info.m_rangeLsb)}; + addForceCallp->addPinsp(ForceState::makeConst32(flp, info.m_rangeMsb)); + addForceCallp->addPinsp(rhsAddrp); + addForceCallp->addPinsp(ForceState::makeConst32(flp, info.m_rangeLsb)); + addForceCallp->dtypeSetVoid(); + AstNodeStmt* const stmtp = addForceCallp->makeStmt(); + + AstNode* tailp = rhsAssignp; + if (valAssignp) { + tailp->addNextHere(valAssignp); + tailp = valAssignp; + } + if (enAssignp) { + tailp->addNextHere(enAssignp); + tailp = enAssignp; + } + tailp->addNextHere(stmtp); + if (varInfo->m_forceRdVscp) { + stmtp->addNextHere(m_state.createForceRdUpdateStmt(*varInfo)); + } + nodep->replaceWith(rhsAssignp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } + + void visit(AstRelease* nodep) override { + UINFO(2, "Converting release statement: " << nodep << "\n"); + + AstNodeExpr* const lhsp = nodep->lhsp(); + AstVarRef* const lhsVarRefp = m_state.getOneVarRef(lhsp); + AstVar* const releasedVarp = lhsVarRefp->varp(); + + const ForceState::VarForceInfo* const varInfo = m_state.getVarInfo(releasedVarp); + if (!varInfo) { + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + return; + } + UASSERT_OBJ(!varInfo->m_forces.empty(), nodep, "Var info for variable with no forces"); + + FileLine* const flp = nodep->fileline(); + + const ForceState::ForceRangeInfo rangeInfo + = m_state.getForceRangeInfo(lhsp, releasedVarp, false); + + AstCMethodHard* const releaseCallp = new AstCMethodHard{ + flp, new AstVarRef{flp, varInfo->m_forceVecVscp, VAccess::WRITE}, + VCMethod::FORCE_RELEASE, ForceState::makeConst32(flp, rangeInfo.m_rangeLsb)}; + releaseCallp->addPinsp(ForceState::makeConst32(flp, rangeInfo.m_rangeMsb)); + releaseCallp->dtypeSetVoid(); + // forceVec.release(range_lsb, range_msb); + AstNodeStmt* const releasep = releaseCallp->makeStmt(); + + AstAssign* clearEnp = nullptr; + // Releases must also clear the external/public force-enable, but only for + // directly forceable variables and only for non-array-select cases that use that external + // force. + if (releasedVarp->isForceable() && varInfo->m_forceEnVscp && !rangeInfo.m_hasArraySel) { + AstNodeExpr* const enWritep + = new AstVarRef{flp, varInfo->m_forceEnVscp, VAccess::WRITE}; + if (ForceState::isBitwiseDType(releasedVarp)) { + const int varWidth = releasedVarp->width(); + if (rangeInfo.m_rangeLsb == 0 && rangeInfo.m_rangeMsb == varWidth - 1) { + clearEnp + = new AstAssign{flp, enWritep, ForceState::makeZeroConst(nodep, varWidth)}; + } else { + // forceEn = forceEn & ~mask(range); + AstNodeExpr* const enReadp + = new AstVarRef{flp, varInfo->m_forceEnVscp, VAccess::READ}; + AstConst* const maskConst = ForceState::makeRangeMaskConst( + nodep, varWidth, rangeInfo.m_rangeLsb, rangeInfo.m_rangeMsb); + AstNodeExpr* const newEnp + = new AstAnd{flp, enReadp, new AstNot{flp, maskConst}}; + clearEnp = new AstAssign{flp, enWritep, newEnp}; + } + } else { + clearEnp = new AstAssign{flp, enWritep, ForceState::makeZeroConst(nodep, 1)}; + } + } + + const AstSel* const selp = VN_CAST(lhsp, Sel); + AstNodeExpr* const basep = selp ? selp->fromp() : lhsp; + + AstNode* stmtListp = releasep; + if (clearEnp) { + clearEnp->addNextHere(stmtListp); + stmtListp = clearEnp; + } + + // IEEE 1800-2023 10.6.2: When released, if the variable is not continuously driven, + // it maintains its current value until the next procedural assignment. + if (!releasedVarp->isContinuously()) { + // Member/struct paths on non-bitwise types do not lower to a plain VarRef/bit range, + // so their current forced value is recovered via the same synthetic path index. + // if (!continuously_driven) lhs = force_read_current(lhs_path); + // forceVec.release(range); + const bool hasOpaquePath = !ForceState::isBitwiseDType(releasedVarp) + && !rangeInfo.m_hasArraySel && !VN_IS(lhsp, VarRef); + AstNodeExpr* forceReadp = nullptr; + if (hasOpaquePath) { + forceReadp = m_state.createForceReadIndexExpression( + *varInfo, lhsp, ForceState::makeConst32(flp, rangeInfo.m_rangeLsb)); + } else if (rangeInfo.m_hasArraySel) { + forceReadp = m_state.createForceReadIndexExpression( + *varInfo, basep, + ForceState::buildFlattenIndexExpr(flp, rangeInfo.m_arrayInfo)); + if (selp) { forceReadp = ForceState::rebuildSelPath(lhsp, forceReadp); } + } else { + forceReadp + = selp ? ForceState::rebuildSelPath( + lhsp, m_state.createForceReadExpression(*varInfo, lhsVarRefp)) + : m_state.createForceReadExpression(*varInfo, lhsVarRefp); + } + AstAssign* const assignp = new AstAssign{flp, lhsp->cloneTreePure(false), forceReadp}; + assignp->addNextHere(stmtListp); + stmtListp = assignp; + } + + if (varInfo->m_forceRdVscp) { + AstNode* tailp = stmtListp; + while (tailp->nextp()) tailp = tailp->nextp(); + tailp->addNextHere(m_state.createForceRdUpdateStmt(*varInfo)); + } + + nodep->replaceWith(stmtListp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } + + void visit(AstNode* nodep) override { iterateChildren(nodep); } + public: - // CONSTRUCTOR - // cppcheck-suppress constParameterCallback ForceConvertVisitor(AstNetlist* nodep, ForceState& state) : m_state{state} { - // Transform all force and release statements iterateAndNextNull(nodep->modulesp()); } }; +//###################################################################### +// ForceReplaceVisitor - Replace variable reads with force-aware reads + class ForceReplaceVisitor final : public VNVisitor { - // STATE const ForceState& m_state; AstNodeStmt* m_stmtp = nullptr; bool m_inLogic = false; - bool m_releaseRhs = false; // Inside RHS of assignment created for release statement - // METHODS - void iterateLogic(AstNode* logicp) { + void iterateLogic(AstNode* nodep) { VL_RESTORER(m_inLogic); m_inLogic = true; - iterateChildren(logicp); + iterateChildren(nodep); } - // VISITORS void visit(AstNodeStmt* nodep) override { VL_RESTORER(m_stmtp); m_stmtp = nodep; @@ -620,18 +937,14 @@ class ForceReplaceVisitor final : public VNVisitor { } void visit(AstAssign* nodep) override { VL_RESTORER(m_stmtp); - VL_RESTORER(m_releaseRhs); m_stmtp = nodep; iterate(nodep->lhsp()); - m_releaseRhs = nodep->user2(); iterate(nodep->rhsp()); } void visit(AstCFunc* nodep) override { iterateLogic(nodep); } void visit(AstCoverToggle* nodep) override { iterateLogic(nodep); } void visit(AstNodeProcedure* nodep) override { iterateLogic(nodep); } void visit(AstAlways* nodep) override { - // TODO: this is the old behavioud prior to moving AssignW under Always. - // Review if this is appropriate or if we are missing something... if (nodep->keyword() == VAlwaysKwd::CONT_ASSIGN) { iterateChildren(nodep); return; @@ -639,92 +952,136 @@ class ForceReplaceVisitor final : public VNVisitor { iterateLogic(nodep); } void visit(AstSenItem* nodep) override { iterateLogic(nodep); } + void visit(AstArraySel* nodep) override { + if (nodep->backp() && VN_IS(nodep->backp(), ArraySel)) { + // Only the outermost unpacked array selection should become a force-aware read; + // inner nested selections are folded into the final flattened index. + iterateChildren(nodep); + return; + } + + AstVarRef* const baseRefp = m_state.getOneVarRef(nodep); + AstVar* const varp = baseRefp->varp(); + const ForceState::VarForceInfo* const varInfo = m_state.getVarInfo(varp); + // Skip non-forceable reads, reads we intentionally protected earlier, and intermediate + // selections that still evaluate to an unpacked array rather than a scalar element. + if (ForceState::isNotReplaceable(baseRefp) || !varInfo + || !ForceState::isUnpackedArrayDType(varp->dtypep()) + || VN_IS(nodep->dtypep()->skipRefp(), UnpackArrayDType)) { + iterateChildren(nodep); + return; + } + + if (!baseRefp->access().isReadOnly()) { + iterateChildren(nodep); + return; + } + const ForceState::ArraySelInfo arrayInfo = ForceState::getArraySelInfo(nodep); + AstNodeExpr* const indexExprp + = ForceState::buildFlattenIndexExpr(nodep->fileline(), arrayInfo); + AstNodeExpr* const readExprp + = m_state.createForceReadIndexExpression(*varInfo, nodep, indexExprp); + nodep->replaceWith(readExprp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } + void visit(AstVarRef* nodep) override { if (ForceState::isNotReplaceable(nodep)) return; + if (nodep->backp() && VN_IS(nodep->backp(), ArraySel)) return; - switch (nodep->access()) { - case VAccess::READ: { - // Replace VarRef from forced LHS with rdVscp. - if (ForceState::ForceComponentsVarScope* const fcp - = m_state.tryGetForceComponents(nodep)) { - nodep->varp(fcp->m_rdVscp->varp()); - nodep->varScopep(fcp->m_rdVscp); - } - break; - } - case VAccess::WRITE: { - if (!m_inLogic) return; - // Emit rdVscp update after each write to any VarRef on forced LHS. - if (ForceState::ForceComponentsVarScope* const fcp - = m_state.tryGetForceComponents(nodep)) { - FileLine* const flp = nodep->fileline(); - AstVarRef* const lhsRefp = new AstVarRef{flp, fcp->m_rdVscp, VAccess::WRITE}; - AstNodeExpr* lhsp; - AstNodeExpr* rhsp; - if (nodep->dtypep()->skipRefp()->isIntegralOrPacked()) { - rhsp = fcp->forcedUpdate(nodep->varScopep()); - lhsp = lhsRefp; - } else { - AstNodeExpr* wholeExprp = nodep; - while (VN_IS(wholeExprp->backp(), NodeExpr)) { - wholeExprp = VN_AS(wholeExprp->backp(), NodeExpr); - // wholeExprp should never be ExprStmt, because: - // * if nodep is inside stmtsp() of one, we should sooner get NodeStmt node - // * nodep should never be in resultp(), because it is a WRITE reference - // and resultp() should be an rvalue - UASSERT_OBJ(!VN_IS(wholeExprp, ExprStmt), nodep, "Unexpected AstExprStmt"); - } - lhsp = ForceState::ForceComponentsVarScope::wrapIntoExprp(lhsRefp, wholeExprp, - nodep); - rhsp = fcp->forcedUpdate(nodep->varScopep(), wholeExprp, nodep); + AstVar* const varp = nodep->varp(); + const ForceState::VarForceInfo* const varInfo = m_state.getVarInfo(varp); + if (!varInfo) return; + + if (varInfo->m_forceRdVscp) { + if (nodep->access().isRW()) { + if (m_inLogic) { + nodep->v3warn(E_UNSUPPORTED, + "Unsupported: Signals used via read-write reference cannot be " + "forced"); } - m_stmtp->addNextHere(new AstAssign{flp, lhsp, rhsp}); + return; } - // Emit valVscp update after each write to any VarRef on forced RHS. - if (!m_state.getValVscps(nodep)) break; - for (AstVarScope* const valVscp : *m_state.getValVscps(nodep)) { - FileLine* const flp = nodep->fileline(); - AstAssign* assignp = m_state.getValVscpAssign(valVscp); - UASSERT_OBJ(assignp, flp, "Missing stored assignment for forced valVscp"); - - assignp = assignp->cloneTreePure(false); - - assignp->rhsp()->foreach( - [](AstVarRef* refp) { ForceState::markNonReplaceable(refp); }); - - m_stmtp->addNextHere(assignp); + if (nodep->access().isReadOnly()) { + nodep->varp(varInfo->m_forceRdVscp->varp()); + nodep->varScopep(varInfo->m_forceRdVscp); + return; } - break; + if (m_inLogic && m_stmtp) { + m_stmtp->addNextHere(m_state.createForceRdUpdateStmt(*varInfo)); + } + return; } - default: - if (!m_inLogic) return; - if (m_state.tryGetForceComponents(nodep) || m_state.getValVscps(nodep)) { - nodep->v3warn( - E_UNSUPPORTED, - "Unsupported: Signals used via read-write reference cannot be forced"); - } - break; + + // For opaque member/struct paths we rewrite the outer path node instead of the base + // VarRef, so leave the base reference alone and let visit(AstNode*) handle it. + if (nodep->backp() && (VN_IS(nodep->backp(), Sel) || VN_IS(nodep->backp(), StructSel)) + && !ForceState::isBitwiseDType(nodep->varp()) + && !ForceState::isUnpackedArrayDType(nodep->varp()->dtypep())) { + return; + } + + UASSERT_OBJ(!varInfo->m_forces.empty(), nodep, "Var info for variable with no forces"); + + if (nodep->access().isRW()) { + nodep->v3warn(E_UNSUPPORTED, + "Unsupported: Signals used via read-write reference cannot be forced"); + } else if (nodep->access().isReadOnly()) { + ForceState::markNonReplaceable(nodep); + AstNodeExpr* const readExprp = m_state.createForceReadExpression(*varInfo, nodep); + nodep->replaceWith(readExprp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); } } - void visit(AstNode* nodep) override { iterateChildren(nodep); } + void visit(AstNode* nodep) override { + if (ForceState::isOutermostOpaquePathSelector(nodep)) { + // Handle the whole opaque path at its outermost node so we can assign one stable + // synthetic force-path index to the full selection/member chain. + AstNodeExpr* const exprp = VN_AS(nodep, NodeExpr); + AstNode* const basep = AstArraySel::baseFromp(exprp, true); + AstVarRef* const baseRefp = VN_CAST(basep, VarRef); + if (baseRefp) { + AstVar* const varp = baseRefp->varp(); + if (!ForceState::isBitwiseDType(varp) + && !ForceState::isUnpackedArrayDType(varp->dtypep())) { + const ForceState::VarForceInfo* const varInfo = m_state.getVarInfo(varp); + if (!ForceState::isNotReplaceable(baseRefp) && varInfo) { + const int forcePathIndex = varInfo->findForcePathIndex(exprp); + if (forcePathIndex >= 0) { + if (!baseRefp->access().isReadOnly()) return; + AstNodeExpr* const readExprp = m_state.createForceReadIndexExpression( + *varInfo, exprp, + ForceState::makeConst32(nodep->fileline(), forcePathIndex)); + nodep->replaceWith(readExprp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } + } + } + } + } + iterateChildren(nodep); + } public: - // CONSTRUCTOR explicit ForceReplaceVisitor(AstNetlist* nodep, const ForceState& state) : m_state{state} { - iterateChildren(nodep); + iterateAndNextNull(nodep->modulesp()); } }; //###################################################################### // +//###################################################################### +// V3Force - Main entry point + void V3Force::forceAll(AstNetlist* nodep) { - UINFO(2, __FUNCTION__ << ":"); + UINFO(2, __FUNCTION__ << ":\n"); if (!v3Global.hasForceableSignals()) return; - { - ForceState state; - { ForceConvertVisitor{nodep, state}; } - { ForceReplaceVisitor{nodep, state}; } - V3Global::dumpCheckGlobalTree("force", 0, dumpTreeEitherLevel() >= 3); - } + ForceState state; + { ForceDiscoveryVisitor{nodep, state}; } + state.finalizeRhsVars(); + { ForceConvertVisitor{nodep, state}; } + { ForceReplaceVisitor{nodep, state}; } + V3Global::dumpCheckGlobalTree("force", 0, dumpTreeEitherLevel() >= 3); } diff --git a/src/V3FsmDetect.cpp b/src/V3FsmDetect.cpp new file mode 100644 index 000000000..3a9d48877 --- /dev/null +++ b/src/V3FsmDetect.cpp @@ -0,0 +1,788 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: FSM coverage detect pass +// +// 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: 2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +// FSM COVERAGE DETECT: +// Walk clocked always blocks while the original FSM structure is still +// present, build a per-FSM V3Graph representation of the extracted +// states/transitions, then immediately lower that completed graph state +// into the final coverage declarations, previous-state tracking, and +// active blocks needed to implement FSM state and arc coverage in the +// generated model. +// +//************************************************************************* + +#include "V3PchAstNoMT.h" + +#include "V3FsmDetect.h" + +#include "V3Ast.h" +#include "V3Graph.h" + +#include +#include +#include +#include + +VL_DEFINE_DEBUG_FUNCTIONS; + +namespace { + +// Captures one sensitivity-list entry so the lowering phase can later rebuild +// an active block with the same triggering event control. +struct FsmSenDesc final { + // Encoded edge kind copied from AstSenItem::edgeType() so lowering can + // rebuild the same trigger semantics on the synthesized coverage block. + VEdgeType::en edgeType = static_cast(0); + // Triggering signal in the saved scoped AST. + AstVarScope* varScopep = nullptr; +}; + +// Captures the simple reset predicate shape that survives to this pass after +// earlier normalization so reset arcs can be reconstructed during lowering. +struct FsmResetCondDesc final { + // Reset signal used by the FSM in the saved scoped AST. + AstVarScope* varScopep = nullptr; +}; + +class FsmGraph; + +class FsmVertex VL_NOT_FINAL : public V3GraphVertex { + VL_RTTI_IMPL(FsmVertex, V3GraphVertex) + +public: + enum class Kind : uint8_t { STATE, RESET_ANY, DEFAULT_ANY }; + +private: + Kind m_kind; // State vs synthetic ANY/default vertex role. + string m_label; // User-facing state or pseudo-state label. + int m_value = 0; // Encoded state value for real state vertices. + +protected: + FsmVertex(V3Graph* graphp, Kind kind, string label, int value) VL_MT_DISABLED + : V3GraphVertex{graphp}, + m_kind{kind}, + m_label{label}, + m_value{value} {} + ~FsmVertex() override = default; + +public: + Kind kind() const { return m_kind; } + bool isState() const { return m_kind == Kind::STATE; } + bool isResetAny() const { return m_kind == Kind::RESET_ANY; } + bool isDefaultAny() const { return m_kind == Kind::DEFAULT_ANY; } + const string& label() const { return m_label; } + int value() const { return m_value; } + + string name() const override VL_MT_SAFE { return m_label + "=" + cvtToStr(m_value); } +}; + +class FsmStateVertex final : public FsmVertex { + VL_RTTI_IMPL(FsmStateVertex, FsmVertex) + +public: + FsmStateVertex(V3Graph* graphp, string label, int value) VL_MT_DISABLED + : FsmVertex{graphp, Kind::STATE, label, value} {} + ~FsmStateVertex() override = default; + + string dotColor() const override { return "lightblue"; } + string dotShape() const override { return "ellipse"; } +}; + +class FsmPseudoVertex final : public FsmVertex { + VL_RTTI_IMPL(FsmPseudoVertex, FsmVertex) + +public: + FsmPseudoVertex(V3Graph* graphp, Kind kind, string label) VL_MT_DISABLED + : FsmVertex{graphp, kind, label, 0} {} + ~FsmPseudoVertex() override = default; + + string name() const override VL_MT_SAFE { return label(); } + string dotColor() const override { return isResetAny() ? "darkgreen" : "orange"; } + string dotShape() const override { return "diamond"; } +}; + +class FsmArcEdge final : public V3GraphEdge { + VL_RTTI_IMPL(FsmArcEdge, V3GraphEdge) + bool m_isReset = false; // Arc originates from the synthetic reset source. + bool m_isCond = false; // Arc came from a conditional next-state split. + bool m_isDefault = false; // Arc represents a case default source. + FileLine* m_flp = nullptr; // Source location for emitted coverage metadata. + +public: + FsmArcEdge(V3Graph* graphp, FsmVertex* fromp, FsmStateVertex* top, bool isReset, bool isCond, + bool isDefault, FileLine* flp) VL_MT_DISABLED : V3GraphEdge{graphp, fromp, top, 1}, + m_isReset{isReset}, + m_isCond{isCond}, + m_isDefault{isDefault}, + m_flp{flp} {} + ~FsmArcEdge() override = default; + + bool isReset() const { return m_isReset; } + bool isCond() const { return m_isCond; } + bool isDefault() const { return m_isDefault; } + FileLine* fileline() const { return m_flp; } + + string dotLabel() const override { + if (m_isReset) return "reset"; + if (m_isDefault) return "default"; + if (m_isCond) return "cond"; + return ""; + } + string dotColor() const override { + if (m_isReset) return "darkgreen"; + if (m_isDefault) return "orange"; + if (m_isCond) return "blue"; + return "black"; + } +}; + +// One graph per detected FSM. Graph-level metadata captures the non-graph +// context needed to lower states/arcs back into the AST after detection. +class FsmGraph final : public V3Graph { + AstScope* m_scopep = nullptr; // Owning scoped block for the detected FSM. + AstAlways* m_alwaysp = nullptr; // Original always block being instrumented. + string m_stateVarName; // Pretty state variable name for user-visible output. + string m_stateVarInternalName; // Internal state symbol name for dump tags. + AstVarScope* m_stateVarScopep = nullptr; // Scoped state variable being tracked. + std::vector m_senses; // Saved event controls for recreated active blocks. + FsmResetCondDesc m_resetCond; // Saved reset predicate shape, if one exists. + bool m_hasResetCond = false; // Whether the detected FSM had a reset branch. + bool m_resetInclude = false; // Whether reset arcs count toward coverage totals. + bool m_inclCond = false; // Whether conditional arcs should be kept explicitly. + FileLine* m_flp = nullptr; // Representative source location for declarations/arcs. + std::unordered_map m_stateVertices; // Value to state-vertex map. + FsmPseudoVertex* m_resetVertexp = nullptr; // Synthetic ANY source for reset arcs. + FsmPseudoVertex* m_defaultVertexp = nullptr; // Synthetic default source for case defaults. + +public: + FsmGraph() VL_MT_DISABLED + : m_resetVertexp{new FsmPseudoVertex{this, FsmVertex::Kind::RESET_ANY, "ANY"}}, + m_defaultVertexp{new FsmPseudoVertex{this, FsmVertex::Kind::DEFAULT_ANY, "default"}} {} + + AstScope* scopep() const { return m_scopep; } + void scopep(AstScope* scopep) { m_scopep = scopep; } + AstAlways* alwaysp() const { return m_alwaysp; } + void alwaysp(AstAlways* alwaysp) { m_alwaysp = alwaysp; } + const string& stateVarName() const { return m_stateVarName; } + void stateVarName(const string& name) { m_stateVarName = name; } + const string& stateVarInternalName() const { return m_stateVarInternalName; } + void stateVarInternalName(const string& name) { m_stateVarInternalName = name; } + AstVarScope* stateVarScopep() const { return m_stateVarScopep; } + void stateVarScopep(AstVarScope* vscp) { m_stateVarScopep = vscp; } + const std::vector& senses() const { return m_senses; } + std::vector& senses() { return m_senses; } + const FsmResetCondDesc& resetCond() const { return m_resetCond; } + FsmResetCondDesc& resetCond() { return m_resetCond; } + bool hasResetCond() const { return m_hasResetCond; } + void hasResetCond(bool flag) { m_hasResetCond = flag; } + bool resetInclude() const { return m_resetInclude; } + void resetInclude(bool flag) { m_resetInclude = flag; } + bool inclCond() const { return m_inclCond; } + void inclCond(bool flag) { m_inclCond = flag; } + FileLine* fileline() const { return m_flp; } + void fileline(FileLine* flp) { m_flp = flp; } + + FsmStateVertex* addStateVertex(string label, int value) VL_MT_DISABLED { + FsmStateVertex* const vertexp = new FsmStateVertex{this, label, value}; + m_stateVertices.emplace(value, vertexp); + return vertexp; + } + FsmPseudoVertex* resetAnyVertex() VL_MT_DISABLED { return m_resetVertexp; } + FsmPseudoVertex* defaultAnyVertex() VL_MT_DISABLED { return m_defaultVertexp; } + FsmArcEdge* addArc(int fromValue, int toValue, bool isReset, bool isCond, bool isDefault, + FileLine* flp) VL_MT_DISABLED { + FsmStateVertex* const top = m_stateVertices.at(toValue); + FsmVertex* fromp = nullptr; + if (isReset) { + fromp = resetAnyVertex(); + } else if (isDefault) { + fromp = defaultAnyVertex(); + } else { + fromp = m_stateVertices.at(fromValue); + } + return new FsmArcEdge{this, fromp, top, isReset, isCond, isDefault, flp}; + } + + string name() const VL_MT_SAFE { + return "FSM " + + (m_stateVarName.empty() ? (m_stateVarScopep ? m_stateVarScopep->name() : "") + : m_stateVarName); + } + string dumpTag(size_t index) const { + string tag = stateVarInternalName(); + for (char& ch : tag) { + if (!std::isalnum(static_cast(ch))) ch = '_'; + } + return "fsm_" + cvtToStr(index) + "_" + tag; + } +}; + +struct DetectedFsm final { + std::unique_ptr graphp; // Extracted graph for one detected FSM candidate. +}; +using DetectedFsmMap = std::map; + +// Local shared state between the two adjacent FSM coverage phases. Detection +// fills this with recovered FSM graphs; lowering consumes the completed graphs +// immediately afterward without needing any AST serialization bridge. +class FsmState final { + // All detected FSMs keyed by state varscope name. This is the only bridge + // between the adjacent detect and lower phases, so the second phase never + // needs to rediscover or serialize the extracted machine. + DetectedFsmMap m_fsms; + +public: + DetectedFsmMap& fsms() { return m_fsms; } + const DetectedFsmMap& fsms() const { return m_fsms; } +}; + +// Detection runs while the original clocked/case structure is still intact and +// populates graph-backed FSM models without mutating the tree mid-traversal. +// This pass is intentionally conservative: for this PR we only lock down the +// small set of transition/selector forms that are already stable in the +// normalized AST we see here. The remaining reject branches are therefore +// mostly future-feature boundaries, not accidental dead code. +class FsmDetectVisitor final : public VNVisitor { + // STATE - for current visit position (use VL_RESTORER) + FsmState& m_state; + AstScope* m_scopep = nullptr; + + // METHODS + // Enum-backed FSMs may be wrapped in refs/typedefs; normalize to the + // underlying enum type before deciding whether a case is a candidate. + static AstNodeDType* unwrapEnumCandidate(AstNodeDType* dtypep) { + return dtypep->skipRefToEnump(); + } + + // Reset arcs are only modeled for the simple signal form that survives to + // this pass after earlier normalization. + static bool isSimpleResetCond(AstNodeExpr* condp) { return VN_IS(condp, VarRef); } + + // Normalize the reset condition into a compact description so the lowering + // phase can regenerate the same predicate after detection. By the time + // this pass runs, active-low source forms such as "!rst_n" have already + // been canonicalized to a positive-condition if/else shape, so only a + // plain VarRef survives here. + static FsmResetCondDesc describeResetCond(AstNodeExpr* condp) { + FsmResetCondDesc desc; + if (AstVarRef* const vrefp = VN_CAST(condp, VarRef)) { + desc.varScopep = vrefp->varScopep(); + } + return desc; + } + + // Snapshot the original event control so the lowering phase can rebuild an + // active block with the same edge semantics. + static std::vector describeSenTree(AstSenTree* sentreep) { + std::vector senses; + for (AstSenItem* itemp = sentreep->sensesp(); itemp; + itemp = VN_AS(itemp->nextp(), SenItem)) { + AstNodeVarRef* const vrefp = itemp->varrefp(); + if (!vrefp) continue; + FsmSenDesc desc; + desc.edgeType = itemp->edgeType().m_e; + desc.varScopep = vrefp->varScopep(); + senses.push_back(desc); + } + return senses; + } + + // Ignore existing coverage increments so FSM detection sees the user logic + // rather than other instrumentation already attached to the block. + static bool isIgnorableStmt(AstNode* nodep) { return VN_IS(nodep, CoverInc); } + + // Conservative extractor: only treat a branch as simple when exactly one + // non-coverage statement remains after unwrapping. Richer multi-statement + // or control-flow forms are intentionally left for follow-on FSM-detection + // work instead of being partially inferred here. + static AstNode* singleMeaningfulStmt(AstNode* stmtp) { + AstNode* resultp = nullptr; + for (AstNode* nodep = stmtp; nodep; nodep = nodep->nextp()) { + if (isIgnorableStmt(nodep)) continue; + if (resultp) return nullptr; + resultp = nodep; + } + return resultp; + } + + // Recognize the direct "state <= X" form that gives us an unambiguous arc + // target without needing deeper control-flow reasoning. Branches that fall + // out here represent currently unsupported next-state shapes rather than + // bugs in the implemented subset. + static AstNodeAssign* directStateAssign(AstNode* stmtp, AstVarScope* stateVscp) { + AstNode* const nodep = singleMeaningfulStmt(stmtp); + if (!nodep) return nullptr; + AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign); + if (!assp) return nullptr; + AstVarRef* const vrefp = VN_CAST(assp->lhsp(), VarRef); + if (!vrefp || vrefp->varScopep() != stateVscp) return nullptr; + return assp; + } + + // Prefer enum labels in reports; fall back to synthetic labels for forced + // non-enum FSMs so coverage points remain human-readable. + static string labelForValue(const std::unordered_map& labels, int value) { + const std::unordered_map::const_iterator it = labels.find(value); + return it == labels.end() ? ("S" + cvtToStr(value)) : it->second; + } + + // The extractor only models constant-valued state transitions, and by the + // time detect runs those values have already been constant-folded. + static bool exprConstValue(AstNodeExpr* exprp, int& value) { + if (AstConst* const constp = VN_CAST(exprp, Const)) { + value = constp->toSInt(); + return true; + } + return false; + } + + // Enum-backed FSMs should only transition to values that were interned as + // known states. If a constant transition targets some other encoding, warn + // and skip FSM instrumentation for that edge rather than silently dropping + // it or turning optional coverage into a hard compile failure. + static bool validateKnownStateValue(AstNode* nodep, + const std::unordered_map& labels, int value) { + if (labels.find(value) != labels.end()) return true; + nodep->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on enum state transitions " + "that assign a constant not present in the declared enum"); + return false; + } + + // Extract supported case-item transitions in one place so the conservative + // policy for direct and ternary forms stays consistent. The false exits in + // this helper are deliberate subset boundaries: they document shapes we do + // not yet model in this PR and that future FSM-detection work may widen. + static bool emitCaseItemArcs(FsmGraph& graph, AstCaseItem* itemp, AstVarScope* stateVscp, + const std::unordered_map& labels, bool inclCond) { + std::vector> froms; + if (itemp->isDefault()) { + if (!inclCond) return false; + froms.emplace_back("default", 0); + } else { + for (AstNodeExpr* condp = itemp->condsp(); condp; + condp = VN_CAST(condp->nextp(), NodeExpr)) { + int value = 0; + if (!exprConstValue(condp, value)) continue; + froms.emplace_back(labelForValue(labels, value), value); + } + if (froms.empty()) return false; + } + + if (AstNodeAssign* const assp = directStateAssign(itemp->stmtsp(), stateVscp)) { + int toValue = 0; + if (exprConstValue(assp->rhsp(), toValue)) { + if (!validateKnownStateValue(assp, labels, toValue)) return true; + for (const std::pair& from : froms) { + graph.addArc(from.second, toValue, false, false, itemp->isDefault(), + assp->fileline()); + } + return true; + } + + if (AstCond* const condp = VN_CAST(assp->rhsp(), Cond)) { + int thenValue = 0; + int elseValue = 0; + const bool simpleCond = exprConstValue(condp->thenp(), thenValue) + && exprConstValue(condp->elsep(), elseValue); + if (simpleCond || inclCond) { + if (!validateKnownStateValue(condp->thenp(), labels, thenValue)) return true; + if (!validateKnownStateValue(condp->elsep(), labels, elseValue)) return true; + for (const int branchValue : {thenValue, elseValue}) { + for (const std::pair& from : froms) { + graph.addArc(from.second, branchValue, false, true, itemp->isDefault(), + assp->fileline()); + } + } + return true; + } + } + } + + return false; + } + + // Reset transitions are described separately because they live in the reset + // branch outside the steady-state case statement. + static void addResetArcs(FsmGraph& graph, AstNode* stmtsp, AstVarScope* stateVscp, + const std::unordered_map& labels) { + for (AstNode* nodep = stmtsp; nodep; nodep = nodep->nextp()) { + if (AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign)) { + AstVarRef* const vrefp = VN_CAST(assp->lhsp(), VarRef); + int toValue = 0; + if (vrefp && vrefp->varScopep() == stateVscp + && exprConstValue(assp->rhsp(), toValue)) { + if (!validateKnownStateValue(assp, labels, toValue)) continue; + graph.addArc(0, toValue, true, false, false, assp->fileline()); + } + } + } + } + + // Turn one candidate case statement into the graph representation that the + // later lowering phase will consume directly, while reviewers can still + // inspect the extracted machine via DOT dumps. + void processCase(AstCase* casep, AstNodeExpr* resetCondp, AstAlways* alwaysp) { + AstVarRef* const selp = VN_CAST(casep->exprp(), VarRef); + if (!selp) return; + AstVarScope* const stateVscp = selp->varScopep(); + AstVar* const stateVarp = selp->varp(); + AstEnumDType* enump = VN_CAST(unwrapEnumCandidate(stateVscp->dtypep()), EnumDType); + if (!enump) enump = VN_CAST(unwrapEnumCandidate(stateVarp->dtypep()), EnumDType); + const bool forced = stateVarp->attrFsmState(); + if (!enump && !forced) return; + + std::vector> states; + std::unordered_map labels; + if (enump) { + if (stateVscp->width() < 1 || stateVscp->width() > 32) { + casep->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on enum-typed state " + "variables wider than 32 bits"); + return; + } + for (AstEnumItem* itemp = enump->itemsp(); itemp; + itemp = VN_AS(itemp->nextp(), EnumItem)) { + const AstConst* const constp = VN_AS(itemp->valuep(), Const); + const int value = constp->toSInt(); + states.emplace_back(itemp->name(), value); + labels.emplace(value, itemp->name()); + } + if (states.size() < 2) return; + } else { + const int width = stateVarp->width(); + if (width < 1 || width >= 31) return; + const unsigned stateCount = 1U << width; + for (unsigned value = 0; value < stateCount; ++value) { + const string label = "S" + cvtToStr(value); + states.emplace_back(label, static_cast(value)); + labels.emplace(static_cast(value), label); + } + } + + DetectedFsm& entry = m_state.fsms()[stateVscp->name()]; + if (!entry.graphp) { + entry.graphp.reset(new FsmGraph{}); + entry.graphp->scopep(m_scopep); + entry.graphp->alwaysp(alwaysp); + entry.graphp->stateVarName(stateVscp->prettyName()); + entry.graphp->stateVarInternalName(stateVarp->name()); + entry.graphp->stateVarScopep(stateVscp); + entry.graphp->senses() = describeSenTree(alwaysp->sentreep()); + entry.graphp->resetCond() = describeResetCond(resetCondp); + entry.graphp->hasResetCond(entry.graphp->resetCond().varScopep != nullptr); + entry.graphp->resetInclude(stateVarp->attrFsmResetArc()); + entry.graphp->inclCond(stateVarp->attrFsmArcInclCond()); + entry.graphp->fileline(casep->fileline()); + for (const std::pair& state : states) { + entry.graphp->addStateVertex(state.first, state.second); + } + } + for (AstCaseItem* itemp = casep->itemsp(); itemp; + itemp = VN_AS(itemp->nextp(), CaseItem)) { + emitCaseItemArcs(*entry.graphp, itemp, stateVscp, labels, entry.graphp->inclCond()); + } + } + + // Find the first supported FSM candidate in a clocked always block, warn on + // additional candidates, and attach reset arcs when present. Candidate + // filtering stays narrow on purpose: we prefer to skip ambiguous shapes now + // and expand detection in a later PR rather than over-infer coverage from + // forms we do not yet model confidently. + void processAlways(AstAlways* alwaysp) { + if (!alwaysp->sentreep() || !alwaysp->sentreep()->hasClocked()) return; + std::vector> candidates; + AstNode* stmtsp = alwaysp->stmtsp(); + AstIf* const firstIfp = VN_CAST(stmtsp, If); + if (firstIfp) { + if (AstCase* const casep = VN_CAST(firstIfp->elsesp(), Case)) { + candidates.emplace_back( + casep, isSimpleResetCond(firstIfp->condp()) ? firstIfp->condp() : nullptr); + } + } + for (AstNode* nodep = stmtsp; nodep; nodep = nodep->nextp()) { + if (AstCase* const casep = VN_CAST(nodep, Case)) + candidates.emplace_back(casep, nullptr); + } + if (candidates.empty()) return; + + AstVarScope* firstVscp = nullptr; + for (const std::pair& cand : candidates) { + AstVarRef* const selp = VN_CAST(cand.first->exprp(), VarRef); + AstVarScope* const vscp = selp ? selp->varScopep() : nullptr; + if (!vscp) continue; + if (!firstVscp) { + firstVscp = vscp; + processCase(cand.first, cand.second, alwaysp); + } else if (vscp != firstVscp) { + cand.first->v3warn(FSMMULTI, + "FSM coverage: multiple enum-typed case statements found in " + "the same always block. Only the first candidate will be " + "instrumented."); + } else { + cand.first->v3warn(COVERIGN, + "Ignoring unsupported: FSM coverage on multiple supported case " + "statements found in the same always block. Only the first " + "candidate will be instrumented."); + } + } + + if (!(firstIfp && firstVscp)) return; + const DetectedFsmMap& fsms = m_state.fsms(); + const DetectedFsmMap::const_iterator it = fsms.find(firstVscp->name()); + if (it == fsms.end()) return; + FsmGraph* const graphp = it->second.graphp.get(); + if (!graphp->hasResetCond()) return; + std::unordered_map labels; + for (const V3GraphVertex& vtx : graphp->vertices()) { + const FsmVertex* const vertexp = vtx.as(); + if (!vertexp->isState()) continue; + labels.emplace(vertexp->value(), vertexp->label()); + } + addResetArcs(*graphp, firstIfp->thensp(), firstVscp, labels); + } + + // Track the current scope so each detected FSM records the module/scope + // where instrumentation must later be inserted. + void visit(AstScope* nodep) override { + VL_RESTORER(m_scopep); + m_scopep = nodep; + iterateChildren(nodep); + } + + // FSM extraction only cares about clocked always processes. + void visit(AstAlways* nodep) override { processAlways(nodep); } + + // Continue the walk through the rest of the design hierarchy. + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + // CONSTRUCTORS + // Collect all FSM graphs into the shared local state before the lowering + // phase starts mutating the AST with coverage machinery. + FsmDetectVisitor(FsmState& state, AstNetlist* rootp) + : m_state{state} { + iterate(rootp); + } +}; + +// Lower the completed FSM graphs into the concrete coverage declarations, +// previous-state tracking, and pre/post-triggered instrumentation that the +// runtime uses to record state and transition coverage. +class FsmLowerVisitor final { + // STATE - across all visitors + const FsmState& m_state; + + // METHODS + // Rebuild a state-typed constant using the tracked state variable + // width/sign so emitted comparisons match the original representation. + static AstConst* makeStateConst(FileLine* flp, AstVarScope* vscp, int value) { + V3Number num{flp, vscp->width(), static_cast(value)}; + num.isSigned(vscp->dtypep()->isSigned()); + return new AstConst{flp, num}; + } + + // Build guards incrementally without forcing callers to special-case the + // first predicate; this keeps emitted state/arc conditions readable. + static AstNodeExpr* andExpr(FileLine* flp, AstNodeExpr* lhsp, AstNodeExpr* rhsp) { + if (!lhsp) return rhsp; + return new AstLogAnd{flp, lhsp, rhsp}; + } + + static AstNodeExpr* buildResetCond(FileLine* flp, AstVarScope* resetVscp, + const FsmResetCondDesc&) { + return new AstVarRef{flp, resetVscp, VAccess::READ}; + } + + // Rebuild the original event control from the saved sense description so + // post-state coverage sampling runs on the same triggering edges. + static AstSenTree* buildSenTree(FileLine* flp, const std::vector& senses) { + AstSenTree* const sentreep = new AstSenTree{flp, nullptr}; + for (const FsmSenDesc& sense : senses) { + AstSenItem* const senItemp + = new AstSenItem{flp, VEdgeType{sense.edgeType}, + new AstVarRef{flp, sense.varScopep, VAccess::READ}}; + sentreep->addSensesp(senItemp); + } + return sentreep; + } + + // Lower one fully detected FSM graph into the concrete coverage machinery + // used by generated models: declarations, previous-state tracking, and the + // pre/post-triggered increment logic for states and arcs. + void buildOne(const FsmGraph& graph) { + AstAlways* const alwaysp = graph.alwaysp(); + AstScope* const scopep = graph.scopep(); + AstVarScope* const stateVscp = graph.stateVarScopep(); + FileLine* const flp = graph.fileline(); + AstNodeModule* const modp = scopep->modp(); + AstNodeDType* const prevDTypep = scopep->findLogicDType( + stateVscp->width(), stateVscp->width(), stateVscp->dtypep()->numeric()); + AstVarScope* const prevVscp + = scopep->createTemp("__Vfsmcov_prev__" + stateVscp->varp()->shortName(), 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. + stateVscp->optimizeLifePost(true); + prevVscp->optimizeLifePost(true); + + AstActive* const initActivep + = new AstActive{flp, "fsm-coverage-init", + new AstSenTree{flp, new AstSenItem{flp, AstSenItem::Initial{}}}}; + initActivep->senTreeStorep(initActivep->sentreep()); + // Seed the previous-state temp during initialization so the first + // clock edge compares against a defined state value. + initActivep->addStmtsp(new AstInitialStatic{ + flp, new AstAssign{flp, new AstVarRef{flp, prevVscp, VAccess::WRITE}, + new AstVarRef{flp, stateVscp, VAccess::READ}}}); + scopep->addBlocksp(initActivep); + + AstAlwaysPost* const covPostp = new AstAlwaysPost{flp}; + // Save the previous state as plain sequential logic at the front of + // the original always_ff body, then evaluate coverage in post logic + // after the delayed state update commits. This avoids a scheduler race + // between a separate AstAlwaysPre task and the real state commit. + AstNode* const bodysp = alwaysp->stmtsp()->unlinkFrBackWithNext(); + alwaysp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, prevVscp, VAccess::WRITE}, + new AstVarRef{flp, stateVscp, VAccess::READ}}); + alwaysp->addStmtsp(bodysp); + + for (const V3GraphVertex& vtx : graph.vertices()) { + const FsmVertex* const vertexp = vtx.as(); + if (!vertexp->isState()) continue; + const FsmStateVertex* const statep = vtx.as(); + // State coverage fires when the FSM enters a state from any other + // value, so repeated self-holds do not count as new entries. + AstCoverOtherDecl* const declp + = new AstCoverOtherDecl{flp, + "v_fsm_state/" + modp->prettyName(), + graph.stateVarName() + "::" + statep->label(), + "", + 0, + graph.stateVarName(), + "", + statep->label()}; + declp->hier(scopep->prettyName()); + modp->addStmtsp(declp); + AstNodeExpr* const guardp + = andExpr(flp, + new AstNeq{flp, new AstVarRef{flp, prevVscp, VAccess::READ}, + makeStateConst(flp, prevVscp, statep->value())}, + new AstEq{flp, new AstVarRef{flp, stateVscp, VAccess::READ}, + makeStateConst(flp, stateVscp, statep->value())}); + covPostp->addStmtsp(new AstIf{flp, guardp, new AstCoverInc{flp, declp}}); + } + + for (const V3GraphVertex& vtx : graph.vertices()) { + const FsmVertex* const fromVertexp = vtx.as(); + for (const V3GraphEdge& edge : fromVertexp->outEdges()) { + const FsmArcEdge* const arcp = edge.as(); + const FsmStateVertex* const toStatep = arcp->top()->as(); + // Arc coverage mirrors the extracted graph exactly, including + // reset and synthetic-default sources, so reports match the + // reviewer-visible graph dump and the user-visible annotation. + const string resetTag + = arcp->isReset() ? (graph.resetInclude() ? "[reset_include]" : "[reset]") + : ""; + const string fsmTag = arcp->isReset() + ? (graph.resetInclude() ? "reset_include" : "reset") + : arcp->isDefault() ? "default" + : ""; + AstCoverOtherDecl* const declp + = new AstCoverOtherDecl{flp, + "v_fsm_arc/" + modp->prettyName(), + graph.stateVarName() + "::" + fromVertexp->label() + + "->" + toStatep->label() + resetTag, + "", + 0, + graph.stateVarName(), + fromVertexp->label(), + toStatep->label(), + fsmTag}; + declp->hier(scopep->prettyName()); + modp->addStmtsp(declp); + AstNodeExpr* guardp = nullptr; + if (fromVertexp->isResetAny()) { + // Reset arcs are modeled as pseudo-source edges in the + // graph, then reconstructed here into the original simple + // reset predicate combined with the destination state. + guardp = buildResetCond(flp, graph.resetCond().varScopep, graph.resetCond()); + guardp = andExpr(flp, guardp, + new AstEq{flp, new AstVarRef{flp, stateVscp, VAccess::READ}, + makeStateConst(flp, stateVscp, toStatep->value())}); + } else if (fromVertexp->isDefaultAny()) { + // Synthetic default arcs mean "none of the explicit + // source states matched", so rebuild that as a conjunction + // of previous-state != known-state tests. + for (const V3GraphVertex& stateVtx : graph.vertices()) { + const FsmVertex* const stateVertexp = stateVtx.as(); + if (!stateVertexp->isState()) continue; + guardp = andExpr( + flp, guardp, + new AstNeq{flp, new AstVarRef{flp, prevVscp, VAccess::READ}, + makeStateConst(flp, prevVscp, stateVertexp->value())}); + } + guardp = andExpr(flp, guardp, + new AstEq{flp, new AstVarRef{flp, stateVscp, VAccess::READ}, + makeStateConst(flp, stateVscp, toStatep->value())}); + } else { + guardp + = andExpr(flp, + new AstEq{flp, new AstVarRef{flp, prevVscp, VAccess::READ}, + makeStateConst(flp, prevVscp, fromVertexp->value())}, + new AstEq{flp, new AstVarRef{flp, stateVscp, VAccess::READ}, + makeStateConst(flp, stateVscp, toStatep->value())}); + } + covPostp->addStmtsp(new AstIf{flp, guardp, new AstCoverInc{flp, declp}}); + } + } + + AstSenTree* const sentreep = buildSenTree(flp, graph.senses()); + AstActive* const activep = new AstActive{flp, "fsm-coverage", sentreep}; + activep->senTreeStorep(sentreep); + scopep->addBlocksp(activep); + activep->addStmtsp(covPostp); + } + +public: + // CONSTRUCTORS + // Lower every detected FSM graph from the shared local state into + // concrete coverage instrumentation while the saved scoped pointers are + // still valid in the same pass. + explicit FsmLowerVisitor(const FsmState& state) + : m_state{state} { + for (const std::pair& it : m_state.fsms()) { + buildOne(*it.second.graphp); + } + } +}; + +} // namespace + +void V3FsmDetect::detect(AstNetlist* rootp) { + UINFO(2, __FUNCTION__ << ":"); + FsmState state; + // Phase 1: recover each supported FSM into a complete graph while the + // original clocked/case structure is still easy to recognize. + FsmDetectVisitor detect{state, rootp}; + if (dumpGraphLevel() >= 6) { + size_t index = 0; + for (const std::pair& it : state.fsms()) { + it.second.graphp->dumpDotFilePrefixed(it.second.graphp->dumpTag(index++)); + } + } + // Phase 2: lower the completed in-memory graph state immediately, without + // crossing into another pass owner or serializing through AST placeholders. + { FsmLowerVisitor lower{state}; } + V3Global::dumpCheckGlobalTree("fsm-detect", 0, dumpTreeEitherLevel() >= 3); +} diff --git a/src/V3FsmDetect.h b/src/V3FsmDetect.h new file mode 100644 index 000000000..878efe5be --- /dev/null +++ b/src/V3FsmDetect.h @@ -0,0 +1,33 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: FSM coverage detect pass +// +// 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: 2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#ifndef VERILATOR_V3FSMDETECT_H_ +#define VERILATOR_V3FSMDETECT_H_ + +#include "config_build.h" +#include "verilatedos.h" + +class AstNetlist; + +class V3FsmDetect final { +public: + // Detect FSMs while the original clocked/case structure is still visible, + // then immediately lower the recovered graphs into concrete coverage + // instrumentation as a second local phase in the same pass. + static void detect(AstNetlist* rootp) VL_MT_DISABLED; +}; + +#endif diff --git a/src/V3Global.h b/src/V3Global.h index 7b5c1aa18..44930c2aa 100644 --- a/src/V3Global.h +++ b/src/V3Global.h @@ -125,6 +125,7 @@ class V3Global final { bool m_usesProbDist = false; // Uses $dist_* bool m_usesStdPackage = false; // Design uses the std package bool m_usesTiming = false; // Design uses timing constructs + bool m_usesForce = false; // Design uses force/release statements bool m_usesZeroDelay = false; // Design uses #0 delay (or non-constant delay) bool m_hasForceableSignals = false; // Need to apply V3Force pass bool m_hasSystemCSections = false; // Has AstSystemCSection that need to be emitted @@ -205,6 +206,8 @@ public: void setUsesZeroDelay() { m_usesZeroDelay = true; } bool hasForceableSignals() const { return m_hasForceableSignals; } void setHasForceableSignals() { m_hasForceableSignals = true; } + bool usesForce() const { return m_usesForce; } + void setUsesForce() { m_usesForce = true; } bool hasSystemCSections() const VL_MT_SAFE { return m_hasSystemCSections; } void setHasSystemCSections() { m_hasSystemCSections = true; } V3HierGraph* hierGraphp() const { return m_hierGraphp; } diff --git a/src/V3GraphAlg.cpp b/src/V3GraphAlg.cpp index 9640f6272..a501b96a4 100644 --- a/src/V3GraphAlg.cpp +++ b/src/V3GraphAlg.cpp @@ -40,7 +40,7 @@ VL_DEFINE_DEBUG_FUNCTIONS; // Changes user() and weight() class GraphRemoveRedundant final : GraphAlg<> { - const bool m_sumWeights; ///< Sum, rather then maximize weights + const bool m_sumWeights; // Sum, rather then maximize weights private: void main() { for (V3GraphVertex& vertex : m_graphp->vertices()) vertexIterate(&vertex); diff --git a/src/V3Inst.cpp b/src/V3Inst.cpp index ad6a04860..2d9b443dd 100644 --- a/src/V3Inst.cpp +++ b/src/V3Inst.cpp @@ -178,9 +178,8 @@ class InstDeVisitor final : public VNVisitor { // Find all cells with arrays, and convert to non-arrayed private: // STATE - // Range for arrayed instantiations, nullptr for normal instantiations - const AstRange* m_cellRangep = nullptr; - int m_instSelNum = 0; // Current instantiation count 0..N-1 + const AstRange* m_cellRangep = nullptr; // Outer range; nullptr for non-arrayed cells + int m_instSelNum = 0; // Row-major flat index for 1D-compat pin expansion InstDeModVarVisitor m_deModVars; // State of variables for current cell module // VISITORS @@ -233,52 +232,72 @@ private: m_deModVars.main(nodep->modp()); // if (nodep->rangep()) { - m_cellRangep = nodep->rangep(); + // Collect the full range chain (outer first). + std::vector rangesp; + for (AstRange* rp = nodep->rangep(); rp; rp = VN_CAST(rp->nextp(), Range)) { + rangesp.push_back(rp); + } + m_cellRangep = rangesp.front(); + const int ndim = static_cast(rangesp.size()); + std::vector sizes(ndim); + int totalElems = 1; + for (int d = 0; d < ndim; ++d) { + sizes[d] = rangesp[d]->elementsConst(); + totalElems *= sizes[d]; + } AstVar* const ifaceVarp = VN_CAST(nodep->nextp(), Var); - // cppcheck-suppress constVariablePointer - AstNodeDType* const ifaceVarDtp - = ifaceVarp ? ifaceVarp->dtypep()->skipRefp() : nullptr; - const bool isIface - = ifaceVarp && VN_IS(ifaceVarDtp, UnpackArrayDType) - && VN_IS(VN_AS(ifaceVarDtp, UnpackArrayDType)->subDTypep()->skipRefp(), - IfaceRefDType) - && !VN_AS(VN_AS(ifaceVarDtp, UnpackArrayDType)->subDTypep()->skipRefp(), - IfaceRefDType) - ->isVirtual(); + // Peel all UnpackArrayDType layers to reach the bottom IfaceRefDType. + AstIfaceRefDType* origIfaceRefp = nullptr; + AstUnpackArrayDType* innermostArrp = nullptr; + for (AstNodeDType* dp = ifaceVarp ? ifaceVarp->dtypep()->skipRefp() : nullptr; dp;) { + if (AstUnpackArrayDType* const arrp = VN_CAST(dp, UnpackArrayDType)) { + innermostArrp = arrp; + dp = arrp->subDTypep()->skipRefp(); + } else { + origIfaceRefp = VN_CAST(dp, IfaceRefDType); + break; + } + } + const bool isIface = origIfaceRefp && !origIfaceRefp->isVirtual(); - // Make all of the required clones - for (int i = 0; i < m_cellRangep->elementsConst(); i++) { - m_instSelNum - = m_cellRangep->ascending() ? (m_cellRangep->elementsConst() - 1 - i) : i; - const int instNum = m_cellRangep->loConst() + i; + std::vector idx(ndim, 0); + for (int n = 0; n < totalElems; ++n) { + // Unflatten n into a row-major cartesian index; outer dim most significant. + int rem = n; + for (int d = ndim - 1; d >= 0; --d) { + idx[d] = rem % sizes[d]; + rem /= sizes[d]; + } + // Flat select number for 1D-compat pin expansion; ascending dims invert. + // Also build the "__BRA__i__KET__..." suffix (encodeNumber for negative idx). + int flatSel = 0; + string suffix; + for (int d = 0; d < ndim; ++d) { + const int sel = rangesp[d]->ascending() ? (sizes[d] - 1 - idx[d]) : idx[d]; + flatSel = flatSel * sizes[d] + sel; + suffix += "__BRA__" + AstNode::encodeNumber(rangesp[d]->loConst() + idx[d]) + + "__KET__"; + } + m_instSelNum = flatSel; AstCell* const newp = nodep->cloneTree(false); nodep->addNextHere(newp); - // Remove ranging and fix name - newp->rangep()->unlinkFrBack()->deleteTree(); - // Somewhat illogically, we need to rename the original name of the cell too. - // as that is the name users expect for dotting - // The spec says we add [x], but that won't work in C... - newp->name(newp->name() + "__BRA__" + cvtToStr(instNum) + "__KET__"); - newp->origName(newp->origName() + "__BRA__" + cvtToStr(instNum) + "__KET__"); + while (newp->rangep()) newp->rangep()->unlinkFrBack()->deleteTree(); + newp->name(newp->name() + suffix); + newp->origName(newp->origName() + suffix); UINFO(8, " CELL loop " << newp); - // If this AstCell is actually an interface instantiation, also clone the IfaceRef - // within the same parent module as the cell + // Interface instantiation: also clone the IfaceRef in the parent module. if (isIface) { - AstUnpackArrayDType* const arrdtype = VN_AS(ifaceVarDtp, UnpackArrayDType); - AstIfaceRefDType* const origIfaceRefp - = VN_AS(arrdtype->subDTypep()->skipRefp(), IfaceRefDType); origIfaceRefp->cellp(nullptr); AstVar* const varNewp = ifaceVarp->cloneTree(false); AstIfaceRefDType* const ifaceRefp = origIfaceRefp->cloneTree(false); - arrdtype->addNextHere(ifaceRefp); + innermostArrp->addNextHere(ifaceRefp); ifaceRefp->cellp(newp); ifaceRefp->cellName(newp->name()); - varNewp->name(varNewp->name() + "__BRA__" + cvtToStr(instNum) + "__KET__"); - varNewp->origName(varNewp->origName() + "__BRA__" + cvtToStr(instNum) - + "__KET__"); + varNewp->name(varNewp->name() + suffix); + varNewp->origName(varNewp->origName() + suffix); varNewp->dtypep(ifaceRefp); newp->addNextHere(varNewp); if (debug() == 9) { @@ -389,6 +408,105 @@ private: } } else { AstVar* const pinVarp = nodep->modVarp(); + // Multi-dim whole-array iface pin fanout: cartesian-product the port's + // nested UnpackArrayDType layers and emit one pin + per-element var per cell. + // For 1-dim falls through to the original code below. + std::vector portArrs; + for (AstNodeDType* d = pinVarp->dtypep()->skipRefp(); d;) { + if (const AstUnpackArrayDType* const arrp = VN_CAST(d, UnpackArrayDType)) { + portArrs.push_back(arrp); + d = arrp->subDTypep()->skipRefp(); + } else { + break; + } + } + if (portArrs.size() >= 2) { + AstIfaceRefDType* const portIrp + = VN_CAST(portArrs.back()->subDTypep()->skipRefp(), IfaceRefDType); + if (!portIrp || portIrp->isVirtual()) return; + const int ndim = static_cast(portArrs.size()); + std::vector sizes(ndim); + int totalElems = 1; + for (int d = 0; d < ndim; ++d) { + sizes[d] = portArrs[d]->elementsConst(); + totalElems *= sizes[d]; + } + // V3Width should have already rejected non-VarRef and rank-mismatch pins; + // these are defensive internal-invariant guards. + const AstVarRef* const varrefp = VN_CAST(nodep->exprp(), VarRef); + UASSERT_OBJ(varrefp, nodep->exprp(), "Unexpected connection to arrayed port"); + std::vector exprArrs; + for (AstNodeDType* d = varrefp->dtypep()->skipRefp(); d;) { + if (const AstUnpackArrayDType* const arrp = VN_CAST(d, UnpackArrayDType)) { + exprArrs.push_back(arrp); + d = arrp->subDTypep()->skipRefp(); + } else { + break; + } + } + UASSERT_OBJ(exprArrs.size() == static_cast(ndim), nodep->exprp(), + "Multi-dim iface pin expression rank does not match port"); + AstNode* prevp = nullptr; + AstNode* prevPinp = nullptr; + std::vector idx(ndim, 0); + for (int n = 0; n < totalElems; ++n) { + int rem = n; + for (int d = ndim - 1; d >= 0; --d) { + idx[d] = rem % sizes[d]; + rem /= sizes[d]; + } + string portSuffix; + string exprSuffix; + for (int d = 0; d < ndim; ++d) { + portSuffix += "__BRA__" + AstNode::encodeNumber(portArrs[d]->lo() + idx[d]) + + "__KET__"; + exprSuffix += "__BRA__" + AstNode::encodeNumber(exprArrs[d]->lo() + idx[d]) + + "__KET__"; + } + const string varNewName = pinVarp->name() + portSuffix; + AstVar* varNewp = nullptr; + if (!pinVarp->backp()) { + varNewp = m_deModVars.find(varNewName); + } else { + portIrp->cellp(nullptr); + varNewp = pinVarp->cloneTree(false); + varNewp->name(varNewName); + varNewp->origName(varNewp->origName() + portSuffix); + varNewp->dtypep(portIrp); + m_deModVars.insert(varNewp); + if (!prevp) { + prevp = varNewp; + } else { + prevp->addNextHere(varNewp); + } + } + if (!varNewp) { + if (debug() >= 9) m_deModVars.dump(); // LCOV_EXCL_LINE + nodep->v3fatalSrc("Module dearray failed for " + << AstNode::prettyNameQ(varNewName)); + } + AstPin* const newp = nodep->cloneTree(false); + newp->modVarp(varNewp); + newp->name(newp->name() + portSuffix); + AstVarXRef* const newVarXRefp = new AstVarXRef{ + nodep->fileline(), varrefp->name() + exprSuffix, "", VAccess::WRITE}; + newVarXRefp->varp(newp->modVarp()); + newp->exprp()->unlinkFrBack()->deleteTree(); + newp->exprp(newVarXRefp); + if (!prevPinp) { + prevPinp = newp; + } else { + prevPinp->addNextHere(newp); + } + } + if (prevp) { + pinVarp->replaceWith(prevp); + pushDeletep(pinVarp); + } + nodep->replaceWith(prevPinp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } const AstUnpackArrayDType* const pinArrp = VN_CAST(pinVarp->dtypep()->skipRefp(), UnpackArrayDType); if (!pinArrp || !VN_IS(pinArrp->subDTypep()->skipRefp(), IfaceRefDType)) return; @@ -472,28 +590,50 @@ private: } } void visit(AstArraySel* nodep) override { - if (const AstUnpackArrayDType* const arrp - = VN_CAST(nodep->fromp()->dtypep()->skipRefp(), UnpackArrayDType)) { - if (!VN_IS(arrp->subDTypep()->skipRefp(), IfaceRefDType)) return; - if (VN_AS(arrp->subDTypep()->skipRefp(), IfaceRefDType)->isVirtual()) return; - V3Const::constifyParamsEdit(nodep->bitp()); - const AstConst* const constp = VN_CAST(nodep->bitp(), Const); + // If a parent is also an ArraySel into the same iface array, let it handle the chain. + if (VN_IS(nodep->backp(), ArraySel) && nodep->backp()->op1p() == nodep) return; + // Collect nested ArraySels top-down (nodep is outermost in AST, innermost dim index). + std::vector sels; + AstNode* curp = nodep; + while (AstArraySel* const asp = VN_CAST(curp, ArraySel)) { + sels.push_back(asp); + curp = asp->fromp(); + } + const AstVarRef* const varrefp = VN_CAST(curp, VarRef); + if (!varrefp) return; + // Confirm base is a (possibly nested) UnpackArray wrapping an IfaceRefDType. + std::vector arrs; // outer dim first + AstNodeDType* dtp = varrefp->dtypep()->skipRefp(); + while (AstUnpackArrayDType* const ap = VN_CAST(dtp, UnpackArrayDType)) { + arrs.push_back(ap); + dtp = ap->subDTypep()->skipRefp(); + } + if (arrs.empty() || sels.size() != arrs.size()) return; + AstIfaceRefDType* const irp = VN_CAST(dtp, IfaceRefDType); + if (!irp || irp->isVirtual()) return; + // Constify bitps and collect indices in outer-dim-first order (sels is inner-first). + std::vector indices(sels.size()); + for (size_t i = 0; i < sels.size(); ++i) { + AstArraySel* const asp = sels[i]; + V3Const::constifyParamsEdit(asp->bitp()); + const AstConst* const constp = VN_CAST(asp->bitp(), Const); if (!constp) { - nodep->bitp()->v3warn(E_UNSUPPORTED, - "Non-constant index in RHS interface array selection"); + asp->bitp()->v3warn(E_UNSUPPORTED, + "Non-constant index in RHS interface array selection"); return; } - const string index = AstNode::encodeNumber(constp->toSInt() + arrp->lo()); - const AstVarRef* const varrefp = VN_CAST(nodep->fromp(), VarRef); - UASSERT_OBJ(varrefp, nodep, "No interface varref under array"); - AstVarXRef* const newp = new AstVarXRef{ - nodep->fileline(), varrefp->name() + "__BRA__" + index + "__KET__", "", - VAccess::READ}; - newp->dtypep(arrp->subDTypep()); - newp->classOrPackagep(varrefp->classOrPackagep()); - nodep->addNextHere(newp); - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + indices[sels.size() - 1 - i] = constp->toSInt(); } + string indexStr; + for (size_t i = 0; i < indices.size(); ++i) { + indexStr += "__BRA__" + AstNode::encodeNumber(indices[i] + arrs[i]->lo()) + "__KET__"; + } + AstVarXRef* const newp + = new AstVarXRef{nodep->fileline(), varrefp->name() + indexStr, "", VAccess::READ}; + newp->dtypep(irp); + newp->classOrPackagep(varrefp->classOrPackagep()); + nodep->addNextHere(newp); + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); } void visit(AstNodeAssign* nodep) override { if (AstSliceSel* const arrslicep = VN_CAST(nodep->rhsp(), SliceSel)) { diff --git a/src/V3LinkCells.cpp b/src/V3LinkCells.cpp index 3eb3cc60e..48134d037 100644 --- a/src/V3LinkCells.cpp +++ b/src/V3LinkCells.cpp @@ -776,12 +776,19 @@ class LinkCellsVisitor final : public VNVisitor { idtypep->cellp(nodep); // Only set when real parent cell known. AstVar* varp; if (nodep->rangep()) { - // For arrayed interfaces, we replace cellp when de-arraying in V3Inst - AstNodeArrayDType* const arrp - = new AstUnpackArrayDType{nodep->fileline(), VFlagChildDType{}, idtypep, - nodep->rangep()->cloneTree(true)}; + // For arrayed interfaces, we replace cellp when de-arraying in V3Inst. + // Multi-dim arrays wrap one UnpackArrayDType per range, innermost first. + std::vector rangesp; + for (AstRange* rp = nodep->rangep(); rp; rp = VN_CAST(rp->nextp(), Range)) { + rangesp.push_back(rp); + } + AstNodeDType* dtp = idtypep; + for (auto it = rangesp.rbegin(); it != rangesp.rend(); ++it) { + dtp = new AstUnpackArrayDType{nodep->fileline(), VFlagChildDType{}, dtp, + (*it)->cloneTree(false)}; + } varp = new AstVar{nodep->fileline(), VVarType::IFACEREF, varName, - VFlagChildDType{}, arrp}; + VFlagChildDType{}, dtp}; } else { varp = new AstVar{nodep->fileline(), VVarType::IFACEREF, varName, VFlagChildDType{}, idtypep}; diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 31826e53f..2aa4561c9 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -524,17 +524,19 @@ public: void insertIfaceVarSym(VSymEnt* symp) { // Where sym is for a VAR of dtype IFACEREFDTYPE m_ifaceVarSyms.push_back(symp); } - // Iface for a raw or arrayed iface + // Iface for a raw or arrayed iface; peels nested array layers for multi-dim arrays. static AstIfaceRefDType* ifaceRefFromArray(AstNodeDType* nodep) { - AstIfaceRefDType* ifacerefp = VN_CAST(nodep, IfaceRefDType); - if (!ifacerefp) { + while (nodep) { + if (AstIfaceRefDType* const ifp = VN_CAST(nodep, IfaceRefDType)) return ifp; if (const AstBracketArrayDType* const arrp = VN_CAST(nodep, BracketArrayDType)) { - ifacerefp = VN_CAST(arrp->subDTypep(), IfaceRefDType); + nodep = arrp->subDTypep(); } else if (const AstUnpackArrayDType* const arrp = VN_CAST(nodep, UnpackArrayDType)) { - ifacerefp = VN_CAST(arrp->subDTypep(), IfaceRefDType); + nodep = arrp->subDTypep(); + } else { + return nullptr; } } - return ifacerefp; + return nullptr; } // Given a pin expression, resolve it to a live AstIface* (or nullptr). // Handles both simple VarRef and dotted VarXRef pin connections. @@ -770,10 +772,16 @@ public: if (forPrearray()) { // GENFOR Begin is foo__BRA__##__KET__ after we've genloop unrolled, // but presently should be just "foo". - // Likewise cell foo__[array] before we've expanded arrays is just foo - if ((pos = ident.rfind("__BRA__")) != string::npos) { - altIdent = ident.substr(0, pos); + // Likewise cell foo__[array] before we've expanded arrays is just foo. + // Multi-dim iface arrays append multiple __BRA__..__KET__ suffixes; strip them + // all. + altIdent = ident; + while (VString::endsWith(altIdent, "__KET__")) { + const auto braPos = altIdent.rfind("__BRA__"); + if (braPos == string::npos) break; + altIdent = altIdent.substr(0, braPos); } + if (altIdent == ident) altIdent.clear(); } UINFO(8, " id " << ident << " alt " << altIdent << " left " << leftname << " at se" << lookupSymp); @@ -5316,10 +5324,18 @@ class LinkDotResolveVisitor final : public VNVisitor { symIterateNull(nodep->attrp(), m_curSymp); if (m_ds.m_unresolvedCell && (m_ds.m_dotPos == DP_SCOPE || m_ds.m_dotPos == DP_FIRST)) { AstNodeExpr* const exprp = nodep->bitp()->unlinkFrBack(); - AstCellArrayRef* const newp - = new AstCellArrayRef{nodep->fileline(), nodep->fromp()->name(), exprp}; - nodep->replaceWith(newp); - VL_DO_DANGLING(pushDeletep(nodep), nodep); + if (AstCellArrayRef* const fromArrRefp = VN_CAST(nodep->fromp(), CellArrayRef)) { + // Multi-dim iface array access: append this select to the existing chain + fromArrRefp->addSelp(exprp); + AstCellArrayRef* const movedp = fromArrRefp->unlinkFrBack(); + nodep->replaceWith(movedp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } else { + AstCellArrayRef* const newp + = new AstCellArrayRef{nodep->fileline(), nodep->fromp()->name(), exprp}; + nodep->replaceWith(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } } } void visit(AstNodePreSel* nodep) override { diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index 1014e6332..f75526eac 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -611,6 +611,18 @@ class LinkParseVisitor final : public VNVisitor { m_varp->attrSplitVar(true); } VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); + } else if (nodep->attrType() == VAttrType::VAR_FSM_ARC_INCLUDE_COND) { + UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); + m_varp->attrFsmArcInclCond(true); + VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); + } else if (nodep->attrType() == VAttrType::VAR_FSM_RESET_ARC) { + UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); + m_varp->attrFsmResetArc(true); + VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); + } else if (nodep->attrType() == VAttrType::VAR_FSM_STATE) { + UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); + m_varp->attrFsmState(true); + VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); } else if (nodep->attrType() == VAttrType::VAR_SC_BIGUINT) { UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); m_varp->attrScBigUint(true); diff --git a/src/V3Options.cpp b/src/V3Options.cpp index 4d01fa1ec..6736434fb 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1352,6 +1352,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, DECL_OPTION("-coverage", CbOnOff, [this](bool flag) { coverage(flag); }); DECL_OPTION("-coverage-expr", OnOff, &m_coverageExpr); DECL_OPTION("-coverage-expr-max", Set, &m_coverageExprMax); + DECL_OPTION("-coverage-fsm", OnOff, &m_coverageFsm); DECL_OPTION("-coverage-line", OnOff, &m_coverageLine); DECL_OPTION("-coverage-max-width", Set, &m_coverageMaxWidth); DECL_OPTION("-coverage-toggle", OnOff, &m_coverageToggle); @@ -2295,7 +2296,10 @@ void V3Options::setDebugMode(int level) { if (!m_dumpLevel.count("tree")) m_dumpLevel["tree"] = 3; // Don't override if already set. m_stats = true; m_debugCheck = true; - if (level) cout << "Starting " << version() << "\n"; + if (level) { + cout << "- Starting " << version() << "\n"; + UINFO(1, "Current working directory (CWD) is " << V3Os::cwd()); + } } unsigned V3Options::debugLevel(const string& tag) const VL_MT_SAFE { diff --git a/src/V3Options.h b/src/V3Options.h index d99dd69de..127142f66 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -226,6 +226,7 @@ private: bool m_build = false; // main switch: --build bool m_context = true; // main switch: --Wcontext bool m_coverageExpr = false; // main switch: --coverage-expr + bool m_coverageFsm = false; // main switch: --coverage-fsm bool m_coverageLine = false; // main switch: --coverage-block bool m_coverageToggle = false; // main switch: --coverage-toggle bool m_coverageUnderscore = false; // main switch: --coverage-underscore @@ -447,7 +448,7 @@ private: void optimize(int level); void showVersion(bool verbose); void coverage(bool flag) { - m_coverageLine = m_coverageToggle = m_coverageExpr = m_coverageUser = flag; + m_coverageLine = m_coverageToggle = m_coverageExpr = m_coverageFsm = m_coverageUser = flag; } static bool suffixed(const string& sw, const char* arg); static string parseFileArg(const string& optdir, const string& relfilename); @@ -508,9 +509,19 @@ public: void buildDepBin(const string& flag) { m_buildDepBin = flag; } bool context() const VL_MT_SAFE { return m_context; } bool coverage() const VL_MT_SAFE { + // Any enabled coverage kind, including FSM coverage. Code generation + // and runtime support should generally query this accessor. + return m_coverageLine || m_coverageToggle || m_coverageExpr || m_coverageUser + || m_coverageFsm; + } + bool coverageNonFsm() const VL_MT_SAFE { + // The broad line/toggle/expr/user coverage transforms use this + // accessor. FSM coverage shares the overall coverage umbrella, but its + // extraction still happens through a separate early-recognition path. return m_coverageLine || m_coverageToggle || m_coverageExpr || m_coverageUser; } bool coverageExpr() const { return m_coverageExpr; } + bool coverageFsm() const { return m_coverageFsm; } bool coverageLine() const { return m_coverageLine; } bool coverageToggle() const { return m_coverageToggle; } bool coverageUnderscore() const { return m_coverageUnderscore; } @@ -534,6 +545,9 @@ public: bool diagnosticsSarif() const VL_MT_SAFE { return m_diagnosticsSarif; } bool dpiHdrOnly() const { return m_dpiHdrOnly; } bool dumpDefines() const { return m_dumpLevel.count("defines") && m_dumpLevel.at("defines"); } + bool dumpDfgPatterns() const { + return m_dumpLevel.count("dfg-patterns") && m_dumpLevel.at("dfg-patterns"); + } bool dumpTreeDot() const { return m_dumpLevel.count("tree-dot") && m_dumpLevel.at("tree-dot"); } diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 902f5477f..0bc8a68a8 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -475,6 +475,12 @@ class ParamProcessor final { } return nullptr; } + // Peel all unpacked-array layers to reach the innermost subDTypep. + // Used for multi-dim iface arrays where port dtype is nested UnpackArrayDType. + static AstNodeDType* arraySubDTypeDeepp(AstNodeDType* nodep) { + while (AstNodeDType* const subp = arraySubDTypep(nodep)) nodep = subp; + return nodep; + } static bool isString(AstNodeDType* nodep) { if (AstBasicDType* const basicp = VN_CAST(nodep->skipRefToNonRefp(), BasicDType)) return basicp->isString(); @@ -1406,13 +1412,16 @@ class ParamProcessor final { } cloneVarp->valuep(exprp->cloneTree(false)); if (AstNodeDType* const origDTypep = modvarp->subDTypep()) { - AstNodeDType* const dtypeClonep = origDTypep->cloneTree(false); - // Inline every param ref so widthing doesn't reach back into the template - // (#7411). Cycle detector for dependent parameters in the same module. + // Attach clone under cloneVarp so the root has a back pointer. + if (cloneVarp->childDTypep()) + cloneVarp->childDTypep()->unlinkFrBack()->deleteTree(); + cloneVarp->childDTypep(origDTypep->cloneTree(false)); + cloneVarp->dtypep(nullptr); + // Inline param refs so widthing doesn't touch the template (#7411). constexpr int maxSubstIters = 1000; for (int it = 0; it < maxSubstIters; ++it) { bool any = false; - dtypeClonep->foreach([&](AstVarRef* varrefp) { + cloneVarp->foreach([&](AstVarRef* varrefp) { AstVar* const targetp = varrefp->varp(); AstNode* replacep = nullptr; for (AstPin* pp = paramsp; pp; pp = VN_AS(pp->nextp(), Pin)) { @@ -1432,19 +1441,40 @@ class ParamProcessor final { any = true; } }); + // Substitute RefDType to an overridden paramtype. RefDType is + // not a foreach leaf, so collect matches and replace after the + // walk. Reverse order so descendants are replaced before + // ancestors -- replacing an ancestor would free its descendants. + std::vector> toReplace; + cloneVarp->foreach([&](AstRefDType* refp) { + AstParamTypeDType* const ptdp + = VN_CAST(refp->refDTypep(), ParamTypeDType); + if (!ptdp) return; + for (AstPin* pp = paramsp; pp; pp = VN_AS(pp->nextp(), Pin)) { + if (pp->modPTypep() == ptdp) { + if (AstNodeDType* const overDtp + = VN_CAST(pp->exprp(), NodeDType)) { + toReplace.emplace_back(refp, overDtp); + } + break; + } + } + }); + for (auto it = toReplace.rbegin(); it != toReplace.rend(); ++it) { + AstRefDType* const refp = it->first; + refp->replaceWith(it->second->cloneTree(false)); + VL_DO_DANGLING(refp->deleteTree(), refp); + any = true; + } if (!any) break; } // Bail if anything still points at the template. - dtypeClonep->foreach([&](AstVarRef* varrefp) { + cloneVarp->foreach([&](AstVarRef* varrefp) { varrefp->v3fatalSrc( "Unresolved VarRef '" << varrefp->prettyName() << "' in pin dtype clone. Pin: " << pinp->prettyNameQ() << " of " << nodep->prettyNameQ()); }); - if (cloneVarp->childDTypep()) - cloneVarp->childDTypep()->unlinkFrBack()->deleteTree(); - cloneVarp->childDTypep(dtypeClonep); - cloneVarp->dtypep(nullptr); } V3Const::constifyParamsEdit(cloneVarp); if (AstConst* const widthedp = VN_CAST(cloneVarp->valuep(), Const)) { @@ -1589,38 +1619,32 @@ class ParamProcessor final { const AstVar* const modvarp = pinp->modVarp(); if (modvarp && VN_IS(modvarp->subDTypep(), IfaceGenericDType)) continue; if (modvarp->isIfaceRef()) { - AstIfaceRefDType* portIrefp = VN_CAST(modvarp->subDTypep(), IfaceRefDType); - if (!portIrefp && arraySubDTypep(modvarp->subDTypep())) { - portIrefp = VN_CAST(arraySubDTypep(modvarp->subDTypep()), IfaceRefDType); - } - AstIfaceRefDType* pinIrefp = nullptr; + // arraySubDTypeDeepp returns input unchanged if not an array. + AstIfaceRefDType* const portIrefp + = VN_CAST(arraySubDTypeDeepp(modvarp->subDTypep()), IfaceRefDType); const AstNode* const exprp = pinp->exprp(); - const AstVar* const varp = (exprp && VN_IS(exprp, NodeVarRef)) - ? VN_AS(exprp, NodeVarRef)->varp() - : nullptr; - if (varp && varp->subDTypep() && VN_IS(varp->subDTypep(), IfaceRefDType)) { - pinIrefp = VN_AS(varp->subDTypep(), IfaceRefDType); - } else if (varp && varp->subDTypep() && arraySubDTypep(varp->subDTypep()) - && VN_CAST(arraySubDTypep(varp->subDTypep()), IfaceRefDType)) { - pinIrefp = VN_CAST(arraySubDTypep(varp->subDTypep()), IfaceRefDType); - } else if (exprp && exprp->op1p() && VN_IS(exprp->op1p(), VarRef) - && VN_CAST(exprp->op1p(), VarRef)->varp() - && VN_CAST(exprp->op1p(), VarRef)->varp()->subDTypep() - && arraySubDTypep(VN_CAST(exprp->op1p(), VarRef)->varp()->subDTypep()) - && VN_CAST( - arraySubDTypep(VN_CAST(exprp->op1p(), VarRef)->varp()->subDTypep()), - IfaceRefDType)) { - pinIrefp - = VN_AS(arraySubDTypep(VN_AS(exprp->op1p(), VarRef)->varp()->subDTypep()), - IfaceRefDType); - } else if (VN_IS(exprp, CellArrayRef)) { - // Interface array element selection (e.g., l1(l2.l1[0]) for nested iface - // array) The CellArrayRef is not yet fully linked to an interface type. Skip - // interface cleanup for this pin - V3LinkDot will resolve this later. Just - // continue to the next pin without error. + if (VN_IS(exprp, CellArrayRef)) { + // CellArrayRef not yet linked; V3LinkDot resolves this pin later. UINFO(9, "Skipping interface cleanup for CellArrayRef pin: " << pinp); continue; } + AstIfaceRefDType* pinIrefp = nullptr; + // Pin is a VarRef to a var of (possibly arrayed) iface type. + if (const AstNodeVarRef* const vrp = VN_CAST(exprp, NodeVarRef)) { + if (vrp->varp()) { + pinIrefp + = VN_CAST(arraySubDTypeDeepp(vrp->varp()->subDTypep()), IfaceRefDType); + } + } + // Pin's op1p is a VarRef (e.g. SelBit/ArraySel into an iface array). + if (!pinIrefp && exprp) { + if (const AstVarRef* const vrp = VN_CAST(exprp->op1p(), VarRef)) { + if (vrp->varp()) { + pinIrefp = VN_CAST(arraySubDTypeDeepp(vrp->varp()->subDTypep()), + IfaceRefDType); + } + } + } UINFO(9, " portIfaceRef " << portIrefp); @@ -2813,33 +2837,46 @@ class ParamVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(nodep), nodep); } void visit(AstCellArrayRef* nodep) override { - V3Const::constifyParamsEdit(nodep->selp()); - if (const AstConst* const constp = VN_CAST(nodep->selp(), Const)) { - const string index = AstNode::encodeNumber(constp->toSInt()); - // For nested interface array ports, the node name may have a __Viftop suffix - // that doesn't exist in the original unlinked text. Try without the suffix. - const string viftopSuffix = "__Viftop"; - const string baseName - = VString::endsWith(nodep->name(), viftopSuffix) - ? nodep->name().substr(0, nodep->name().size() - viftopSuffix.size()) - : nodep->name(); - const string replacestr = baseName + "__BRA__??__KET__"; - const size_t pos = m_unlinkedTxt.find(replacestr); - // For interface port array element selections (e.g., l1(l2.l1[0])), - // the AstCellArrayRef may be visited outside of an AstUnlinkedRef context. - // In such cases, m_unlinkedTxt won't contain the expected pattern. - // Simply skip the replacement - the cell array ref will be resolved later. - if (pos == string::npos) { - UINFO(9, "Skipping unlinked text replacement for " << nodep); + // Multi-dim iface arrays chain multiple selps via nextp(); constify each in place. + for (AstNode *s = nodep->selp(), *nxt; s; s = nxt) { + nxt = s->nextp(); // cache before constifyParamsEdit replaces s + V3Const::constifyParamsEdit(s); + } + std::vector indices; + for (AstNode* s = nodep->selp(); s; s = s->nextp()) { + const AstConst* const constp = VN_CAST(s, Const); + if (!constp) { + nodep->v3error("Could not expand constant selection inside dotted reference: " + << s->prettyNameQ()); return; } - m_unlinkedTxt.replace(pos, replacestr.length(), - baseName + "__BRA__" + index + "__KET__"); - } else { - nodep->v3error("Could not expand constant selection inside dotted reference: " - << nodep->selp()->prettyNameQ()); + indices.push_back(constp->toSInt()); + } + // Nested iface-array ports may carry a __Viftop suffix that's not in m_unlinkedTxt. + const string viftopSuffix = "__Viftop"; + const string baseName + = VString::endsWith(nodep->name(), viftopSuffix) + ? nodep->name().substr(0, nodep->name().size() - viftopSuffix.size()) + : nodep->name(); + const string placeholder = "__BRA__??__KET__"; + size_t pos = m_unlinkedTxt.find(baseName + placeholder); + // An AstCellArrayRef for an iface-array pin expr (e.g. l1(l2.l1[0])) is visited + // outside AstUnlinkedRef, so m_unlinkedTxt has no placeholder; V3LinkDot resolves later. + if (pos == string::npos) { + UINFO(9, "Skipping unlinked text replacement for " << nodep); return; } + pos += baseName.length(); + for (int idx : indices) { + const string replacement = "__BRA__" + AstNode::encodeNumber(idx) + "__KET__"; + if (m_unlinkedTxt.compare(pos, placeholder.length(), placeholder) != 0) { + nodep->v3fatalSrc( // LCOV_EXCL_LINE + "Expected placeholder at position in multi-dim iface dotted reference"); + return; + } + m_unlinkedTxt.replace(pos, placeholder.length(), replacement); + pos += replacement.length(); + } } // Generate Statements diff --git a/src/V3ParseGrammar.cpp b/src/V3ParseGrammar.cpp index fdc0e218b..d0c5ac659 100644 --- a/src/V3ParseGrammar.cpp +++ b/src/V3ParseGrammar.cpp @@ -106,8 +106,8 @@ AstAssignW* V3ParseGrammar::createSupplyExpr(FileLine* fileline, const string& n return assignp; } -AstRange* V3ParseGrammar::scrubRange(AstNodeRange* nrangep) { - // Remove any UnsizedRange's from list +AstRange* V3ParseGrammar::scrubRangeMulti(AstNodeRange* nrangep) { + // Remove any UnsizedRange's from list; preserves multi-dim chain via nextp(). for (AstNodeRange *nodep = nrangep, *nextp; nodep; nodep = nextp) { nextp = VN_AS(nodep->nextp(), NodeRange); if (!VN_IS(nodep, Range)) { @@ -117,14 +117,17 @@ AstRange* V3ParseGrammar::scrubRange(AstNodeRange* nrangep) { VL_DO_DANGLING(nodep->deleteTree(), nodep); } } - if (nrangep && nrangep->nextp()) { - // Not supported by at least 2 of big 3 - nrangep->nextp()->v3warn(E_UNSUPPORTED, - "Unsupported: Multidimensional instances/interfaces."); - nrangep->nextp()->unlinkFrBackWithNext()->deleteTree(); - } return VN_CAST(nrangep, Range); } +AstRange* V3ParseGrammar::scrubRange(AstNodeRange* nrangep) { + AstRange* const rangep = scrubRangeMulti(nrangep); + if (rangep && rangep->nextp()) { + // Gate primitives only support a single dimension + rangep->nextp()->v3warn(E_UNSUPPORTED, "Unsupported: Multidimensional gate instances."); + rangep->nextp()->unlinkFrBackWithNext()->deleteTree(); + } + return rangep; +} AstNodePreSel* V3ParseGrammar::scrubSel(AstNodeExpr* fromp, AstNodePreSel* selp) VL_MT_DISABLED { // SEL(PARSELVALUE, ...) -> SEL(fromp, ...) diff --git a/src/V3ParseGrammar.h b/src/V3ParseGrammar.h index c7b251f5a..d861d0988 100644 --- a/src/V3ParseGrammar.h +++ b/src/V3ParseGrammar.h @@ -71,6 +71,7 @@ public: return v3Global.opt.trace() && m_tracingParse && fl->tracingOn(); } AstRange* scrubRange(AstNodeRange* rangep) VL_MT_DISABLED; + AstRange* scrubRangeMulti(AstNodeRange* rangep) VL_MT_DISABLED; AstNodePreSel* scrubSel(AstNodeExpr* fromp, AstNodePreSel* selp) VL_MT_DISABLED; AstNodeDType* createArray(AstNodeDType* basep, AstNodeRange* rangep, bool isPacked) VL_MT_DISABLED; @@ -90,7 +91,7 @@ public: singletonp()->m_instModule, pinlistp, (singletonp()->m_instParamp ? singletonp()->m_instParamp->cloneTree(true) : nullptr), - singletonp()->scrubRange(rangelistp)}; + singletonp()->scrubRangeMulti(rangelistp)}; nodep->trace(singletonp()->allTracingOn(fileline)); return nodep; } diff --git a/src/V3Premit.cpp b/src/V3Premit.cpp index 26104ae08..199e6f5b7 100644 --- a/src/V3Premit.cpp +++ b/src/V3Premit.cpp @@ -263,6 +263,10 @@ class PremitVisitor final : public VNVisitor { iterateChildren(nodep); checkNode(nodep); } + void visit(AstCMethodHard* nodep) override { + iterateChildren(nodep); + checkNode(nodep); + } void visit(AstCvtArrayToPacked* nodep) override { iterateChildren(nodep); checkNode(nodep); diff --git a/src/V3ProtectLib.cpp b/src/V3ProtectLib.cpp index c7be84580..49aa38f56 100644 --- a/src/V3ProtectLib.cpp +++ b/src/V3ProtectLib.cpp @@ -221,7 +221,7 @@ class ProtectVisitor final : public VNVisitor { + "_protectlib_final(chandle handle__V);\n\n"); // Local variables - // Avoid tracing handle, as it is not a stable value, so breaks vcddiff + // Avoid tracing handle, as it is not a stable value, so breaks wavediff // Likewise other internals aren't interesting to the user txtp->add("// verilator tracing_off\n"); diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index caea970f0..993ba2207 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -2760,7 +2760,9 @@ class CaptureVisitor final : public VNVisitor { m_ignore.emplace(thisRefp); AstMemberSel* const memberSelp = new AstMemberSel{nodep->fileline(), thisRefp, nodep->varp()}; - if (!m_targetp) memberSelp->user1(true); + // Propagate random-dependence marking; forcing it on read-only operands + // would route them through the solver write path. + if (!m_targetp && nodep->user1()) memberSelp->user1(true); memberSelp->user2p(m_targetp); nodep->replaceWith(memberSelp); VL_DO_DANGLING(pushDeletep(nodep), nodep); diff --git a/src/V3Trace.cpp b/src/V3Trace.cpp index e0471ff1c..a9b7b419b 100644 --- a/src/V3Trace.cpp +++ b/src/V3Trace.cpp @@ -47,6 +47,7 @@ #include "V3UniqueNames.h" #include +#include #include #include @@ -216,6 +217,9 @@ class TraceVisitor final : public VNVisitor { using ActCodeSet = std::set; // For activity set, what traces apply using TraceVec = std::multimap; + // Candidate interface-member VarScopes keyed by (interface type, member name) + std::map, std::vector> + m_ifaceMemberVscps; // METHODS @@ -1125,6 +1129,13 @@ class TraceVisitor final : public VNVisitor { if (nodep->isTop()) m_topModp = nodep; iterateChildren(nodep); } + void visit(AstVarScope* nodep) override { + if (!m_finding) { + if (const AstIface* const ifacep = nodep->varp()->sensIfacep()) { + m_ifaceMemberVscps[{ifacep, nodep->varp()->name()}].push_back(nodep); + } + } + } void visit(AstStmtExpr* nodep) override { if (!m_finding && !nodep->user2()) { if (AstCCall* const callp = VN_CAST(nodep->exprp(), CCall)) { @@ -1214,6 +1225,27 @@ class TraceVisitor final : public VNVisitor { } } } + void visit(AstMemberSel* nodep) override { + if (m_cfuncp && m_finding && nodep->access().isWriteOrRW()) { + AstIfaceRefDType* const dtypep + = VN_CAST(nodep->fromp()->dtypep()->skipRefp(), IfaceRefDType); + if (dtypep && dtypep->isVirtual()) { + const auto it = m_ifaceMemberVscps.find({dtypep->ifacep(), nodep->varp()->name()}); + if (it != m_ifaceMemberVscps.end()) { + V3GraphVertex* const funcVtxp = getCFuncVertexp(m_cfuncp); + for (AstVarScope* const vscp : it->second) { + V3GraphVertex* varVtxp = vscp->user1u().toGraphVertex(); + if (!varVtxp) { + varVtxp = new TraceVarVertex{&m_graph, vscp}; + vscp->user1p(varVtxp); + } + new V3GraphEdge{&m_graph, funcVtxp, varVtxp, 1}; + } + } + } + } + iterateChildren(nodep); + } //-------------------- void visit(AstNode* nodep) override { iterateChildren(nodep); } diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 3154d537a..4eef35cf7 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -55,6 +55,7 @@ #include "V3File.h" #include "V3Force.h" #include "V3Fork.h" +#include "V3FsmDetect.h" #include "V3FuncOpt.h" #include "V3Gate.h" #include "V3Global.h" @@ -232,9 +233,11 @@ static void process() { v3Global.vlExit(0); } - // Coverage insertion - // Before we do dead code elimination and inlining, or we'll lose it. - if (v3Global.opt.coverage()) V3Coverage::coverage(v3Global.rootp()); + // Insert generic non-FSM coverage before dead code elimination and + // inlining, or those opportunities may be optimized away. FSM + // coverage is handled later in V3FsmDetect, after scoping has created + // the AST context needed to recover and lower FSMs reliably. + if (v3Global.opt.coverageNonFsm()) V3Coverage::coverage(v3Global.rootp()); // Resolve randsequence if they are used by the design if (v3Global.useRandSequence()) V3RandSequence::randSequenceNetlist(v3Global.rootp()); @@ -347,6 +350,12 @@ static void process() { // No more AstAlias after linkDotScope V3Scope::scopeAll(v3Global.rootp()); V3LinkDot::linkDotScope(v3Global.rootp()); + // FSM coverage needs scopes, but should otherwise run as early as + // possible before later lowering rewrites user-visible clocked + // case structure. This entry point runs two adjacent phases: + // detect into local graph state, then lower that completed state + // into the concrete coverage machinery. + if (v3Global.opt.coverageFsm()) V3FsmDetect::detect(v3Global.rootp()); // Relocate classes (after linkDot) V3Class::classAll(v3Global.rootp()); @@ -428,8 +437,9 @@ static void process() { "This may cause ordering problems."); } - // Combine COVERINCs with duplicate terms - if (v3Global.opt.coverage()) V3CoverageJoin::coverageJoin(v3Global.rootp()); + // Combine generic COVERINCs with duplicate terms. FSM coverage is + // already lowered separately inside V3FsmDetect. + if (v3Global.opt.coverageNonFsm()) V3CoverageJoin::coverageJoin(v3Global.rootp()); // Remove unused vars V3Const::constifyAll(v3Global.rootp()); @@ -447,7 +457,7 @@ static void process() { } // Create delayed assignments - // This creates lots of duplicate ACTIVES so ActiveTop needs to be after this step + // This creates lots of duplicate ACTIVES so ActiveTop needs to be after this step. V3Delayed::delayedAll(v3Global.rootp()); // Make Active's on the top level. diff --git a/src/VlcMain.cpp b/src/VlcMain.cpp index e5113a966..fb485c500 100644 --- a/src/VlcMain.cpp +++ b/src/VlcMain.cpp @@ -69,6 +69,7 @@ void VlcOptions::parseOptsList(int argc, char** argv) { DECL_OPTION("-debug", CbCall, []() { V3Error::debugDefault(3); }); DECL_OPTION("-debugi", CbVal, [](int v) { V3Error::debugDefault(v); }); DECL_OPTION("-filter-type", Set, &m_filterType); + DECL_OPTION("-include-reset-arcs", OnOff, &m_includeResetArcs); DECL_OPTION("-rank", OnOff, &m_rank); DECL_OPTION("-unlink", OnOff, &m_unlink); DECL_OPTION("-V", CbCall, []() { @@ -140,6 +141,10 @@ int main(int argc, char** argv) { top.points().dump(); } + if (!top.opt.rank() && top.opt.writeFile().empty() && top.opt.writeInfoFile().empty()) { + top.printTypeSummary(); + } + V3Error::abortIfWarnings(); if (!top.opt.annotateOut().empty()) top.annotate(top.opt.annotateOut()); diff --git a/src/VlcOptions.h b/src/VlcOptions.h index 7d957f722..3e46db033 100644 --- a/src/VlcOptions.h +++ b/src/VlcOptions.h @@ -39,6 +39,7 @@ class VlcOptions final { bool m_annotateAll = false; // main switch: --annotate-all int m_annotateMin = 10; // main switch: --annotate-min I bool m_annotatePoints = false; // main switch: --annotate-points + bool m_includeResetArcs = false; // main switch: --include-reset-arcs string m_filterType = "*"; // main switch: --filter-type VlStringSet m_readFiles; // main switch: --read bool m_rank = false; // main switch: --rank @@ -67,6 +68,7 @@ public: int annotateMin() const { return m_annotateMin; } bool countOk(uint64_t count) const { return count >= static_cast(m_annotateMin); } bool annotatePoints() const { return m_annotatePoints; } + bool includeResetArcs() const { return m_includeResetArcs; } bool rank() const { return m_rank; } bool unlink() const { return m_unlink; } string writeFile() const { return m_writeFile; } diff --git a/src/VlcPoint.h b/src/VlcPoint.h index 372b9f2bf..94fba6028 100644 --- a/src/VlcPoint.h +++ b/src/VlcPoint.h @@ -70,6 +70,18 @@ public: return keyExtract(VL_CIK_THRESH, m_name.c_str()); } string linescov() const { return keyExtract(VL_CIK_LINESCOV, m_name.c_str()); } + bool isFsmState() const { return type() == "fsm_state"; } + bool isFsmArc() const { return type() == "fsm_arc"; } + // Arc-specific helpers are used after callers have already filtered to + // FSM arc points, so they do not repeat the type check here. + string fsmVarName() const { return keyExtract(VL_CIK_FSM_VAR, m_name.c_str()); } + string fsmFromState() const { return keyExtract(VL_CIK_FSM_FROM, m_name.c_str()); } + string fsmToState() const { return keyExtract(VL_CIK_FSM_TO, m_name.c_str()); } + string fsmTag() const { return keyExtract(VL_CIK_FSM_TAG, m_name.c_str()); } + bool isFsmResetInclude() const { return fsmTag() == "reset_include"; } + bool isFsmResetArc() const { return fsmTag() == "reset"; } + bool isFsmDefaultArc() const { return fsmTag() == "default"; } + bool fsmIsReset() const { return isFsmResetArc() || isFsmResetInclude(); } int lineno() const { const string lineStr = keyExtract(VL_CIK_LINENO, m_name.c_str()); return std::atoi(lineStr.c_str()); diff --git a/src/VlcTop.cpp b/src/VlcTop.cpp index aad1f7f0d..37cee819d 100644 --- a/src/VlcTop.cpp +++ b/src/VlcTop.cpp @@ -25,6 +25,8 @@ #include #include +#include +#include #include #include @@ -122,12 +124,18 @@ void VlcTop::writeInfo(const string& filename) { int branchesHit = 0; for (auto& li : lines) { VlcSourceCount& sc = li.second; - os << "DA:" << sc.lineno() << "," << sc.maxCount() << "\n"; - const int num_branches = sc.points().size(); - if (num_branches == 1) continue; - branchesFound += num_branches; - int point_num = 0; + uint64_t daCount = 0; + std::vector infoPoints; for (const auto& point : sc.points()) { + if (point->isFsmArc()) continue; + daCount = std::max(daCount, point->count()); + if (!point->isFsmState()) infoPoints.push_back(point); + } + os << "DA:" << sc.lineno() << "," << daCount << "\n"; + if (infoPoints.size() <= 1) continue; + branchesFound += static_cast(infoPoints.size()); + int point_num = 0; + for (const VlcPoint* point : infoPoints) { os << "BRDA:" << sc.lineno() << ","; os << "0,"; os << point_num << ","; @@ -269,8 +277,10 @@ void VlcTop::annotateCalcNeeded() { } } const float pct = totCases ? (100 * totOk / totCases) : 0; - std::cout << "Total coverage (" << totOk << "/" << totCases << ") "; - std::cout << std::fixed << std::setw(3) << std::setprecision(2) << pct << "%\n"; + std::cout << "Annotation Summary:\n"; + std::cout << " lines with all attached points covered : "; + std::cout << std::fixed << std::setw(5) << std::setprecision(2) << pct << "% (" << totOk + << "/" << totCases << ")\n"; if (totOk != totCases) cout << "See lines with '%00' in " << opt.annotateOut() << '\n'; } @@ -326,6 +336,29 @@ void VlcTop::annotateOutputFiles(const string& dirname) { if (opt.annotatePoints()) { for (const auto& pit : sc.points()) pit->dumpAnnotate(os, opt.annotateMin()); } + bool printedFsmHeader = false; + for (const auto& pit : sc.points()) { + if (!pit->isFsmState() && !pit->isFsmArc()) continue; + if (!printedFsmHeader) { + os << " // [FSM coverage]\n"; + printedFsmHeader = true; + } + os << (opt.countOk(pit->count()) ? " " : "%"); + os << std::setfill('0') << std::setw(6) << pit->count() << " "; + if (pit->isFsmState()) { + os << "// [fsm_state " << pit->comment() << "]"; + if (pit->count() == 0) os << " *** UNCOVERED ***"; + os << "\n"; + } else if (pit->isFsmDefaultArc()) { + os << "// [SYNTHETIC DEFAULT ARC: " << pit->comment() << "]\n"; + } else { + os << "// [fsm_arc " << pit->comment() << "]"; + if (pit->fsmIsReset() && !opt.includeResetArcs()) { + os << " [reset arc, excluded from %]"; + } + os << "\n"; + } + } } } } @@ -337,3 +370,47 @@ void VlcTop::annotate(const string& dirname) { annotateCalcNeeded(); annotateOutputFiles(dirname); } + +void VlcTop::printTypeSummary() { + static const std::vector orderedTypes = {"line", "toggle", "branch", "expr"}; + std::map> tally; + for (const auto& i : m_points) { + const VlcPoint& pt = m_points.pointNumber(i.second); + const string type = pt.type().empty() ? "point" : pt.type(); + auto& entry = tally[type]; + if (pt.count() > 0) ++entry.first; + ++entry.second; + } + if (tally.empty()) return; + std::set printed; + size_t typeWidth = 0; + size_t countWidth = 0; + for (const auto& it : tally) { + typeWidth = std::max(typeWidth, it.first.size()); + countWidth = std::max(countWidth, cvtToStr(it.second.first).size()); + countWidth = std::max(countWidth, cvtToStr(it.second.second).size()); + } + std::cout << "Coverage Summary:\n"; + for (const string& type : orderedTypes) { + const auto it = tally.find(type); + if (it == tally.end()) continue; + printed.insert(type); + const uint64_t hit = it->second.first; + const uint64_t total = it->second.second; + const double pct + = total ? (100.0 * static_cast(hit) / static_cast(total)) : 0.0; + std::cout << " " << std::left << std::setw(typeWidth) << type << " : " << std::right + << std::fixed << std::setprecision(1) << pct << "% (" << std::setw(countWidth) + << hit << "/" << std::setw(countWidth) << total << ")\n"; + } + for (const auto& it : tally) { + if (printed.count(it.first)) continue; + const uint64_t hit = it.second.first; + const uint64_t total = it.second.second; + const double pct + = total ? (100.0 * static_cast(hit) / static_cast(total)) : 0.0; + std::cout << " " << std::left << std::setw(typeWidth) << it.first << " : " << std::right + << std::fixed << std::setprecision(1) << pct << "% (" << std::setw(countWidth) + << hit << "/" << std::setw(countWidth) << total << ")\n"; + } +} diff --git a/src/VlcTop.h b/src/VlcTop.h index 13e0d93d6..a4dd731ee 100644 --- a/src/VlcTop.h +++ b/src/VlcTop.h @@ -55,6 +55,7 @@ public: // METHODS void annotate(const string& dirname); + void printTypeSummary(); void readCoverage(const string& filename, bool nonfatal = false); void writeCoverage(const string& filename); void writeInfo(const string& filename); diff --git a/src/astgen b/src/astgen index b9a9fed51..75f68d5fb 100755 --- a/src/astgen +++ b/src/astgen @@ -31,6 +31,7 @@ class Node: self._arity = -1 # Arity of node self._ops = {} # Operands of node self._ptrs = [] # Pointer members of node (name, types) + self._makeDfgVertex = False # Create a corresponding DfgVertex sub-class @property def name(self): @@ -68,6 +69,10 @@ class Node: assert self.isCompleted return self._ptrs + @property + def makeDfgVertex(self): + return self._makeDfgVertex + # Pre completion methods def addSubClass(self, subClass): assert not self.isCompleted @@ -91,6 +96,9 @@ class Node: name = re.sub(r'^m_', '', name) self._ptrs.append({'name': name, 'monad': monad, 'kind': kind, 'legals': legals}) + def setMakeDfgVertex(self): + self._makeDfgVertex = True + # Computes derived properties over entire class hierarchy. # No more changes to the hierarchy are allowed once this was called def complete(self, typeId=0, ordIdx=0): @@ -609,9 +617,12 @@ def read_types(filename, Nodes, prefix): if prefix != "Ast": continue - match = re.match(r'^\s*//\s*@astgen\s+(.*)$', line) - if match: + if match := re.match(r'^\s*//\s*@astgen\s+(.*)$', line): decl = re.sub(r'//.*$', '', match.group(1)) + if decl == "makeDfgVertex": + node.setMakeDfgVertex() + continue + what, sep, rest = partitionAndStrip(decl, ":=") what = re.sub(r'\s+', ' ', what) if not sep: @@ -668,10 +679,12 @@ def read_types(filename, Nodes, prefix): error( lineno, "Malformed @astgen what (expecting 'op1'..'op4'," + " 'alias op1'.., 'ptr'): " + what) - else: - line = re.sub(r'//.*$', '', line) - if re.match(r'.*[Oo]p[1-9].*', line): - error(lineno, "Use generated accessors to access op operands") + continue + + line = re.sub(r'//.*$', '', line) + + if re.match(r'.*[Oo]p[1-9].*', line): + error(lineno, "Use generated accessors to access op operands") if re.match(r'^\s*Ast[A-Z][A-Za-z0-9_]+\s*\*(\s*const)?\s+m_[A-Za-z0-9_]+\s*;', line): error(lineno, "Use '@astgen ptr' for Ast pointer members: " + line) @@ -1407,116 +1420,6 @@ check_types(AstNodeList, "Ast", "Node") # These are standalone so we don't need to parse the sources for this. DfgVertices["Vertex"] = Node("Vertex", None) -# AstNodeExpr that are not representable in Dfg -DfgIgnored = ( - # Floating point operations - "AcosD", - "AcoshD", - "AddD", - "AsinD", - "AsinhD", - "Atan2D", - "AtanD", - "AtanhD", - "BitsToRealD", - "CeilD", - "CosD", - "CoshD", - "DivD", - "EqD", - "ExpD", - "FloorD", - "GtD", - "GteD", - "HypotD", - "ISToRD", - "IToRD", - "Log10D", - "LogD", - "LtD", - "LteD", - "MulD", - "NegateD", - "NeqD", - "PowD", - "RealToBits", - "RToIRoundS", - "RToIS", - "SinD", - "SinhD", - "SqrtD", - "SubD", - "TanD", - "TanhD", - # String operations - "AtoN", - "CompareNN", - "ConcatN", - "CvtPackString", - "EqN", - "GetcN", - "GetcRefN", - "GteN", - "GtN", - "LenN", - "LteN", - "LtN", - "NeqN", - "NToI", - "PutcN", - "ReplicateN", - "SubstrN", - "ToLowerN", - "ToStringN", - "ToUpperN", - # Effectful - "PostAdd", - "PostSub", - "PreAdd", - "PreSub", - # Only used after DFG - "ShiftLOvr", - "ShiftROvr", - "ShiftRSOvr", - "WordSel", - # File operations - "FEof", - "FGetC", - "FGetS", - "FUngetC", - # Dynamic array operations - "AssocSel", - "IsUnbounded", - "WildcardSel", - # Type comparison - "EqT", - "NeqT", - # Distributions - "DistChiSquare", - "DistErlang", - "DistExponential", - "DistNormal", - "DistPoisson", - "DistT", - "DistUniform", - # Specials - "CastDynamic", - "CastWrap", - "CAwait", - "CCast", - "CLog2", - "IsUnknown", - "NullCheck", - "OneHot", - "OneHot0", - "ResizeLValue", - "Signed", - "SliceSel", - "TimeImport", - "Unsigned", - "URandomRange", -) - # Read DfgVertex definitions for filename in Args.dfgdef: read_types(os.path.join(Args.I, filename), DfgVertices, "Dfg") @@ -1527,12 +1430,8 @@ for node in AstNodeList: if not node.isLeaf: continue - # Ignore any explicitly defined vertex - if node.name in DfgVertices: - continue - - # Ignore expressions types that DFG cannot handle - if node.name in DfgIgnored: + # Only create vertices for explcitly marked AstNode sub-types + if not node.makeDfgVertex: continue if node.isSubClassOf(AstNodes["NodeUniop"]): @@ -1542,7 +1441,9 @@ for node in AstNodeList: elif node.isSubClassOf(AstNodes["NodeTriop"]): base = DfgVertices["VertexTernary"] else: - continue + sys.exit( + "%Error: cannot create DfgVertex for node not derived from AstNodeUnary, AstNodeBinary, or AstNodeTernary: " + + node.name) vertex = Node(node.name, base) DfgVertices[node.name] = vertex diff --git a/src/verilog.l b/src/verilog.l index 4e4796a5c..4dc79f210 100644 --- a/src/verilog.l +++ b/src/verilog.l @@ -848,6 +848,14 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5} "/*verilator sc_clock*/" { FL; yylval.fl->v3warn(DEPRECATED, "sc_clock is ignored"); FL_BRK; } "/*verilator sformat*/" { FL; return yVL_SFORMAT; } "/*verilator split_var*/" { FL; return yVL_SPLIT_VAR; } + /* Experimental Verilator-specific FSM coverage controls. These names were + * chosen to match the current extractor behavior, not a published synthesis + * or simulator pragma standard, so they may evolve as we settle on longer- + * term compatibility/aliasing. + */ + "/*verilator fsm_arc_include_cond*/" { FL; return yVL_FSM_ARC_INCL_COND; } + "/*verilator fsm_reset_arc*/" { FL; return yVL_FSM_RESET_ARC; } + "/*verilator fsm_state*/" { FL; return yVL_FSM_STATE; } "/*verilator tag"[^*]*"*/" { FL; yylval.strp = PARSEP->newString(V3ParseImp::lexParseTag(yytext)); return yVL_TAG; } "/*verilator timing_off*/" { FL_FWD; PARSEP->lexFileline()->timingOn(false); FL_BRK; } diff --git a/src/verilog.y b/src/verilog.y index e6507d003..f809924ee 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -804,6 +804,9 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"}) %token yVL_SC_BV "/*verilator sc_bv*/" %token yVL_SFORMAT "/*verilator sformat*/" %token yVL_SPLIT_VAR "/*verilator split_var*/" +%token yVL_FSM_ARC_INCL_COND "/*verilator fsm_arc_include_cond*/" +%token yVL_FSM_RESET_ARC "/*verilator fsm_reset_arc*/" +%token yVL_FSM_STATE "/*verilator fsm_state*/" %token yVL_TAG "/*verilator tag*/" %token yVL_UNROLL_DISABLE "/*verilator unroll_disable*/" %token yVL_UNROLL_FULL "/*verilator unroll_full*/" @@ -3123,6 +3126,9 @@ sigAttr: | yVL_SC_BV { $$ = new AstAttrOf{$1, VAttrType::VAR_SC_BV}; } | yVL_SFORMAT { $$ = new AstAttrOf{$1, VAttrType::VAR_SFORMAT}; } | yVL_SPLIT_VAR { $$ = new AstAttrOf{$1, VAttrType::VAR_SPLIT_VAR}; } + | yVL_FSM_ARC_INCL_COND { $$ = new AstAttrOf{$1, VAttrType::VAR_FSM_ARC_INCLUDE_COND}; } + | yVL_FSM_RESET_ARC { $$ = new AstAttrOf{$1, VAttrType::VAR_FSM_RESET_ARC}; } + | yVL_FSM_STATE { $$ = new AstAttrOf{$1, VAttrType::VAR_FSM_STATE}; } ; rangeListE: // IEEE: [{packed_dimension}] diff --git a/test_regress/driver.py b/test_regress/driver.py index 9489e1c8f..11d9a2a4b 100755 --- a/test_regress/driver.py +++ b/test_regress/driver.py @@ -7,7 +7,6 @@ import collections import ctypes import glob import hashlib -import json import logging import multiprocessing import os @@ -2511,104 +2510,19 @@ class VlTest: print("%Warning: HARNESS_UPDATE_GOLDEN set: cp " + fn1 + " " + fn2, file=sys.stderr) shutil.copy(fn1, fn2) - def vcd_identical(self, fn1: str, fn2: str, ignore_attr: bool = False) -> None: - """Test if two VCD files have logically-identical contents""" - # vcddiff to check transitions, if installed - cmd = "vcddiff --help" - out = test.run_capture(cmd, check=True) - cmd = 'vcddiff ' + fn1 + ' ' + fn2 - out = test.run_capture(cmd, check=True) - if out != "": - cmd = 'vcddiff ' + fn2 + " " + fn1 # Reversed arguments - out = VtOs.run_capture(cmd, check=False) - if out != "": - print(out) - self.copy_if_golden(fn1, fn2) - self.error("VCD miscompares " + fn2 + " " + fn1) - - # vcddiff doesn't check module and variable scope, so check that - # Also provides backup if vcddiff not installed - h1 = self._vcd_read(fn1) - h2 = self._vcd_read(fn2) - if ignore_attr: - h1 = {k: v for k, v in h1.items() if "$attr" not in v} - h2 = {k: v for k, v in h2.items() if "$attr" not in v} - a = json.dumps(h1, sort_keys=True, indent=1) - b = json.dumps(h2, sort_keys=True, indent=1) - if a != b: + def vcd_identical(self, fn1: str, fn2: str) -> None: + """Test if two VCD/FST files have logically-identical contents""" + cmd = VtOs.getenv_def('WAVEDIFF', 'wavediff') + ' --epsilon 0.0000001 ' + fn1 + ' ' + fn2 + proc = subprocess.run([cmd], capture_output=True, text=True, shell=True, check=False) + if proc.returncode: + print(proc.stderr) + print(proc.stdout) self.copy_if_golden(fn1, fn2) - self.error("VCD hier miscompares " + fn1 + " " + fn2 + "\nGOT=" + a + "\nEXP=" + b + - "\n") + self.error("VCD miscompares " + fn1 + " " + fn2) - def fst2vcd(self, fn1: str, fn2: str) -> None: - cmd = "fst2vcd -h" - out = VtOs.run_capture(cmd, check=False) - if out == "" or not re.search(r'Usage:', out): - self.skip("No fst2vcd installed") - return - - cmd = 'fst2vcd -e -f "' + fn1 + '" -o "' + fn2 + '"' - print("\t " + cmd + "\n") # Always print to help debug race cases - out = VtOs.run_capture(cmd, check=False) - print(out) - - # Post-process file and fix up to match upcoming fst2vcd output - # also reindent for readability - - # Slurp whole file - with open(fn2, 'r', encoding='latin-1') as fd: - lines = fd.readlines() - - # Process line by line - new_lines = [] - fixup_array_scope = False - indent = "" - for line in lines: - line = line.strip() - # Change "$attrbegin class" to "$attrbegin pack" - if match := re.match(r'^(\$attrbegin\s+)class(.*)', line): - line = indent + match.group(1) + "pack" + match.group(2) - # Check for "$attrbegin array" - elif re.search(r'^\$attrbegin\s+array', line): - line = indent + line - fixup_array_scope = True - # Check for "$scope" - elif match := re.match(r'(\$scope\s)(\S+)(.*)', line.lstrip('\r\n')): - if not indent: - indent = " " - # Fix up array scope - if (match.group(2) == "module") and fixup_array_scope: - line = indent + match.group(1) + "sv_array" + match.group(3) - fixup_array_scope = False - else: - line = indent + line - indent += " " - # Check for "$upscope" - elif re.search(r'^\$upscope', line): - indent = indent[0:-1] - line = indent + line - if len(indent) == 1: - indent = "" - # Just reindent - else: - line = indent + line - new_lines.append(line + "\n") - - # Write back to file - with open(fn2, 'w', encoding='latin-1') as fd: - fd.writelines(new_lines) - - def fst_identical(self, fn1: str, fn2: str, ignore_attr: bool = False) -> None: + def fst_identical(self, fn1: str, fn2: str) -> None: """Test if two FST files have logically-identical contents""" - if fn1.endswith(".fst"): - tmp = fn1 + ".vcd" - self.fst2vcd(fn1, tmp) - fn1 = tmp - if fn2.endswith(".fst"): - tmp = fn2 + ".vcd" - self.fst2vcd(fn2, tmp) - fn2 = tmp - self.vcd_identical(fn1, fn2, ignore_attr) + self.vcd_identical(fn1, fn2) def saif_identical(self, fn1: str, fn2: str) -> None: """Test if two SAIF files have logically-identical contents""" @@ -2621,96 +2535,17 @@ class VlTest: self.copy_if_golden(fn1, fn2) self.error("SAIF files miscompare") - def trace_identical(self, traceFn: str, goldenFn: str, ignore_attr: bool = False) -> None: + def trace_identical(self, traceFn: str, goldenFn: str) -> None: match traceFn.rpartition(".")[-1]: case "vcd": - self.vcd_identical(traceFn, goldenFn, ignore_attr) + self.vcd_identical(traceFn, goldenFn) case "fst": - self.fst_identical(traceFn, goldenFn, ignore_attr) + self.fst_identical(traceFn, goldenFn) case "saif": self.saif_identical(traceFn, goldenFn) case _: self.error("Unknown trace file format " + traceFn) - @staticmethod - def _vcd_parse_header(filename: str, root_scope: 'str | None' = None) -> 'tuple[dict, dict]': - """Parse VCD header into hierarchy data and signal-code mapping. - - Returns (hier_data, var_codes) where: - hier_data: dict used by _vcd_read for hierarchy comparison - (scope paths -> scope description, var paths -> $var prefix, - attr paths -> $attrbegin text) - var_codes: dict mapping 'scope.signal' -> VCD code identifier - """ - hier_data: dict = {} - var_codes: dict = {} - hier_stack = [root_scope] if root_scope else [] - attr: list = [] - with open(filename, 'r', encoding='latin-1') as fh: - for line in fh: - m_scope = re.search(r'\$scope\s+(\S*)\s+(\S+)', line) - m_var = re.search(r'(\$var\s+\S+\s+\d+\s+)(\S+)\s+(.+)\s+\$end', line) - m_attr = re.search(r'(\$attrbegin .* \$end)', line) - if m_scope: - scope_type = m_scope.group(1) - name = m_scope.group(2) - hier_stack.append(name) - scope = '.'.join(hier_stack) - hier_data[scope] = scope_type + " " + name - if attr: - hier_data[scope + "#"] = " ".join(attr) - attr = [] - elif m_var: - scope = '.'.join(hier_stack) - decl_prefix = m_var.group(1) - code = m_var.group(2) - var_name = m_var.group(3) - hier_data[scope + "." + var_name] = decl_prefix - if attr: - hier_data[scope + "." + var_name + "#"] = " ".join(attr) - attr = [] - bare_name = var_name.split()[0] - var_codes[scope + "." + bare_name] = code - elif m_attr: - attr.append(m_attr.group(1)) - elif re.search(r'\$enddefinitions', line): - break - n = len(re.findall(r'\$upscope', line)) - if n: - for _i in range(0, n): - hier_stack.pop() - return hier_data, var_codes - - def _vcd_read(self, filename: str) -> dict: - data, _ = self._vcd_parse_header(filename, root_scope="TOP") - return data - - def vcd_extract_codes(self, filename: str) -> dict: - _, codes = self._vcd_parse_header(filename) - return codes - - def vcd_check_aliased(self, codes: dict, sig_a: str, sig_b: str) -> None: - code_a = codes.get(sig_a) - code_b = codes.get(sig_b) - if code_a is None: - self.error(f"Signal '{sig_a}' not found in VCD") - if code_b is None: - self.error(f"Signal '{sig_b}' not found in VCD") - if code_a != code_b: - self.error(f"Expected '{sig_a}' (code {code_a}) to alias " - f"'{sig_b}' (code {code_b})") - - def vcd_check_not_aliased(self, codes: dict, sig_a: str, sig_b: str) -> None: - code_a = codes.get(sig_a) - code_b = codes.get(sig_b) - if code_a is None: - self.error(f"Signal '{sig_a}' not found in VCD") - if code_b is None: - self.error(f"Signal '{sig_b}' not found in VCD") - if code_a == code_b: - self.error(f"Expected '{sig_a}' and '{sig_b}' to have different codes, " - f"both have code {code_a}") - def inline_checks(self) -> None: covfn = self.coverage_filename contents = self.file_contents(covfn) diff --git a/test_regress/t/t_assert_consec_rep_unsup.out b/test_regress/t/t_assert_consec_rep_unsup.out index 9129daa7f..69a178932 100644 --- a/test_regress/t/t_assert_consec_rep_unsup.out +++ b/test_regress/t/t_assert_consec_rep_unsup.out @@ -1,6 +1,6 @@ -%Error-UNSUPPORTED: t/t_assert_consec_rep_unsup.v:11:45: Unsupported: multi-cycle sequence expression inside consecutive repetition (IEEE 1800-2023 16.9.2) +%Error-UNSUPPORTED: t/t_assert_consec_rep_unsup.v:13:45: Unsupported: multi-cycle sequence expression inside consecutive repetition (IEEE 1800-2023 16.9.2) : ... note: In instance 't' - 11 | assert property (@(posedge clk) (a ##1 b) [* 2] |-> a); + 13 | assert property (@(posedge clk) (a ##1 b) [* 2] |-> a); | ^~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error: Exiting due to diff --git a/test_regress/t/t_assert_consec_rep_unsup.v b/test_regress/t/t_assert_consec_rep_unsup.v index 3fe3fba7a..d7dd174db 100644 --- a/test_regress/t/t_assert_consec_rep_unsup.v +++ b/test_regress/t/t_assert_consec_rep_unsup.v @@ -4,7 +4,9 @@ // SPDX-FileCopyrightText: 2026 PlanV GmbH // SPDX-License-Identifier: CC0-1.0 -module t (input clk); +module t ( + input clk +); logic a, b; // Unsupported: multi-cycle sequence expression inside consecutive repetition diff --git a/test_regress/t/t_cover_fsm_basic.out b/test_regress/t/t_cover_fsm_basic.out new file mode 100644 index 000000000..8fc6b1c5c --- /dev/null +++ b/test_regress/t/t_cover_fsm_basic.out @@ -0,0 +1,65 @@ +// // verilator_coverage annotation + // DESCRIPTION: Verilator: FSM coverage basic test + // + // This file ONLY is placed under the Creative Commons Public Domain. + // SPDX-FileCopyrightText: 2026 Wilson Snyder + // SPDX-License-Identifier: CC0-1.0 + + module t ( + input clk + ); + + 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*/; + + 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 +%000004 case (state) + // [FSM coverage] +%000001 // [fsm_arc t.state::ANY->S_IDLE[reset_include]] [reset arc, excluded from %] +%000004 // [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_arc t.state::S_RUN->S_DONE] +%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] + S_IDLE: if (start) state <= S_RUN; else state <= S_IDLE; + S_RUN: state <= S_DONE; + S_DONE: state <= S_DONE; + default: state <= S_ERR; + endcase + end + end + + endmodule + diff --git a/test_regress/t/t_cover_fsm_basic.py b/test_regress/t/t_cover_fsm_basic.py new file mode 100755 index 000000000..628914b4f --- /dev/null +++ b/test_regress/t/t_cover_fsm_basic.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage basic 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=['--cc --coverage-fsm']) + +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_basic.v b/test_regress/t/t_cover_fsm_basic.v new file mode 100644 index 000000000..237d89e84 --- /dev/null +++ b/test_regress/t/t_cover_fsm_basic.v @@ -0,0 +1,53 @@ +// DESCRIPTION: Verilator: FSM coverage basic test +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk +); + + 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*/; + + 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: state <= S_DONE; + S_DONE: state <= S_DONE; + default: state <= S_ERR; + endcase + end + end + +endmodule diff --git a/test_regress/t/t_cover_fsm_beginif.out b/test_regress/t/t_cover_fsm_beginif.out new file mode 100644 index 000000000..d24de8d04 --- /dev/null +++ b/test_regress/t/t_cover_fsm_beginif.out @@ -0,0 +1,63 @@ +// // verilator_coverage annotation + // DESCRIPTION: Verilator: FSM coverage begin-wrapped/if-else 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 + + module t( + input logic clk + ); + + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + logic rst; + logic sel; + int cyc; + state_t state /*verilator fsm_reset_arc*/; + + initial begin + rst = 1'b1; + sel = 1'b0; + cyc = 0; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) rst <= 1'b0; + if (cyc == 2) sel <= 1'b1; + if (cyc == 3) sel <= 1'b0; + if (cyc == 6) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + always_ff @(posedge clk) begin + if (rst) begin + state <= S0; + end else begin +%000003 case (state) + // [FSM coverage] +%000001 // [fsm_arc t.state::ANY->S0[reset_include]] [reset arc, excluded from %] +%000000 // [fsm_arc t.state::S0->S1] +%000003 // [fsm_arc t.state::S0->S2] +%000000 // [fsm_arc t.state::S1->S0] +%000002 // [fsm_state t.state::S0] +%000000 // [fsm_state t.state::S1] *** UNCOVERED *** +%000003 // [fsm_state t.state::S2] + S0: if (sel) state <= S1; else state <= S2; + S1: state <= S0; + default: state <= S0; + endcase + end + end + + endmodule + diff --git a/test_regress/t/t_cover_fsm_beginif.py b/test_regress/t/t_cover_fsm_beginif.py new file mode 100755 index 000000000..46a0e0d22 --- /dev/null +++ b/test_regress/t/t_cover_fsm_beginif.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage begin-wrapped/if-else extraction 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=['--cc --coverage-fsm']) +test.execute() + +# Use annotated-source output so the expected file captures both the extracted +# FSM shape and the per-point hit counts. +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_beginif.v b/test_regress/t/t_cover_fsm_beginif.v new file mode 100644 index 000000000..54a569f92 --- /dev/null +++ b/test_regress/t/t_cover_fsm_beginif.v @@ -0,0 +1,53 @@ +// DESCRIPTION: Verilator: FSM coverage begin-wrapped/if-else 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 + +module t( + input logic clk +); + + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + logic rst; + logic sel; + int cyc; + state_t state /*verilator fsm_reset_arc*/; + + initial begin + rst = 1'b1; + sel = 1'b0; + cyc = 0; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) rst <= 1'b0; + if (cyc == 2) sel <= 1'b1; + if (cyc == 3) sel <= 1'b0; + if (cyc == 6) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + always_ff @(posedge clk) begin + if (rst) begin + state <= S0; + end else begin + case (state) + S0: if (sel) state <= S1; else state <= S2; + S1: state <= S0; + default: state <= S0; + endcase + end + end + +endmodule diff --git a/test_regress/t/t_cover_fsm_decldump.py b/test_regress/t/t_cover_fsm_decldump.py new file mode 100755 index 000000000..dc2cd087d --- /dev/null +++ b/test_regress/t/t_cover_fsm_decldump.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM lowered coverage declaration dump 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 + +from pathlib import Path + +import vltest_bootstrap + +test.scenarios('vlt') +test.top_filename = "t/t_cover_fsm_styles.v" + +# Dump the lowered AST so AstCoverOtherDecl::dump() sees FSM metadata-bearing +# coverage declarations directly. This avoids JSON/schema coupling while still +# covering the dump-side formatting for fv/ff/ft/fg. +test.lint(v_flags=["--coverage-fsm", "--dump-tree"]) + +tree_files = [Path(filename) for filename in test.glob_some(test.obj_dir + "/*.tree")] +tree_texts = [filename.read_text(encoding="utf8") for filename in tree_files] + +assert any("COVEROTHERDECL" in text and " fv=t.state" in text for text in tree_texts) +assert any( + "COVEROTHERDECL" in text and " ff=ANY" in text and " ft=S0" in text and " fg=reset" in text + for text in tree_texts) +assert any("COVEROTHERDECL" in text and " ff=default" in text and " ft=S0" in text + and " fg=default" in text for text in tree_texts) + +test.passes() diff --git a/test_regress/t/t_cover_fsm_enum_bad.out b/test_regress/t/t_cover_fsm_enum_bad.out new file mode 100644 index 000000000..5dc28a897 --- /dev/null +++ b/test_regress/t/t_cover_fsm_enum_bad.out @@ -0,0 +1,6 @@ +%Warning-COVERIGN: t/t_cover_fsm_enum_bad.v:27:19: Ignoring unsupported: FSM coverage on enum state transitions that assign a constant not present in the declared enum + 27 | S0: state <= 2'd3; + | ^~ + ... 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_enum_bad.py b/test_regress/t/t_cover_fsm_enum_bad.py new file mode 100755 index 000000000..a79e25037 --- /dev/null +++ b/test_regress/t/t_cover_fsm_enum_bad.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM enum transition bad-value 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') + +# When an enum-backed FSM assigns a constant that is not one of the declared +# enum items, FSM coverage should warn and skip the unsupported edge rather +# than turning optional coverage into a hard compile failure. +test.lint(verilator_flags2=["--coverage-fsm"], fails=True) + +test.file_grep( + test.compile_log_filename, + r'%Warning-COVERIGN: t/t_cover_fsm_enum_bad.v:27:19: Ignoring unsupported: FSM coverage ' + r'on enum state transitions that assign a constant not present in the declared enum') + +test.passes() diff --git a/test_regress/t/t_cover_fsm_enum_bad.v b/test_regress/t/t_cover_fsm_enum_bad.v new file mode 100644 index 000000000..a33717471 --- /dev/null +++ b/test_regress/t/t_cover_fsm_enum_bad.v @@ -0,0 +1,34 @@ +// DESCRIPTION: Verilator: FSM enum transition rejects unknown constant values +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input logic clk, + input logic rst +); + + typedef enum logic [1:0] { + S0, S1 + } state_t; + + state_t state; + + // FSM coverage should reject a constant next-state value that is not one of + // the declared enum items. This keeps graph construction aligned with the + // enum-backed state set instead of silently dropping the transition. + always_ff @(posedge clk) begin + if (rst) begin + state <= S0; + end else begin + case (state) + /* verilator lint_off ENUMVALUE */ + S0: state <= 2'd3; + /* verilator lint_on ENUMVALUE */ + default: state <= S0; + endcase + end + end + +endmodule diff --git a/test_regress/t/t_cover_fsm_enumwide_bad.out b/test_regress/t/t_cover_fsm_enumwide_bad.out new file mode 100644 index 000000000..62da6b270 --- /dev/null +++ b/test_regress/t/t_cover_fsm_enumwide_bad.out @@ -0,0 +1,6 @@ +%Warning-COVERIGN: t/t_cover_fsm_enumwide_bad.v:25:7: Ignoring unsupported: FSM coverage on enum-typed state variables wider than 32 bits + 25 | case (state) + | ^~~~ + ... 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_enumwide_bad.py b/test_regress/t/t_cover_fsm_enumwide_bad.py new file mode 100755 index 000000000..373e2baec --- /dev/null +++ b/test_regress/t/t_cover_fsm_enumwide_bad.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM enum width limit 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') + +# FSM coverage currently stores recovered enum state values in the detector's +# 32-bit internal representation, so wider enum-backed FSMs are rejected. +test.lint(verilator_flags2=["--coverage-fsm"], fails=True) + +test.file_grep( + test.compile_log_filename, + r'%Warning-COVERIGN: t/t_cover_fsm_enumwide_bad.v:25:7: Ignoring unsupported: ' + r'FSM coverage on enum-typed state variables wider than 32 bits') + +test.passes() diff --git a/test_regress/t/t_cover_fsm_enumwide_bad.v b/test_regress/t/t_cover_fsm_enumwide_bad.v new file mode 100644 index 000000000..0687f5369 --- /dev/null +++ b/test_regress/t/t_cover_fsm_enumwide_bad.v @@ -0,0 +1,32 @@ +// DESCRIPTION: Verilator: FSM enum width limit rejects >32-bit enums +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input logic clk, + input logic rst +); + + typedef enum logic [32:0] { + S0 = 33'd0, + S1 = 33'd1 + } state_t; + + state_t state; + + // FSM coverage currently supports enum-backed state variables only up to + // 32 bits wide, so this wider enum should be rejected at FSM detection time. + always_ff @(posedge clk) begin + if (rst) begin + state <= S0; + end else begin + case (state) + S0: state <= S1; + default: state <= S0; + endcase + end + end + +endmodule diff --git a/test_regress/t/t_cover_fsm_flag_off.py b/test_regress/t/t_cover_fsm_flag_off.py new file mode 100755 index 000000000..e3fdca440 --- /dev/null +++ b/test_regress/t/t_cover_fsm_flag_off.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage stays off without --coverage-fsm +# +# 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=['--cc --coverage-line']) + +test.execute() + +test.file_grep_not(test.obj_dir + "/coverage.dat", r"fsm_state") +test.file_grep_not(test.obj_dir + "/coverage.dat", r"fsm_arc") + +test.passes() diff --git a/test_regress/t/t_cover_fsm_flag_off.v b/test_regress/t/t_cover_fsm_flag_off.v new file mode 100644 index 000000000..4005f8888 --- /dev/null +++ b/test_regress/t/t_cover_fsm_flag_off.v @@ -0,0 +1,53 @@ +// DESCRIPTION: Verilator: FSM coverage stays off without --coverage-fsm +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk +); + + 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*/; + + 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: state <= S_DONE; + S_DONE: state <= S_DONE; + default: state <= S_ERR; + endcase + end + end + +endmodule diff --git a/test_regress/t/t_cover_fsm_forced.out b/test_regress/t/t_cover_fsm_forced.out new file mode 100644 index 000000000..d111c0069 --- /dev/null +++ b/test_regress/t/t_cover_fsm_forced.out @@ -0,0 +1,51 @@ +// // verilator_coverage annotation + // DESCRIPTION: Verilator: FSM coverage forced non-enum test + // + // This file ONLY is placed under the Creative Commons Public Domain. + // SPDX-FileCopyrightText: 2026 Wilson Snyder + // SPDX-License-Identifier: CC0-1.0 + + module t ( + input clk + ); + + integer cyc; + logic rst; + logic [1:0] state /*verilator fsm_state*/; + + initial begin + cyc = 0; + rst = 1'b1; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) rst <= 1'b0; + if (cyc == 6) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + always_ff @(posedge clk) begin + if (rst) begin + state <= 2'd0; + end else begin +%000002 case (state) + // [FSM coverage] +%000001 // [fsm_arc t.state::ANY->S0[reset]] [reset arc, excluded from %] +%000002 // [fsm_arc t.state::S0->S1] +%000002 // [fsm_arc t.state::S1->S2] +%000001 // [fsm_state t.state::S0] +%000002 // [fsm_state t.state::S1] +%000002 // [fsm_state t.state::S2] +%000000 // [fsm_state t.state::S3] *** UNCOVERED *** + 2'd0: state <= 2'd1; + 2'd1: state <= 2'd2; + default: state <= 2'd0; + endcase + end + end + + endmodule + diff --git a/test_regress/t/t_cover_fsm_forced.py b/test_regress/t/t_cover_fsm_forced.py new file mode 100755 index 000000000..f368d9cf9 --- /dev/null +++ b/test_regress/t/t_cover_fsm_forced.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage forced non-enum 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=['--cc --coverage-fsm']) + +test.execute() + +# Use annotated-source golden output so hit-count regressions are visible in the +# expected file instead of being hidden behind coarse coverage.dat greps. +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_forced.v b/test_regress/t/t_cover_fsm_forced.v new file mode 100644 index 000000000..c10812859 --- /dev/null +++ b/test_regress/t/t_cover_fsm_forced.v @@ -0,0 +1,41 @@ +// DESCRIPTION: Verilator: FSM coverage forced non-enum test +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk +); + + integer cyc; + logic rst; + logic [1:0] state /*verilator fsm_state*/; + + initial begin + cyc = 0; + rst = 1'b1; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) rst <= 1'b0; + if (cyc == 6) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + always_ff @(posedge clk) begin + if (rst) begin + state <= 2'd0; + end else begin + case (state) + 2'd0: state <= 2'd1; + 2'd1: state <= 2'd2; + default: state <= 2'd0; + endcase + end + end + +endmodule diff --git a/test_regress/t/t_cover_fsm_graphdump.py b/test_regress/t/t_cover_fsm_graphdump.py new file mode 100755 index 000000000..3290be13f --- /dev/null +++ b/test_regress/t/t_cover_fsm_graphdump.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage graph dump 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('vltmt') +test.top_filename = "t/t_cover_fsm_styles.v" + +test.compile(v_flags2=["--coverage-fsm", "--dumpi-graph", "6"], threads=2) + +dot_files = test.glob_some(test.obj_dir + "/*fsm_*.dot") +for dot_filename in dot_files: + test.file_grep(dot_filename, r'digraph v3graph') + +test.file_grep_any(dot_files, r'ANY') +test.file_grep_any(dot_files, r'default') + +test.passes() diff --git a/test_regress/t/t_cover_fsm_negative_extract.out b/test_regress/t/t_cover_fsm_negative_extract.out new file mode 100644 index 000000000..88ac5402d --- /dev/null +++ b/test_regress/t/t_cover_fsm_negative_extract.out @@ -0,0 +1,64 @@ +// // verilator_coverage annotation + // DESCRIPTION: Verilator: FSM coverage negative extraction test + // + // This file ONLY is placed under the Creative Commons Public Domain. + // SPDX-FileCopyrightText: 2026 Wilson Snyder + // SPDX-License-Identifier: CC0-1.0 + + module t ( + input logic clk + ); + + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + int cyc; + logic side; + state_t state /*verilator fsm_reset_arc*/; + + initial begin + cyc = 0; + side = 1'b0; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) side <= 1'b1; + if (cyc == 5) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + // The S0 arm is the supported baseline. The S1 and default arms are + // deliberately unsupported extractor shapes: one has two meaningful + // statements, the other writes a different lhs first. Coverage should ignore + // those arcs rather than guessing. + always_ff @(posedge clk) begin + if (cyc == 0) begin + state <= S0; + end else begin +%000002 case (state) + // [FSM coverage] +%000002 // [fsm_arc t.state::S0->S1] +%000001 // [fsm_state t.state::S0] +%000002 // [fsm_state t.state::S1] +%000002 // [fsm_state t.state::S2] + S0: state <= S1; + S1: begin + side <= ~side; + state <= S2; + end + default: begin + side <= 1'b0; + state <= S0; + end + endcase + end + end + + endmodule + diff --git a/test_regress/t/t_cover_fsm_negative_extract.py b/test_regress/t/t_cover_fsm_negative_extract.py new file mode 100755 index 000000000..53a99158c --- /dev/null +++ b/test_regress/t/t_cover_fsm_negative_extract.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage negative extraction 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') + +# This test is intentionally "half supported": one case item is a simple +# direct state assignment, while the others use shapes the extractor should +# ignore (multiple meaningful statements or assignment to a non-state lhs). +# That lets us hit the conservative negative branches in directStateAssign() +# and singleMeaningfulStmt() without changing user-visible behavior. +test.compile(verilator_flags2=['--cc --coverage-fsm']) + +test.execute() + +# Use annotated-source output so the golden locks down which candidate arcs +# survive extraction and which unsupported shapes are intentionally skipped. +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_negative_extract.v b/test_regress/t/t_cover_fsm_negative_extract.v new file mode 100644 index 000000000..1c60febde --- /dev/null +++ b/test_regress/t/t_cover_fsm_negative_extract.v @@ -0,0 +1,57 @@ +// DESCRIPTION: Verilator: FSM coverage negative extraction test +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input logic clk +); + + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + int cyc; + logic side; + state_t state /*verilator fsm_reset_arc*/; + + initial begin + cyc = 0; + side = 1'b0; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) side <= 1'b1; + if (cyc == 5) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + // The S0 arm is the supported baseline. The S1 and default arms are + // deliberately unsupported extractor shapes: one has two meaningful + // statements, the other writes a different lhs first. Coverage should ignore + // those arcs rather than guessing. + always_ff @(posedge clk) begin + if (cyc == 0) begin + state <= S0; + end else begin + case (state) + S0: state <= S1; + S1: begin + side <= ~side; + state <= S2; + end + default: begin + side <= 1'b0; + state <= S0; + end + endcase + end + end + +endmodule diff --git a/test_regress/t/t_cover_fsm_noreset.out b/test_regress/t/t_cover_fsm_noreset.out new file mode 100644 index 000000000..f2c28be61 --- /dev/null +++ b/test_regress/t/t_cover_fsm_noreset.out @@ -0,0 +1,47 @@ +// // verilator_coverage annotation + // DESCRIPTION: Verilator: FSM coverage no-reset lowering test + // + // This file ONLY is placed under the Creative Commons Public Domain. + // SPDX-FileCopyrightText: 2026 Wilson Snyder + // SPDX-License-Identifier: CC0-1.0 + + module t ( + input logic clk + ); + + typedef enum logic [0:0] { + S0 = 1'b0, + S1 = 1'b1 + } state_t; + + int cyc; + state_t state; + + initial begin + cyc = 0; + state = S0; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 4) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + // No reset branch on purpose: this keeps the test focused on the branch in + // lowering that skips reset reconstruction entirely. + always_ff @(posedge clk) begin +%000003 case (state) + // [FSM coverage] +%000003 // [fsm_arc t.state::S0->S1] +%000002 // [fsm_state t.state::S0] +%000003 // [fsm_state t.state::S1] + S0: state <= S1; + default: state <= S0; + endcase + end + + endmodule + diff --git a/test_regress/t/t_cover_fsm_noreset.py b/test_regress/t/t_cover_fsm_noreset.py new file mode 100755 index 000000000..2f70ddb14 --- /dev/null +++ b/test_regress/t/t_cover_fsm_noreset.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage no-reset lowering 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') + +# This test deliberately uses a clocked FSM with no outer reset branch. It +# keeps coverage extraction in the supported subset, but forces lowering down +# the "hasResetCond() == false" path so we validate the no-reset machinery +# rather than only reset-wrapped FSMs. +test.compile(verilator_flags2=['--cc --coverage-fsm']) + +test.execute() + +# Use annotated-source output so the expected file captures the no-reset shape +# directly, including the absence of reset pseudo-arcs. +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_noreset.v b/test_regress/t/t_cover_fsm_noreset.v new file mode 100644 index 000000000..02d8fc1b7 --- /dev/null +++ b/test_regress/t/t_cover_fsm_noreset.v @@ -0,0 +1,41 @@ +// DESCRIPTION: Verilator: FSM coverage no-reset lowering test +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input logic clk +); + + typedef enum logic [0:0] { + S0 = 1'b0, + S1 = 1'b1 + } state_t; + + int cyc; + state_t state; + + initial begin + cyc = 0; + state = S0; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 4) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + // No reset branch on purpose: this keeps the test focused on the branch in + // lowering that skips reset reconstruction entirely. + always_ff @(posedge clk) begin + case (state) + S0: state <= S1; + default: state <= S0; + endcase + end + +endmodule diff --git a/test_regress/t/t_cover_fsm_reset.out b/test_regress/t/t_cover_fsm_reset.out new file mode 100644 index 000000000..bda581f6a --- /dev/null +++ b/test_regress/t/t_cover_fsm_reset.out @@ -0,0 +1,63 @@ +// // verilator_coverage annotation + // DESCRIPTION: Verilator: FSM coverage reset policy test + // + // This file ONLY is placed under the Creative Commons Public Domain. + // SPDX-FileCopyrightText: 2026 Wilson Snyder + // SPDX-License-Identifier: CC0-1.0 + + module t ( +%000006 input clk + ); + + typedef enum logic [0:0] { + S0 = 1'b0, + S1 = 1'b1 + } state_t; + +%000001 logic rst; + integer cyc; +%000001 state_t state_incl /*verilator fsm_reset_arc*/; +%000001 state_t state_excl; + +%000001 initial begin +%000001 rst = 1'b1; +%000001 cyc = 0; + end + +%000006 always @(posedge clk) begin +%000006 cyc <= cyc + 1; +%000005 if (cyc == 1) rst <= 1'b0; +%000005 if (cyc == 5) begin +%000001 $write("*-* All Finished *-*\n"); +%000001 $finish; + end + end + +%000006 always_ff @(posedge clk) begin +%000004 if (rst) state_incl <= S0; +%000004 else case (state_incl) + // [FSM coverage] +%000001 // [fsm_arc t.state_incl::ANY->S0[reset_include]] [reset arc, excluded from %] +%000001 // [fsm_arc t.state_incl::S0->S1] +%000000 // [fsm_state t.state_incl::S0] *** UNCOVERED *** +%000001 // [fsm_state t.state_incl::S1] +%000001 S0: state_incl <= S1; +%000003 default: state_incl <= S1; + endcase + end + +%000006 always_ff @(posedge clk) begin +%000004 if (rst) state_excl <= S0; +%000004 else case (state_excl) + // [FSM coverage] +%000001 // [fsm_arc t.state_excl::ANY->S0[reset]] [reset arc, excluded from %] +%000001 // [fsm_arc t.state_excl::S0->S1] +%000000 // [fsm_state t.state_excl::S0] *** UNCOVERED *** +%000001 // [fsm_state t.state_excl::S1] +%000001 S0: state_excl <= S1; +%000003 default: state_excl <= S1; + endcase + end + + endmodule + diff --git a/test_regress/t/t_cover_fsm_reset.py b/test_regress/t/t_cover_fsm_reset.py new file mode 100755 index 000000000..c250cbe45 --- /dev/null +++ b/test_regress/t/t_cover_fsm_reset.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage reset policy 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=['--cc --coverage']) + +test.execute() + +test.run(cmd=[ + os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage", + "--include-reset-arcs", + test.obj_dir + "/coverage.dat", +], + verilator_run=True) + +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_reset.v b/test_regress/t/t_cover_fsm_reset.v new file mode 100644 index 000000000..1855ef072 --- /dev/null +++ b/test_regress/t/t_cover_fsm_reset.v @@ -0,0 +1,51 @@ +// DESCRIPTION: Verilator: FSM coverage reset policy test +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk +); + + typedef enum logic [0:0] { + S0 = 1'b0, + S1 = 1'b1 + } state_t; + + logic rst; + integer cyc; + state_t state_incl /*verilator fsm_reset_arc*/; + state_t state_excl; + + initial begin + rst = 1'b1; + cyc = 0; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) rst <= 1'b0; + if (cyc == 5) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + always_ff @(posedge clk) begin + if (rst) state_incl <= S0; + else case (state_incl) + S0: state_incl <= S1; + default: state_incl <= S1; + endcase + end + + always_ff @(posedge clk) begin + if (rst) state_excl <= S0; + else case (state_excl) + S0: state_excl <= S1; + default: state_excl <= S1; + endcase + end + +endmodule diff --git a/test_regress/t/t_cover_fsm_reset_multi.out b/test_regress/t/t_cover_fsm_reset_multi.out new file mode 100644 index 000000000..bc22dfd47 --- /dev/null +++ b/test_regress/t/t_cover_fsm_reset_multi.out @@ -0,0 +1,62 @@ +// // verilator_coverage annotation + // DESCRIPTION: Verilator: FSM coverage reset pseudo-vertex reuse test + // + // This file ONLY is placed under the Creative Commons Public Domain. + // SPDX-FileCopyrightText: 2026 Wilson Snyder + // SPDX-License-Identifier: CC0-1.0 + + module t ( + input clk + ); + + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + logic rst; + integer cyc; + state_t state /*verilator fsm_reset_arc*/; + + initial begin + rst = 1'b1; + cyc = 0; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) rst <= 1'b0; + if (cyc == 5) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + // This reset block is intentionally non-idiomatic. The detector only collects + // reset arcs from top-level direct assignments in the reset branch, so two + // sequential assignments are the narrowest way to force multiple reset arcs + // into one FSM graph and exercise reuse of the synthetic ANY reset source. + always_ff @(posedge clk) begin + if (rst) begin + state <= S0; + state <= S1; + end else begin +%000001 case (state) + // [FSM coverage] +%000000 // [fsm_arc t.state::ANY->S0[reset_include]] [reset arc, excluded from %] +%000001 // [fsm_arc t.state::ANY->S1[reset_include]] [reset arc, excluded from %] +%000000 // [fsm_arc t.state::S0->S2] +%000001 // [fsm_arc t.state::S1->S2] +%000000 // [fsm_state t.state::S0] *** UNCOVERED *** +%000001 // [fsm_state t.state::S1] +%000001 // [fsm_state t.state::S2] + S0: state <= S2; + S1: state <= S2; + default: state <= S2; + endcase + end + end + + endmodule + diff --git a/test_regress/t/t_cover_fsm_reset_multi.py b/test_regress/t/t_cover_fsm_reset_multi.py new file mode 100755 index 000000000..42ab4d0f3 --- /dev/null +++ b/test_regress/t/t_cover_fsm_reset_multi.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage reset pseudo-vertex reuse 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') + +# This regression is aimed at the graph helper, not at recommending RTL style. +# We deliberately create two reset arcs in a single FSM so graph construction +# has to reuse the synthetic ANY reset pseudo-vertex rather than allocating it +# only once for a one-arc machine. +test.compile(verilator_flags2=['--cc --coverage-fsm']) + +test.execute() + +# Use annotated-source output so the golden proves both reset arcs remain +# visible and share the same synthetic ANY reset source. +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_reset_multi.v b/test_regress/t/t_cover_fsm_reset_multi.v new file mode 100644 index 000000000..16b6de31f --- /dev/null +++ b/test_regress/t/t_cover_fsm_reset_multi.v @@ -0,0 +1,52 @@ +// DESCRIPTION: Verilator: FSM coverage reset pseudo-vertex reuse test +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk +); + + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + logic rst; + integer cyc; + state_t state /*verilator fsm_reset_arc*/; + + initial begin + rst = 1'b1; + cyc = 0; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) rst <= 1'b0; + if (cyc == 5) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + // This reset block is intentionally non-idiomatic. The detector only collects + // reset arcs from top-level direct assignments in the reset branch, so two + // sequential assignments are the narrowest way to force multiple reset arcs + // into one FSM graph and exercise reuse of the synthetic ANY reset source. + always_ff @(posedge clk) begin + if (rst) begin + state <= S0; + state <= S1; + end else begin + case (state) + S0: state <= S2; + S1: state <= S2; + default: state <= S2; + endcase + end + end + +endmodule diff --git a/test_regress/t/t_cover_fsm_styles.out b/test_regress/t/t_cover_fsm_styles.out new file mode 100644 index 000000000..33eba2621 --- /dev/null +++ b/test_regress/t/t_cover_fsm_styles.out @@ -0,0 +1,64 @@ +// // verilator_coverage annotation + // DESCRIPTION: Verilator: FSM coverage style coverage test + // + // This file ONLY is placed under the Creative Commons Public Domain. + // SPDX-FileCopyrightText: 2026 Wilson Snyder + // SPDX-License-Identifier: CC0-1.0 + + module t ( + input clk + ); + + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2, + S3 = 2'd3 + } state_t; + + integer cyc; + logic rst; + logic start; + state_t state /*verilator fsm_arc_include_cond*/; + + 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 == 6) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + always_ff @(posedge clk) begin + if (rst) begin + state <= S0; + end else begin +%000003 case (state) + // [FSM coverage] +%000001 // [fsm_arc t.state::ANY->S0[reset]] [reset arc, excluded from %] +%000000 // [fsm_arc t.state::S0->S1] +%000003 // [fsm_arc t.state::S0->S2] +%000000 // [fsm_arc t.state::S1->S3] +%000000 // [SYNTHETIC DEFAULT ARC: t.state::default->S0] +%000002 // [fsm_state t.state::S0] +%000000 // [fsm_state t.state::S1] *** UNCOVERED *** +%000003 // [fsm_state t.state::S2] +%000000 // [fsm_state t.state::S3] *** UNCOVERED *** + S0: if (start) state <= S1; else state <= S2; + S1: state <= S3; + default: state <= S0; + endcase + end + end + + endmodule + diff --git a/test_regress/t/t_cover_fsm_styles.py b/test_regress/t/t_cover_fsm_styles.py new file mode 100755 index 000000000..fc9fde9f0 --- /dev/null +++ b/test_regress/t/t_cover_fsm_styles.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage style coverage 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=['--cc --coverage-fsm']) + +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) # yapf:disable + +test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_cover_fsm_styles.v b/test_regress/t/t_cover_fsm_styles.v new file mode 100644 index 000000000..e8300a9a9 --- /dev/null +++ b/test_regress/t/t_cover_fsm_styles.v @@ -0,0 +1,52 @@ +// DESCRIPTION: Verilator: FSM coverage style coverage test +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk +); + + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2, + S3 = 2'd3 + } state_t; + + integer cyc; + logic rst; + logic start; + state_t state /*verilator fsm_arc_include_cond*/; + + 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 == 6) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + always_ff @(posedge clk) begin + if (rst) begin + state <= S0; + end else begin + case (state) + S0: if (start) state <= S1; else state <= S2; + S1: state <= S3; + default: state <= S0; + endcase + end + end + +endmodule diff --git a/test_regress/t/t_cover_otherdecl_dump.py b/test_regress/t/t_cover_otherdecl_dump.py new file mode 100755 index 000000000..a1524c584 --- /dev/null +++ b/test_regress/t/t_cover_otherdecl_dump.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: generic coverage declaration dump 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 + +from pathlib import Path + +import vltest_bootstrap + +test.scenarios('vlt') +test.top_filename = "t/t_cover_fsm_styles.v" + +# Dump generic COVEROTHERDECL nodes so AstCoverOtherDecl::dump() also sees +# coverage declarations with no FSM metadata, exercising the empty-field side +# of the fv/ff/ft/fg formatting. +test.lint(v_flags=["--coverage-line", "--dump-tree"]) + +tree_files = [Path(filename) for filename in test.glob_some(test.obj_dir + "/*.tree")] +tree_texts = [filename.read_text(encoding="utf8") for filename in tree_files] + +generic_lines = [] +for text in tree_texts: + generic_lines.extend(line for line in text.splitlines() + if "COVEROTHERDECL" in line and " page=v_line/" in line) + +assert generic_lines +assert any(" fv=" not in line and " ff=" not in line and " ft=" not in line and " fg=" not in line + for line in generic_lines) + +test.passes() diff --git a/test_regress/t/t_dfg_break_cycles.v b/test_regress/t/t_dfg_break_cycles.v index 906bbda77..5de4c628b 100644 --- a/test_regress/t/t_dfg_break_cycles.v +++ b/test_regress/t/t_dfg_break_cycles.v @@ -403,9 +403,9 @@ module t ( wire logic [63:0] volatile_packed_out_of_cycle /* verilator forceable */ = rand_a; assign VOLATILE_PACKED_OUT_OF_CYCLE = volatile_packed_out_of_cycle ^ 64'(VOLATILE_PACKED_OUT_OF_CYCLE[63:1]); + wire logic [2:0] volatile_packed_in_cycle /* verilator forceable */; // verilator lint_off UNOPTFLAT `signal(VOLATILE_PACKED_IN_CYCLE, 3); - wire logic [2:0] volatile_packed_in_cycle /* verilator forceable */; assign volatile_packed_in_cycle = rand_a[2:0] ^ 3'(volatile_packed_in_cycle[2:1]); assign VOLATILE_PACKED_IN_CYCLE = volatile_packed_in_cycle; // verilator lint_on diff --git a/test_regress/t/t_dfg_dump_patterns.out b/test_regress/t/t_dfg_dump_patterns.out new file mode 100644 index 000000000..0d686ee07 --- /dev/null +++ b/test_regress/t/t_dfg_dump_patterns.out @@ -0,0 +1,123 @@ +DFG patterns with depth 1 + 8 (CONCAT _:1 _:a):b + 5 (CONST #A):a + 5 (REDXOR _:a*):1 + 5 (VARPACKED _:a*):a + 3 (NOT (VARPACKED):a):a* + 3 (REDXOR _:a):1 + 3 (VARPACKED):a + 2 (AND _:a* _:a*):a + 1 (AND _:a* _:a*):a* + 1 (CONCAT _:1 _:1*):a + 1 (CONCAT _:1* _:a):b + 1 (NOT _:a*):a + 1 (REDXOR _:a):1* + 1 (REPLICATE _:1 (CONST #A):a):b* + 1 (REPLICATE _:a (CONST #A):a):b* + 1 (REPLICATE _:a* (CONST #A):a):b + 1 (REPLICATE _:a* (CONST #A):b):b* + 1 (REPLICATE _:a* (CONST #A):b):c* + 1 (SEL@0 _:a*):1 + 1 (SEL@0 _:a*):b + 1 (SEL@A _:a*):1 + 1 (VARPACKED _:a):a + +DFG patterns with depth 2 + 3 (CONCAT (REDXOR _:a*):1 (CONCAT _:1 _:b):c):d + 2 (AND (NOT (VARPACKED):a):a* (NOT (VARPACKED):a):a*):a + 2 (CONCAT (REDXOR _:a):1 (CONCAT _:1 _:b):c):d + 2 (REDXOR (AND _:a* _:a*):a):1 + 1 (AND (NOT (VARPACKED):a):a* (NOT (VARPACKED):a):a*):a* + 1 (CONCAT (REDXOR _:a):1 (REDXOR _:b):1*):c + 1 (CONCAT (REDXOR _:a):1* (CONCAT _:1 _:b):c):d + 1 (CONCAT (REDXOR _:a*):1 (CONCAT _:1 _:1*):b):c + 1 (CONCAT (REDXOR _:a*):1 (CONCAT _:1* _:b):c):d + 1 (CONCAT (SEL@0 _:a*):1 (CONCAT _:1 _:b):c):d + 1 (NOT (REPLICATE _:a* (CONST #A):b):b*):b + 1 (REDXOR (AND _:a* _:a*):a*):1 + 1 (REDXOR (REPLICATE _:1 (CONST #A):a):b*):1 + 1 (REDXOR (REPLICATE _:a (CONST #A):a):b*):1 + 1 (REDXOR (REPLICATE _:a* (CONST #A):a):b):1* + 1 (REDXOR (REPLICATE _:a* (CONST #A):b):b*):1 + 1 (REDXOR (REPLICATE _:a* (CONST #A):b):c*):1 + 1 (REDXOR (SEL@0 _:a*):b):1 + 1 (REPLICATE (NOT _:a*):a (CONST #A):a):b* + 1 (REPLICATE (REPLICATE _:1 (CONST #A):a):b* (CONST #B):a):c* + 1 (REPLICATE (REPLICATE _:a* (CONST #A):b):b* (CONST #A):b):c + 1 (REPLICATE (REPLICATE _:a* (CONST #A):b):c* (CONST #A):b):b* + 1 (REPLICATE (SEL@A _:a*):1 (CONST #A):b):c* + 1 (SEL@0 (AND _:a* _:a*):a*):1 + 1 (SEL@0 (REPLICATE _:a (CONST #A):a):b*):c + 1 (SEL@A (AND _:a* _:a*):a*):1 + 1 (VARPACKED (AND _:a* _:a*):a*):a + 1 (VARPACKED (CONCAT _:1 _:a):b):b + 1 (VARPACKED (REPLICATE _:1 (CONST #A):a):b*):b + 1 (VARPACKED (REPLICATE _:a (CONST #A):a):b*):b + 1 (VARPACKED (REPLICATE _:a* (CONST #A):b):b*):b + 1 (VARPACKED (REPLICATE _:a* (CONST #A):b):c*):c + +DFG patterns with depth 3 + 2 (REDXOR (AND (NOT (VARPACKED):a):a* (NOT (VARPACKED):a):a*):a):1 + 1 (CONCAT (REDXOR (AND _:a* _:a*):a):1 (CONCAT (REDXOR _:a):1 (CONCAT _:1 _:b):c):d):e + 1 (CONCAT (REDXOR (AND _:a* _:a*):a):1 (CONCAT (SEL@0 _:a*):1 (CONCAT _:1 _:b):c):d):e + 1 (CONCAT (REDXOR (AND _:a* _:a*):a*):1 (CONCAT (REDXOR _:a):1 (CONCAT _:1 _:b):c):d):e + 1 (CONCAT (REDXOR (REPLICATE _:1 (CONST #A):a):b*):1 (CONCAT (REDXOR _:c*):1 (CONCAT _:1 _:d):e):f):g + 1 (CONCAT (REDXOR (REPLICATE _:a (CONST #A):a):b*):1 (CONCAT (REDXOR _:c):1 (REDXOR _:b):1*):d):e + 1 (CONCAT (REDXOR (REPLICATE _:a* (CONST #A):b):b*):1 (CONCAT (REDXOR _:c):1* (CONCAT _:1 _:d):e):f):g + 1 (CONCAT (REDXOR (REPLICATE _:a* (CONST #A):b):c*):1 (CONCAT (REDXOR _:b*):1 (CONCAT _:1* _:d):e):f):g + 1 (CONCAT (REDXOR (SEL@0 _:a*):b):1 (REDXOR (REPLICATE _:c* (CONST #A):c):a):1*):d + 1 (CONCAT (SEL@0 (AND _:a* _:a*):a*):1 (CONCAT (REDXOR _:b*):1 (CONCAT _:1 _:c):d):e):b + 1 (CONCAT A:1* (CONCAT (REDXOR _:a*):1 (CONCAT _:1 A:1*):b):c):d | A is (REDXOR (REPLICATE _:e* (CONST #A):e):a):1* + 1 (NOT (REPLICATE (REPLICATE _:a* (CONST #A):b):c* (CONST #A):b):b*):b + 1 (REDXOR (AND (NOT (VARPACKED):a):a* (NOT (VARPACKED):a):a*):a*):1 + 1 (REDXOR (REPLICATE (NOT _:a*):a (CONST #A):a):b*):1 + 1 (REDXOR (REPLICATE (REPLICATE _:1 (CONST #A):a):b* (CONST #B):a):c*):1 + 1 (REDXOR (REPLICATE (REPLICATE _:a* (CONST #A):b):b* (CONST #A):b):c):1* + 1 (REDXOR (REPLICATE (REPLICATE _:a* (CONST #A):b):c* (CONST #A):b):b*):1 + 1 (REDXOR (REPLICATE (SEL@A _:a*):1 (CONST #A):b):c*):1 + 1 (REDXOR (SEL@0 (REPLICATE _:a (CONST #A):a):b*):c):1 + 1 (REPLICATE (NOT (REPLICATE _:a* (CONST #A):b):b*):b (CONST #A):b):c* + 1 (REPLICATE (REPLICATE (REPLICATE _:1 (CONST #A):a):b* (CONST #B):a):c* (CONST #B):a):a* + 1 (REPLICATE (REPLICATE (REPLICATE _:a* (CONST #A):b):c* (CONST #A):b):b* (CONST #A):b):d + 1 (REPLICATE (REPLICATE (SEL@A _:a*):1 (CONST #A):b):c* (CONST #B):b):d* + 1 (REPLICATE (SEL@A (AND _:a* _:a*):a*):1 (CONST #A):b):c* + 1 (SEL@0 (AND (NOT (VARPACKED):a):a* (NOT (VARPACKED):a):a*):a*):1 + 1 (SEL@0 (REPLICATE (NOT _:a*):a (CONST #A):a):b*):c + 1 (SEL@A (AND (NOT (VARPACKED):a):a* (NOT (VARPACKED):a):a*):a*):1 + 1 (VARPACKED (AND (NOT (VARPACKED):a):a* (NOT (VARPACKED):a):a*):a*):a + 1 (VARPACKED (CONCAT (REDXOR _:a*):1 (CONCAT _:1 _:b):c):d):d + 1 (VARPACKED (REPLICATE (NOT _:a*):a (CONST #A):a):b*):b + 1 (VARPACKED (REPLICATE (REPLICATE _:1 (CONST #A):a):b* (CONST #B):a):c*):c + 1 (VARPACKED (REPLICATE (REPLICATE _:a* (CONST #A):b):c* (CONST #A):b):b*):b + 1 (VARPACKED (REPLICATE (SEL@A _:a*):1 (CONST #A):b):c*):c + +DFG patterns with depth 4 + 1 (CONCAT (REDXOR (AND (NOT (VARPACKED):a):a* A:a*):a):1 (CONCAT (REDXOR (AND A:a* _:a*):a):1 (CONCAT (SEL@0 _:a*):1 (CONCAT _:1 _:b):c):d):e):f | A is (NOT (VARPACKED):a):a* + 1 (CONCAT (REDXOR (AND (NOT (VARPACKED):a):a* A:a*):a):1 (CONCAT (SEL@0 (AND A:a* _:a*):a*):1 (CONCAT (REDXOR _:b*):1 (CONCAT _:1 _:c):d):e):b):f | A is (NOT (VARPACKED):a):a* + 1 (CONCAT (REDXOR (AND (NOT (VARPACKED):a):a* A:a*):a*):1 (CONCAT (REDXOR (AND A:a* _:a*):a):1 (CONCAT (REDXOR _:a):1 (CONCAT _:1 _:b):c):d):e):f | A is (NOT (VARPACKED):a):a* + 1 (CONCAT (REDXOR (SEL@0 (REPLICATE _:a (CONST #A):a):b*):c):1 (REDXOR (REPLICATE (REPLICATE _:d* (CONST #A):a):a* (CONST #A):a):b):1*):e + 1 (CONCAT (REDXOR A:a*):1 (CONCAT (REDXOR (REPLICATE A:a* (CONST #A):b):b*):1 (CONCAT (REDXOR _:c):1* (CONCAT _:1 _:d):e):f):g):h | A is (REPLICATE (REPLICATE _:1 (CONST #B):b):i* (CONST #A):b):a* + 1 (CONCAT (REDXOR A:a*):1 (CONCAT (REDXOR (REPLICATE A:a* (CONST #A):b):c*):1 (CONCAT (REDXOR _:b*):1 (CONCAT _:1* _:d):e):f):g):h | A is (REPLICATE (SEL@A _:e*):1 (CONST #B):b):a* + 1 (CONCAT (REDXOR A:a*):1 (CONCAT (REDXOR (SEL@0 A:a*):b):1 (REDXOR (REPLICATE B:c* (CONST #A):c):a):1*):d):e | A is (REPLICATE (NOT B:c*):c (CONST #A):c):a* | B is _:c* + 1 (CONCAT (REDXOR A:a*):1 (CONCAT B:1* (CONCAT (REDXOR _:b*):1 (CONCAT _:1 B:1*):c):d):e):f | A is (REPLICATE (REPLICATE _:g* (CONST #A):a):h* (CONST #A):a):a* | B is (REDXOR (REPLICATE A:a* (CONST #A):a):b):1* + 1 (CONCAT (SEL@0 (AND (NOT (VARPACKED):a):a* (NOT (VARPACKED):a):a*):a*):1 (CONCAT (REDXOR (REPLICATE _:1 (CONST #A):b):c*):1 (CONCAT (REDXOR _:d*):1 (CONCAT _:1 _:a):e):f):g):c + 1 (CONCAT A:1* (CONCAT (REDXOR (REPLICATE _:a (CONST #A):a):b*):1 (CONCAT (REDXOR _:c):1 A:1*):d):e):f | A is (REDXOR B:b):1* | B is (REPLICATE (REPLICATE _:g* (CONST #A):a):a* (CONST #A):a):b + 1 (NOT (REPLICATE (REPLICATE (REPLICATE _:1 (CONST #A):a):b* (CONST #B):a):c* (CONST #B):a):a*):a + 1 (REDXOR (REPLICATE (NOT (REPLICATE _:a* (CONST #A):b):b*):b (CONST #A):b):c*):1 + 1 (REDXOR (REPLICATE (REPLICATE (REPLICATE _:1 (CONST #A):a):b* (CONST #B):a):c* (CONST #B):a):a*):1 + 1 (REDXOR (REPLICATE (REPLICATE (REPLICATE _:a* (CONST #A):b):c* (CONST #A):b):b* (CONST #A):b):d):1* + 1 (REDXOR (REPLICATE (REPLICATE (SEL@A _:a*):1 (CONST #A):b):c* (CONST #B):b):d*):1 + 1 (REDXOR (REPLICATE (SEL@A (AND _:a* _:a*):a*):1 (CONST #A):b):c*):1 + 1 (REDXOR (SEL@0 (REPLICATE (NOT _:a*):a (CONST #A):a):b*):c):1 + 1 (REPLICATE (NOT (REPLICATE (REPLICATE _:a* (CONST #A):b):c* (CONST #A):b):b*):b (CONST #A):b):d* + 1 (REPLICATE (REPLICATE (REPLICATE (REPLICATE _:1 (CONST #A):a):b* (CONST #B):a):c* (CONST #B):a):a* (CONST #B):a):d + 1 (REPLICATE (REPLICATE (REPLICATE (SEL@A _:a*):1 (CONST #A):b):c* (CONST #B):b):d* (CONST #B):b):b* + 1 (REPLICATE (REPLICATE (SEL@A (AND _:a* _:a*):a*):1 (CONST #A):b):c* (CONST #B):b):d* + 1 (REPLICATE (SEL@A (AND (NOT (VARPACKED):a):a* (NOT (VARPACKED):a):a*):a*):1 (CONST #A):b):c* + 1 (SEL@0 (REPLICATE (NOT (REPLICATE _:a* (CONST #A):b):b*):b (CONST #A):b):c*):d + 1 (VARPACKED (CONCAT (REDXOR (AND _:a* _:a*):a*):1 (CONCAT (REDXOR _:a):1 (CONCAT _:1 _:b):c):d):e):e + 1 (VARPACKED (REPLICATE (NOT (REPLICATE _:a* (CONST #A):b):b*):b (CONST #A):b):c*):c + 1 (VARPACKED (REPLICATE (REPLICATE (REPLICATE _:1 (CONST #A):a):b* (CONST #B):a):c* (CONST #B):a):a*):a + 1 (VARPACKED (REPLICATE (REPLICATE (SEL@A _:a*):1 (CONST #A):b):c* (CONST #B):b):d*):d + 1 (VARPACKED (REPLICATE (SEL@A (AND _:a* _:a*):a*):1 (CONST #A):b):c*):c + diff --git a/test_regress/t/t_dfg_stats_patterns.py b/test_regress/t/t_dfg_dump_patterns.py similarity index 71% rename from test_regress/t/t_dfg_stats_patterns.py rename to test_regress/t/t_dfg_dump_patterns.py index 858c7942e..d0f557d1d 100755 --- a/test_regress/t/t_dfg_stats_patterns.py +++ b/test_regress/t/t_dfg_dump_patterns.py @@ -10,11 +10,10 @@ import vltest_bootstrap test.scenarios('vlt') -test.top_filename = "t/t_dfg_stats_patterns.v" -test.compile(verilator_flags2=["--stats --no-skip-identical"]) +test.compile(verilator_flags2=["--dump-dfg-patterns --no-skip-identical"]) -test.files_identical(test.obj_dir + "/" + test.vm_prefix + "__stats_dfg_patterns.txt", +test.files_identical(test.obj_dir + "/" + test.vm_prefix + "__dfg_patterns.txt", test.golden_filename) test.passes() diff --git a/test_regress/t/t_dfg_stats_patterns.v b/test_regress/t/t_dfg_dump_patterns.v similarity index 100% rename from test_regress/t/t_dfg_stats_patterns.v rename to test_regress/t/t_dfg_dump_patterns.v diff --git a/test_regress/t/t_dfg_peephole.v b/test_regress/t/t_dfg_peephole.v index 30e685075..0d88de5d9 100644 --- a/test_regress/t/t_dfg_peephole.v +++ b/test_regress/t/t_dfg_peephole.v @@ -250,10 +250,10 @@ module t ( `signal(REUSE_ASSOC_LHS_WITH_RHS_OF_RHS_XOR_COMMON, rand_a[23:4] ^ rand_a[39:20]); `signal(REUSE_ASSOC_LHS_WITH_RHS_OF_RHS_XOR, rand_a[23:4] ^ (~rand_b[24:5] ^ rand_a[39:20])); - `signal(REPLACE_COND_CONST_ONE_ZERO, rand_a[0] ? 8'b1 : 8'b0); - `signal(REPLACE_COND_CONST_ZERO_ONE, rand_a[0] ? 8'b0 : 8'b1); - `signal(REPLACE_COND_CONST_ONES_ZERO, rand_a[0] ? 8'hff : 8'b0); - `signal(REPLACE_COND_CONST_ZERO_ONAE, rand_a[0] ? 8'b0 : 8'hff); + `signal(REPLACE_COND_CONST_ONE_ZERO, rand_a[0] ? 80'b1 : 80'b0); + `signal(REPLACE_COND_CONST_ZERO_ONE, rand_a[0] ? 80'b0 : 80'b1); + `signal(REPLACE_COND_CONST_ONES_ZERO, rand_a[0] ? -80'b1 : 80'b0); + `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]}); @@ -325,6 +325,7 @@ 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); // Operators that should work wiht mismatched widths `signal(MISMATCHED_ShiftL,const_a << 4'd2); diff --git a/test_regress/t/t_dfg_stats_patterns.out b/test_regress/t/t_dfg_stats_patterns.out deleted file mode 100644 index 59c6272db..000000000 --- a/test_regress/t/t_dfg_stats_patterns.out +++ /dev/null @@ -1,121 +0,0 @@ -DFG patterns with depth 1 - 8 (CONCAT _:A/1 _:B/a):C/b - 5 (REDXOR _:A*/a):B/1 - 5 (VARPACKED _:A*/a):B/a - 3 (NOT _:A/a):B*/a - 3 (REDXOR _:A/a):B/1 - 2 (AND _:A*/a _:B*/a):C/a - 1 (AND _:A*/a _:B*/a):C*/a - 1 (CONCAT _:A*/1 _:B/a):C/b - 1 (CONCAT _:A/1 _:B*/1):C/a - 1 (NOT _:A*/a):B/a - 1 (REDXOR _:A/a):B*/1 - 1 (REPLICATE _:A*/a cA:B/a):C/b - 1 (REPLICATE _:A*/a cA:B/b):C*/b - 1 (REPLICATE _:A*/a cA:B/b):C*/c - 1 (REPLICATE _:A/1 cA:B/a):C*/b - 1 (REPLICATE _:A/a cA:B/a):C*/b - 1 (SEL@0 _:A*/a):B/1 - 1 (SEL@0 _:A*/a):B/b - 1 (SEL@A _:A*/a):B/1 - 1 (VARPACKED _:A/a):B/a - -DFG patterns with depth 2 - 3 (CONCAT (REDXOR _:A*/a):B/1 (CONCAT _:C/1 _:D/b):E/c):F/d - 2 (AND (NOT _:A/a):B*/a (NOT _:C/a):D*/a):E/a - 2 (CONCAT (REDXOR _:A/a):B/1 (CONCAT _:C/1 _:D/b):E/c):F/d - 2 (REDXOR (AND _:A*/a _:B*/a):C/a):D/1 - 1 (AND (NOT _:A/a):B*/a (NOT _:C/a):D*/a):E*/a - 1 (CONCAT (REDXOR _:A*/a):B/1 (CONCAT _:C*/1 _:D/b):E/c):F/d - 1 (CONCAT (REDXOR _:A*/a):B/1 (CONCAT _:C/1 _:D*/1):E/b):F/c - 1 (CONCAT (REDXOR _:A/a):B*/1 (CONCAT _:C/1 _:D/b):E/c):F/d - 1 (CONCAT (REDXOR _:A/a):B/1 (REDXOR _:C/b):D*/1):E/c - 1 (CONCAT (SEL@0 _:A*/a):B/1 (CONCAT _:C/1 _:D/b):E/c):F/d - 1 (NOT (REPLICATE _:A*/a cA:B/b):C*/b):D/b - 1 (REDXOR (AND _:A*/a _:B*/a):C*/a):D/1 - 1 (REDXOR (REPLICATE _:A*/a cA:B/a):C/b):D*/1 - 1 (REDXOR (REPLICATE _:A*/a cA:B/b):C*/b):D/1 - 1 (REDXOR (REPLICATE _:A*/a cA:B/b):C*/c):D/1 - 1 (REDXOR (REPLICATE _:A/1 cA:B/a):C*/b):D/1 - 1 (REDXOR (REPLICATE _:A/a cA:B/a):C*/b):D/1 - 1 (REDXOR (SEL@0 _:A*/a):B/b):C/1 - 1 (REPLICATE (NOT _:A*/a):B/a cA:C/a):D*/b - 1 (REPLICATE (REPLICATE _:A*/a cA:B/b):C*/b cA:D/b):E/c - 1 (REPLICATE (REPLICATE _:A*/a cA:B/b):C*/c cA:D/b):E*/b - 1 (REPLICATE (REPLICATE _:A/1 cA:B/a):C*/b cB:D/a):E*/c - 1 (REPLICATE (SEL@A _:A*/a):B/1 cA:C/b):D*/c - 1 (SEL@0 (AND _:A*/a _:B*/a):C*/a):D/1 - 1 (SEL@0 (REPLICATE _:A/a cA:B/a):C*/b):D/c - 1 (SEL@A (AND _:A*/a _:B*/a):C*/a):D/1 - 1 (VARPACKED (AND _:A*/a _:B*/a):C*/a):D/a - 1 (VARPACKED (CONCAT _:A/1 _:B/a):C/b):D/b - 1 (VARPACKED (REPLICATE _:A*/a cA:B/b):C*/b):D/b - 1 (VARPACKED (REPLICATE _:A*/a cA:B/b):C*/c):D/c - 1 (VARPACKED (REPLICATE _:A/1 cA:B/a):C*/b):D/b - 1 (VARPACKED (REPLICATE _:A/a cA:B/a):C*/b):D/b - -DFG patterns with depth 3 - 2 (REDXOR (AND (NOT _:A/a):B*/a (NOT _:C/a):D*/a):E/a):F/1 - 1 (CONCAT (REDXOR (AND _:A*/a _:B*/a):C*/a):D/1 (CONCAT (REDXOR _:E/a):F/1 (CONCAT _:G/1 _:H/b):I/c):J/d):K/e - 1 (CONCAT (REDXOR (AND _:A*/a _:B*/a):C/a):D/1 (CONCAT (REDXOR _:E/a):F/1 (CONCAT _:G/1 _:H/b):I/c):J/d):K/e - 1 (CONCAT (REDXOR (AND _:A*/a _:B*/a):C/a):D/1 (CONCAT (SEL@0 _:E*/a):F/1 (CONCAT _:G/1 _:H/b):I/c):J/d):K/e - 1 (CONCAT (REDXOR (REPLICATE _:A*/a cA:B/a):C/b):D*/1 (CONCAT (REDXOR _:E*/b):F/1 (CONCAT _:G/1 _:D*/1):H/c):I/d):J/e - 1 (CONCAT (REDXOR (REPLICATE _:A*/a cA:B/b):C*/b):D/1 (CONCAT (REDXOR _:E/c):F*/1 (CONCAT _:G/1 _:H/d):I/e):J/f):K/g - 1 (CONCAT (REDXOR (REPLICATE _:A*/a cA:B/b):C*/c):D/1 (CONCAT (REDXOR _:E*/b):F/1 (CONCAT _:G*/1 _:H/d):I/e):J/f):K/g - 1 (CONCAT (REDXOR (REPLICATE _:A/1 cA:B/a):C*/b):D/1 (CONCAT (REDXOR _:E*/c):F/1 (CONCAT _:G/1 _:H/d):I/e):J/f):K/g - 1 (CONCAT (REDXOR (REPLICATE _:A/a cA:B/a):C*/b):D/1 (CONCAT (REDXOR _:E/c):F/1 (REDXOR _:G/b):H*/1):I/d):J/e - 1 (CONCAT (REDXOR (SEL@0 _:A*/a):B/b):C/1 (REDXOR (REPLICATE _:D*/c cA:E/c):F/a):G*/1):H/d - 1 (CONCAT (SEL@0 (AND _:A*/a _:B*/a):C*/a):D/1 (CONCAT (REDXOR _:E*/b):F/1 (CONCAT _:G/1 _:H/c):I/d):J/e):K/b - 1 (NOT (REPLICATE (REPLICATE _:A*/a cA:B/b):C*/c cA:D/b):E*/b):F/b - 1 (REDXOR (AND (NOT _:A/a):B*/a (NOT _:C/a):D*/a):E*/a):F/1 - 1 (REDXOR (REPLICATE (NOT _:A*/a):B/a cA:C/a):D*/b):E/1 - 1 (REDXOR (REPLICATE (REPLICATE _:A*/a cA:B/b):C*/b cA:D/b):E/c):F*/1 - 1 (REDXOR (REPLICATE (REPLICATE _:A*/a cA:B/b):C*/c cA:D/b):E*/b):F/1 - 1 (REDXOR (REPLICATE (REPLICATE _:A/1 cA:B/a):C*/b cB:D/a):E*/c):F/1 - 1 (REDXOR (REPLICATE (SEL@A _:A*/a):B/1 cA:C/b):D*/c):E/1 - 1 (REDXOR (SEL@0 (REPLICATE _:A/a cA:B/a):C*/b):D/c):E/1 - 1 (REPLICATE (NOT (REPLICATE _:A*/a cA:B/b):C*/b):D/b cA:E/b):F*/c - 1 (REPLICATE (REPLICATE (REPLICATE _:A*/a cA:B/b):C*/c cA:D/b):E*/b cA:F/b):G/d - 1 (REPLICATE (REPLICATE (REPLICATE _:A/1 cA:B/a):C*/b cB:D/a):E*/c cB:F/a):G*/a - 1 (REPLICATE (REPLICATE (SEL@A _:A*/a):B/1 cA:C/b):D*/c cB:E/b):F*/d - 1 (REPLICATE (SEL@A (AND _:A*/a _:B*/a):C*/a):D/1 cA:E/b):F*/c - 1 (SEL@0 (AND (NOT _:A/a):B*/a (NOT _:C/a):D*/a):E*/a):F/1 - 1 (SEL@0 (REPLICATE (NOT _:A*/a):B/a cA:C/a):D*/b):E/c - 1 (SEL@A (AND (NOT _:A/a):B*/a (NOT _:C/a):D*/a):E*/a):F/1 - 1 (VARPACKED (AND (NOT _:A/a):B*/a (NOT _:C/a):D*/a):E*/a):F/a - 1 (VARPACKED (CONCAT (REDXOR _:A*/a):B/1 (CONCAT _:C/1 _:D/b):E/c):F/d):G/d - 1 (VARPACKED (REPLICATE (NOT _:A*/a):B/a cA:C/a):D*/b):E/b - 1 (VARPACKED (REPLICATE (REPLICATE _:A*/a cA:B/b):C*/c cA:D/b):E*/b):F/b - 1 (VARPACKED (REPLICATE (REPLICATE _:A/1 cA:B/a):C*/b cB:D/a):E*/c):F/c - 1 (VARPACKED (REPLICATE (SEL@A _:A*/a):B/1 cA:C/b):D*/c):E/c - -DFG patterns with depth 4 - 1 (CONCAT (REDXOR (AND (NOT _:A/a):B*/a (NOT _:C/a):D*/a):E*/a):F/1 (CONCAT (REDXOR (AND _:D*/a _:G*/a):H/a):I/1 (CONCAT (REDXOR _:J/a):K/1 (CONCAT _:L/1 _:M/b):N/c):O/d):P/e):Q/f - 1 (CONCAT (REDXOR (AND (NOT _:A/a):B*/a (NOT _:C/a):D*/a):E/a):F/1 (CONCAT (REDXOR (AND _:D*/a _:G*/a):H/a):I/1 (CONCAT (SEL@0 _:J*/a):K/1 (CONCAT _:L/1 _:M/b):N/c):O/d):P/e):Q/f - 1 (CONCAT (REDXOR (AND (NOT _:A/a):B*/a (NOT _:C/a):D*/a):E/a):F/1 (CONCAT (SEL@0 (AND _:D*/a _:G*/a):H*/a):I/1 (CONCAT (REDXOR _:J*/b):K/1 (CONCAT _:L/1 _:M/c):N/d):O/e):P/b):Q/f - 1 (CONCAT (REDXOR (REPLICATE (NOT _:A*/a):B/a cA:C/a):D*/b):E/1 (CONCAT (REDXOR (SEL@0 _:D*/b):F/c):G/1 (REDXOR (REPLICATE _:A*/a cA:H/a):I/b):J*/1):K/d):L/e - 1 (CONCAT (REDXOR (REPLICATE (REPLICATE _:A*/a cA:B/b):C*/b cA:D/b):E/c):F*/1 (CONCAT (REDXOR (REPLICATE _:G/b cA:H/b):I*/c):J/1 (CONCAT (REDXOR _:K/d):L/1 (REDXOR _:E/c):F*/1):M/e):N/f):O/g - 1 (CONCAT (REDXOR (REPLICATE (REPLICATE _:A*/a cA:B/b):C*/c cA:D/b):E*/b):F/1 (CONCAT (REDXOR (REPLICATE _:E*/b cA:G/b):H/d):I*/1 (CONCAT (REDXOR _:J*/d):K/1 (CONCAT _:L/1 _:I*/1):M/e):N/f):O/g):P/h - 1 (CONCAT (REDXOR (REPLICATE (REPLICATE _:A/1 cA:B/a):C*/b cB:D/a):E*/c):F/1 (CONCAT (REDXOR (REPLICATE _:E*/c cB:G/a):H*/a):I/1 (CONCAT (REDXOR _:J/d):K*/1 (CONCAT _:L/1 _:M/e):N/f):O/g):P/h):Q/i - 1 (CONCAT (REDXOR (REPLICATE (SEL@A _:A*/a):B/1 cA:C/b):D*/c):E/1 (CONCAT (REDXOR (REPLICATE _:D*/c cB:F/b):G*/d):H/1 (CONCAT (REDXOR _:I*/b):J/1 (CONCAT _:K*/1 _:L/e):M/a):N/f):O/g):P/h - 1 (CONCAT (REDXOR (SEL@0 (REPLICATE _:A/a cA:B/a):C*/b):D/c):E/1 (REDXOR (REPLICATE (REPLICATE _:F*/d cA:G/a):H*/a cA:I/a):J/b):K*/1):L/e - 1 (CONCAT (SEL@0 (AND (NOT _:A/a):B*/a (NOT _:C/a):D*/a):E*/a):F/1 (CONCAT (REDXOR (REPLICATE _:G/1 cA:H/b):I*/c):J/1 (CONCAT (REDXOR _:K*/d):L/1 (CONCAT _:M/1 _:N/a):O/e):P/f):Q/g):R/c - 1 (NOT (REPLICATE (REPLICATE (REPLICATE _:A/1 cA:B/a):C*/b cB:D/a):E*/c cB:F/a):G*/a):H/a - 1 (REDXOR (REPLICATE (NOT (REPLICATE _:A*/a cA:B/b):C*/b):D/b cA:E/b):F*/c):G/1 - 1 (REDXOR (REPLICATE (REPLICATE (REPLICATE _:A*/a cA:B/b):C*/c cA:D/b):E*/b cA:F/b):G/d):H*/1 - 1 (REDXOR (REPLICATE (REPLICATE (REPLICATE _:A/1 cA:B/a):C*/b cB:D/a):E*/c cB:F/a):G*/a):H/1 - 1 (REDXOR (REPLICATE (REPLICATE (SEL@A _:A*/a):B/1 cA:C/b):D*/c cB:E/b):F*/d):G/1 - 1 (REDXOR (REPLICATE (SEL@A (AND _:A*/a _:B*/a):C*/a):D/1 cA:E/b):F*/c):G/1 - 1 (REDXOR (SEL@0 (REPLICATE (NOT _:A*/a):B/a cA:C/a):D*/b):E/c):F/1 - 1 (REPLICATE (NOT (REPLICATE (REPLICATE _:A*/a cA:B/b):C*/c cA:D/b):E*/b):F/b cA:G/b):H*/d - 1 (REPLICATE (REPLICATE (REPLICATE (REPLICATE _:A/1 cA:B/a):C*/b cB:D/a):E*/c cB:F/a):G*/a cB:H/a):I/d - 1 (REPLICATE (REPLICATE (REPLICATE (SEL@A _:A*/a):B/1 cA:C/b):D*/c cB:E/b):F*/d cB:G/b):H*/b - 1 (REPLICATE (REPLICATE (SEL@A (AND _:A*/a _:B*/a):C*/a):D/1 cA:E/b):F*/c cB:G/b):H*/d - 1 (REPLICATE (SEL@A (AND (NOT _:A/a):B*/a (NOT _:C/a):D*/a):E*/a):F/1 cA:G/b):H*/c - 1 (SEL@0 (REPLICATE (NOT (REPLICATE _:A*/a cA:B/b):C*/b):D/b cA:E/b):F*/c):G/d - 1 (VARPACKED (CONCAT (REDXOR (AND _:A*/a _:B*/a):C*/a):D/1 (CONCAT (REDXOR _:E/a):F/1 (CONCAT _:G/1 _:H/b):I/c):J/d):K/e):L/e - 1 (VARPACKED (REPLICATE (NOT (REPLICATE _:A*/a cA:B/b):C*/b):D/b cA:E/b):F*/c):G/c - 1 (VARPACKED (REPLICATE (REPLICATE (REPLICATE _:A/1 cA:B/a):C*/b cB:D/a):E*/c cB:F/a):G*/a):H/a - 1 (VARPACKED (REPLICATE (REPLICATE (SEL@A _:A*/a):B/1 cA:C/b):D*/c cB:E/b):F*/d):G/d - 1 (VARPACKED (REPLICATE (SEL@A (AND _:A*/a _:B*/a):C*/a):D/1 cA:E/b):F*/c):G/c - diff --git a/test_regress/t/t_dist_copyright.py b/test_regress/t/t_dist_copyright.py index 53ee776c6..e9aab9c58 100755 --- a/test_regress/t/t_dist_copyright.py +++ b/test_regress/t/t_dist_copyright.py @@ -26,6 +26,7 @@ EXEMPT_FILES_LIST = """ REUSE.toml ci/ci-win-compile.ps1 ci/ci-win-test.ps1 + ci/docker/buildenv/wavetools.conf ci/codecov docs/CONTRIBUTORS docs/_static diff --git a/test_regress/t/t_force.v b/test_regress/t/t_force.v index 858b42ebb..8d08f5311 100644 --- a/test_regress/t/t_force.v +++ b/test_regress/t/t_force.v @@ -10,14 +10,29 @@ `define checkr(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got=%f exp=%f\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); // verilog_format: on +module t_assert; + logic clk; + logic zeroize; + logic [7:0] key_mem [0:0]; + assert property (@(posedge clk) zeroize |=> (key_mem[0] == 0)); + initial force zeroize = 0; +endmodule + module t ( input clk ); + t_assert t_assert_i(); + integer cyc = 0; + localparam logic [95:0] WIDE_INIT = 96'h12345678_9abcdef0_13579bdf; + localparam logic [94:0] WIDE_FORCE95 = {3'b101, 32'h12345678, 32'h89abcdef, 28'h2468ace}; reg [3:0] in; tri [3:0] bus = in; + logic [95:0] wide_src; + wire [95:0] wide_bus = wide_src; + logic [7:0] unpacked [0:3]; int never_driven; int never_forced; @@ -98,6 +113,25 @@ module t ( `checkh(bus, 4'b0101); end // + else if (cyc == 35) begin + force bus = 4'b1111; + end + else if (cyc == 36) begin + `checkh(bus, 4'b1111); + force bus[3:1] = 3'b000; + end + else if (cyc == 37) begin + `checkh(bus, 4'b0001); + release bus[2]; + end + else if (cyc == 38) begin + `checkh(bus, 4'b0101); + release bus; + end + else if (cyc == 39) begin + `checkh(bus, 4'b0101); + end + // else if (cyc == 40) begin r <= 1.25; end @@ -117,6 +151,66 @@ module t ( `checkr(r, 2.5); end // + else if (cyc == 50) begin + wide_src <= WIDE_INIT; + end + else if (cyc == 51) begin + `checkh(wide_bus, WIDE_INIT); + end + else if (cyc == 52) begin + force wide_bus[95:1] = WIDE_FORCE95; + end + else if (cyc == 53) begin + `checkh(wide_bus, {WIDE_FORCE95, WIDE_INIT[0]}); + end + else if (cyc == 54) begin + release wide_bus[95:1]; + end + else if (cyc == 55) begin + `checkh(wide_bus, WIDE_INIT); + end + // + else if (cyc == 60) begin + unpacked[0] <= 8'h10; + unpacked[1] <= 8'h20; + unpacked[2] <= 8'h30; + unpacked[3] <= 8'h40; + end + else if (cyc == 61) begin + `checkh(unpacked[0], 8'h10); + `checkh(unpacked[1], 8'h20); + `checkh(unpacked[2], 8'h30); + `checkh(unpacked[3], 8'h40); + end + else if (cyc == 62) begin + force unpacked[1] = 8'hb1; + force unpacked[2] = 8'hc2; + end + else if (cyc == 63) begin + `checkh(unpacked[0], 8'h10); + `checkh(unpacked[1], 8'hb1); + `checkh(unpacked[2], 8'hc2); + `checkh(unpacked[3], 8'h40); + end + else if (cyc == 64) begin + release unpacked[1]; + release unpacked[2]; + end + else if (cyc == 65) begin + `checkh(unpacked[0], 8'h10); + `checkh(unpacked[1], 8'hb1); + `checkh(unpacked[2], 8'hc2); + `checkh(unpacked[3], 8'h40); + unpacked[1] <= 8'h21; + unpacked[2] <= 8'h32; + end + else if (cyc == 66) begin + `checkh(unpacked[0], 8'h10); + `checkh(unpacked[1], 8'h21); + `checkh(unpacked[2], 8'h32); + `checkh(unpacked[3], 8'h40); + end + // else if (cyc == 99) begin $write("*-* All Finished *-*\n"); $finish; diff --git a/test_regress/t/t_force_chained.out b/test_regress/t/t_force_chained.out deleted file mode 100644 index e52b51e2b..000000000 --- a/test_regress/t/t_force_chained.out +++ /dev/null @@ -1,4 +0,0 @@ -%Error: t/t_force_chained.v:30: got='h0 exp='h00000001 -%Error: t/t_force_chained.v:36: got='h0 exp='h00000002 -%Error: t/t_force_chained.v:43: got='h0 exp='h00000003 -%Error: t/t_force_chained.v:49: got='h0 exp='h00000003 diff --git a/test_regress/t/t_force_chained.py b/test_regress/t/t_force_chained.py index e9f4a44c9..3c6aa8922 100755 --- a/test_regress/t/t_force_chained.py +++ b/test_regress/t/t_force_chained.py @@ -13,6 +13,6 @@ test.scenarios('vlt') test.compile(verilator_flags2=["--binary"]) -test.execute(expect_filename=test.golden_filename) +test.execute() test.passes() diff --git a/test_regress/t/t_force_chained.v b/test_regress/t/t_force_chained.v index e197ee355..75a9b8082 100644 --- a/test_regress/t/t_force_chained.v +++ b/test_regress/t/t_force_chained.v @@ -4,10 +4,10 @@ // SPDX-FileCopyrightText: 2025 Antmicro // SPDX-License-Identifier: CC0-1.0 -// verilog_format: off -`define stop // TODO -`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 +`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: off module t; reg [1:0] a; @@ -26,7 +26,6 @@ module t; #1; `checkh(a, 1); `checkh(b, 1); - // TODO implement inter-dependency resolution between force statements `checkh(c, 1); a = 2; diff --git a/test_regress/t/t_force_cond.py b/test_regress/t/t_force_cond.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_force_cond.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_force_cond.v b/test_regress/t/t_force_cond.v new file mode 100644 index 000000000..46f8ac3c9 --- /dev/null +++ b/test_regress/t/t_force_cond.v @@ -0,0 +1,35 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Antmicro. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +`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) + +`define IMPURE_ONE |($random | $random) + +module t; + reg [1:0] a = 2; + bit [1:0] b = 1; + bit [1:0] c = 0; + + initial begin + force a = b; + if (`IMPURE_ONE == 1) force a = c; + if (`IMPURE_ONE == 0) force a = b; + c = 3; + b = 2; + #1; + `checkh(a, 3); + if (`IMPURE_ONE == 1) force a = b; + if (`IMPURE_ONE == 0) force a = c; + c = 2; + b = 1; + #1; + `checkh(a, 1); + #1 $finish; + end + +endmodule diff --git a/test_regress/t/t_force_forceable_readwrite_unsup.out b/test_regress/t/t_force_forceable_readwrite_unsup.out new file mode 100644 index 000000000..b766afe41 --- /dev/null +++ b/test_regress/t/t_force_forceable_readwrite_unsup.out @@ -0,0 +1,5 @@ +%Error-UNSUPPORTED: t/t_force_forceable_readwrite_unsup.v:15:14: Unsupported: Signals used via read-write reference cannot be forced + 15 | take_ref(value); + | ^~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_force_unpacked_unsup.py b/test_regress/t/t_force_forceable_readwrite_unsup.py similarity index 85% rename from test_regress/t/t_force_unpacked_unsup.py rename to test_regress/t/t_force_forceable_readwrite_unsup.py index 382ad0d44..344a4e20a 100755 --- a/test_regress/t/t_force_unpacked_unsup.py +++ b/test_regress/t/t_force_forceable_readwrite_unsup.py @@ -4,12 +4,12 @@ # 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 Wilson Snyder +# SPDX-FileCopyrightText: 2026 Wilson Snyder # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 import vltest_bootstrap -test.scenarios('simulator') +test.scenarios('vlt') test.lint(fails=True, expect_filename=test.golden_filename) diff --git a/test_regress/t/t_force_forceable_readwrite_unsup.v b/test_regress/t/t_force_forceable_readwrite_unsup.v new file mode 100644 index 000000000..b5b320971 --- /dev/null +++ b/test_regress/t/t_force_forceable_readwrite_unsup.v @@ -0,0 +1,19 @@ +// 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; + logic value /* verilator forceable */; + + task take_ref(ref logic i); + // verilator no_inline_task + endtask + + initial begin + take_ref(value); + force value = 1'b1; + $finish; + end +endmodule diff --git a/test_regress/t/t_force_forceable_rhs_ref.py b/test_regress/t/t_force_forceable_rhs_ref.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_force_forceable_rhs_ref.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_force_forceable_rhs_ref.v b/test_regress/t/t_force_forceable_rhs_ref.v new file mode 100644 index 000000000..7ff6f01b8 --- /dev/null +++ b/test_regress/t/t_force_forceable_rhs_ref.v @@ -0,0 +1,49 @@ +// 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 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 ( + input clk +); + integer cyc = 0; + logic src_1 = 0; + logic [7:0] src_8 = 8'h10; + logic dst_1 /* verilator forceable */; + logic [7:0] dst_8 /* verilator forceable */; + + always @* dst_1 = src_1; + always @* dst_8 = src_8; + + always @(posedge clk) begin + cyc <= cyc + 1; + case (cyc) + 0: begin + force dst_1 = src_1; + force dst_8 = src_8 ^ 8'hf0; + `checkh(dst_1, 1'b0); + `checkh(dst_8, 8'he0); + end + 1: begin + release dst_1; + release dst_8; + src_1 = 1'b1; + src_8 = 8'h23; + end + 2: begin + `checkh(dst_1, 1'b1); + `checkh(dst_8, 8'h23); + $write("*-* All Finished *-*\n"); + $finish; + end + default: begin + end + endcase + end +endmodule diff --git a/test_regress/t/t_force_multi.v b/test_regress/t/t_force_multi.v index c856443e5..1479359f5 100644 --- a/test_regress/t/t_force_multi.v +++ b/test_regress/t/t_force_multi.v @@ -9,35 +9,35 @@ `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(/*AUTOARG*/ - // Inputs - input clk - ); +module t ( /*AUTOARG*/ + // Inputs + input clk +); - integer cyc = 0; + integer cyc = 0; - logic [3:0] busa; - logic [3:0] busb; + logic [3:0] busa; + logic [3:0] busb; - // Test loop - always @ (posedge clk) begin - cyc <= cyc + 1; - if (cyc == 0) begin - busa <= 4'b0101; - busb <= 4'b0111; - end - else if (cyc == 1) begin - force {busa, busb} = 8'b1111_1101; - end - else if (cyc == 2) begin - `checkh(busa, 4'b1111); - `checkh(busb, 4'b1101); - end - // - else if (cyc == 99) begin - $write("*-* All Finished *-*\n"); - $finish; - end - end + // Test loop + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 0) begin + busa <= 4'b0101; + busb <= 4'b0111; + end + else if (cyc == 1) begin + force {busa, busb} = 8'b1111_1101; + end + else if (cyc == 2) begin + `checkh(busa, 4'b1111); + `checkh(busb, 4'b1101); + end + // + else if (cyc == 99) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end endmodule diff --git a/test_regress/t/t_force_nested_struct.py b/test_regress/t/t_force_nested_struct.py new file mode 100755 index 000000000..1005aa93b --- /dev/null +++ b/test_regress/t/t_force_nested_struct.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 by Wilson Snyder. 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('vlt') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_force_nested_struct.v b/test_regress/t/t_force_nested_struct.v new file mode 100644 index 000000000..6e71d41a4 --- /dev/null +++ b/test_regress/t/t_force_nested_struct.v @@ -0,0 +1,48 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Antmicro. +// SPDX-FileCopyrightText: 2026 Antmicro +// 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='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0) +// verilog_format: on + +typedef struct { + logic [31:0] val; + logic [31:0] other; +} St1; + +typedef struct { + St1 inner; + logic [31:0] tail; +} St2; + +module m; + St2 st2; + St2 forced; + St2 snapshot; + initial begin + st2.inner.val = 32'h11; + st2.inner.other = 32'h12; + st2.tail = 32'h13; + forced.inner.val = 32'h21; + forced.inner.other = 32'h22; + forced.tail = 32'h23; + force st2 = forced; + snapshot = st2; + `checkh(snapshot.inner.val, 32'h21); + `checkh(snapshot.inner.val[0], 1'b1); + force st2.inner.val = 32'h30; + release st2.inner.val; + snapshot = st2; + `checkh(snapshot.inner.val, 32'h21); + `checkh(snapshot.inner.val[0], 1'b1); + `checkh(snapshot.inner.other, 32'h22); + `checkh(snapshot.tail, 32'h23); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_force_nested_struct2.py b/test_regress/t/t_force_nested_struct2.py new file mode 100755 index 000000000..c03142385 --- /dev/null +++ b/test_regress/t/t_force_nested_struct2.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2026 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_force_nested_struct2.v b/test_regress/t/t_force_nested_struct2.v new file mode 100644 index 000000000..aa156003a --- /dev/null +++ b/test_regress/t/t_force_nested_struct2.v @@ -0,0 +1,35 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Antmicro. +// SPDX-FileCopyrightText: 2026 Antmicro +// 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='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0) +// verilog_format: on + +typedef struct packed { + logic [31:0] value; +} Entry; + +typedef struct packed { + Entry [1:0][1:0] entries; +} DataBlock; + +module sub; + DataBlock data_block; +endmodule + +module t; + sub sub1 (); + logic [31:0] forced_value; + initial begin + forced_value = 32'h00000001; + force sub1.data_block.entries[0][0].value = forced_value[31:0]; + `checkh(sub1.data_block.entries[0][0].value[0], 1'b1); + `checkh(sub1.data_block.entries[0][0].value, 32'h00000001); + $finish; + end +endmodule diff --git a/test_regress/t/t_force_port_inline.v b/test_regress/t/t_force_port_inline.v index cb5eee2b3..27f7fbe18 100644 --- a/test_regress/t/t_force_port_inline.v +++ b/test_regress/t/t_force_port_inline.v @@ -9,69 +9,75 @@ `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 sub( - input wire [7:0] i, - output wire [7:0] o +module sub ( + input wire [7:0] i, + output wire [7:0] o ); - // Must inline this module - // verilator inline_module + // Must inline this module + // verilator inline_module - wire [7:0] m; - assign m = i; - assign o = m; + wire [7:0] m; + assign m = i; + assign o = m; endmodule module top; - // Variable input - reg [7:0] i = 8'h01; - reg [7:0] o_v; - sub sub_v(i, o_v); + // Variable input + reg [7:0] i = 8'h01; + reg [7:0] o_v; + sub sub_v ( + i, + o_v + ); - // Constant input - reg [7:0] o_c; - sub sub_c(8'h10, o_c); + // Constant input + reg [7:0] o_c; + sub sub_c ( + 8'h10, + o_c + ); - logic clk = 1'b0; - always #1 clk = ~clk; - int cyc = 0; + logic clk = 1'b0; + always #1 clk = ~clk; + int cyc = 0; - always @ (posedge clk) begin - cyc <= cyc + 1; - if (cyc == 1) begin - `checkh(i, 8'h01); - `checkh(sub_v.i, 8'h01); - `checkh(sub_v.m, 8'h01); - `checkh(sub_v.o, 8'h01); - `checkh(o_v, 8'h01); - `checkh(sub_c.i, 8'h10); - `checkh(sub_c.m, 8'h10); - `checkh(sub_c.o, 8'h10); - `checkh(o_c, 8'h10); - end - else if (cyc == 2) begin - force sub_v.i = 8'h02; - force sub_v.m = 8'h03; - force sub_v.o = 8'h04; - force sub_c.i = 8'h20; - force sub_c.m = 8'h30; - force sub_c.o = 8'h40; - end - else if (cyc == 3) begin - `checkh(i, 8'h01); - `checkh(sub_v.i, 8'h02); - `checkh(sub_v.m, 8'h03); - `checkh(sub_v.o, 8'h04); - `checkh(o_v, 8'h04); - `checkh(sub_c.i, 8'h20); - `checkh(sub_c.m, 8'h30); - `checkh(sub_c.o, 8'h40); - `checkh(o_c, 8'h40); - end - // - else if (cyc == 99) begin - $write("*-* All Finished *-*\n"); - $finish; - end - end + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) begin + `checkh(i, 8'h01); + `checkh(sub_v.i, 8'h01); + `checkh(sub_v.m, 8'h01); + `checkh(sub_v.o, 8'h01); + `checkh(o_v, 8'h01); + `checkh(sub_c.i, 8'h10); + `checkh(sub_c.m, 8'h10); + `checkh(sub_c.o, 8'h10); + `checkh(o_c, 8'h10); + end + else if (cyc == 2) begin + force sub_v.i = 8'h02; + force sub_v.m = 8'h03; + force sub_v.o = 8'h04; + force sub_c.i = 8'h20; + force sub_c.m = 8'h30; + force sub_c.o = 8'h40; + end + else if (cyc == 3) begin + `checkh(i, 8'h01); + `checkh(sub_v.i, 8'h02); + `checkh(sub_v.m, 8'h03); + `checkh(sub_v.o, 8'h04); + `checkh(o_v, 8'h04); + `checkh(sub_c.i, 8'h20); + `checkh(sub_c.m, 8'h30); + `checkh(sub_c.o, 8'h40); + `checkh(o_c, 8'h40); + end + // + else if (cyc == 99) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end endmodule diff --git a/test_regress/t/t_force_readwrite_unsup.out b/test_regress/t/t_force_readwrite_unsup.out index e8f793171..c13632bf3 100644 --- a/test_regress/t/t_force_readwrite_unsup.out +++ b/test_regress/t/t_force_readwrite_unsup.out @@ -2,7 +2,4 @@ 19 | cls.take_ref(a); | ^ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_force_readwrite_unsup.v:20:18: Unsupported: Signals used via read-write reference cannot be forced - 20 | cls.take_ref(b); - | ^ %Error: Exiting due to diff --git a/test_regress/t/t_force_unpacked_unsup.out b/test_regress/t/t_force_unpacked_unsup.out deleted file mode 100644 index 3d07924aa..000000000 --- a/test_regress/t/t_force_unpacked_unsup.out +++ /dev/null @@ -1,14 +0,0 @@ -%Error-UNSUPPORTED: t/t_force_unpacked_unsup.v:26:7: Unsupported: Force of variable with >= 1000 unpacked elements - 26 | bit big_array[40][40][40]; - | ^~~~~~~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_force_unpacked_unsup.v:25:12: Unsupported: Force of variable with >= 1000 unpacked elements - 25 | struct_t s_array[3000]; - | ^~~~~~~ -%Error-UNSUPPORTED: t/t_force_unpacked_unsup.v:27:11: Forcing variable of unsupported type: REFDTYPE 'union_t' - 27 | union_t my_union; - | ^~~~~~~~ -%Error: Internal Error: t/t_force_unpacked_unsup.v:27:11: ../V3Force.cpp:#: Unsupported: Force of variable of unhandled data type - 27 | union_t my_union; - | ^~~~~~~~ - ... This fatal error may be caused by the earlier error(s); resolve those first. diff --git a/test_regress/t/t_force_unpacked_unsup.v b/test_regress/t/t_force_unpacked_unsup.v deleted file mode 100644 index 225f2cb2d..000000000 --- a/test_regress/t/t_force_unpacked_unsup.v +++ /dev/null @@ -1,84 +0,0 @@ -// 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 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) -`define checkr(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got=%f exp=%f\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); -// verilog_format: on - -module t ( - input clk -); - - integer cyc = 0; - - typedef struct {int x;} struct_t; - typedef union { - int x; - logic y; - } union_t; - - struct_t s_array[3000]; - bit big_array[40][40][40]; - union_t my_union; - - // Test loop - always @(posedge clk) begin - cyc <= cyc + 1; - if (cyc == 0) begin - big_array[1][2][3] <= 1; - s_array[1].x <= 1; - my_union.x <= 1; - end - else if (cyc == 1) begin - `checkr(big_array[1][2][3], 1); - `checkh(s_array[1].x, 1); - `checkh(my_union.x, 1); - end - else if (cyc == 2) begin - force big_array[1][2][3] = 0; - force s_array[1].x = 0; - force my_union.x = 0; - end - else if (cyc == 3) begin - `checkr(big_array[1][2][3], 0); - big_array[1][2][3] <= 1; - `checkh(s_array[1].x, 0); - s_array[1].x <= 1; - `checkh(my_union.x, 0); - my_union.x <= 1; - end - else if (cyc == 4) begin - `checkr(big_array[1][2][3], 0); - `checkh(s_array[1].x, 0); - `checkh(my_union.x, 0); - end - else if (cyc == 5) begin - release big_array[1][2][3]; - release s_array[1].x; - release my_union.x; - end - else if (cyc == 6) begin - `checkr(big_array[1][2][3], 0); - big_array[1][2][3] <= 1; - `checkh(s_array[1].x, 0); - s_array[1].x <= 1; - `checkh(my_union.x, 0); - my_union.x <= 1; - end - else if (cyc == 7) begin - `checkr(big_array[1][2][3], 1); - `checkh(s_array[1].x, 1); - `checkh(my_union.x, 1); - end - else if (cyc == 8) begin - $write("*-* All Finished *-*\n"); - $finish; - end - end - -endmodule diff --git a/test_regress/t/t_forceable_public_flat.py b/test_regress/t/t_forceable_public_flat.py index f18943111..8a355eab7 100755 --- a/test_regress/t/t_forceable_public_flat.py +++ b/test_regress/t/t_forceable_public_flat.py @@ -14,7 +14,6 @@ test.scenarios('vlt') test.compile() files = test.glob_some(test.obj_dir + "/" + test.vm_prefix + "*.h") -test.file_grep_any(files, r' u_sub__DOT__a__VforceRd') test.file_grep_any(files, r' u_sub__DOT__a__VforceEn') test.file_grep_any(files, r' u_sub__DOT__a__VforceVal') diff --git a/test_regress/t/t_forceable_public_flat.v b/test_regress/t/t_forceable_public_flat.v index e5b842be6..711bd3859 100644 --- a/test_regress/t/t_forceable_public_flat.v +++ b/test_regress/t/t_forceable_public_flat.v @@ -25,7 +25,7 @@ module t ( endmodule module sub ( - input a /* verilator forceable */ /* verilator public_flat */, + input a /* verilator forceable */ /* verilator public_flat */, input b, output c ); diff --git a/test_regress/t/t_forceable_string_bad.out b/test_regress/t/t_forceable_string_bad.out index 6735c1393..6fb21ca01 100644 --- a/test_regress/t/t_forceable_string_bad.out +++ b/test_regress/t/t_forceable_string_bad.out @@ -2,8 +2,4 @@ 8 | string str /*verilator forceable*/; | ^~~ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error-UNSUPPORTED: t/t_forceable_string_bad.v:8:10: Forcing variable of unsupported type: BASICDTYPE 'string' - 8 | string str /*verilator forceable*/; - | ^~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error: Exiting due to diff --git a/test_regress/t/t_fsm_metacmt_dump.py b/test_regress/t/t_fsm_metacmt_dump.py new file mode 100755 index 000000000..d5a9dfe04 --- /dev/null +++ b/test_regress/t/t_fsm_metacmt_dump.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM metacomment dump 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 json + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_fsm_metacmt_dump.v" + +test.lint(v_flags=["--dump-tree --dump-tree-json --no-json-edit-nums"]) + +tree_files = test.glob_some(test.obj_dir + "/*.tree") +json_files = test.glob_some(test.obj_dir + "/*.tree.json") + +test.file_grep_any(tree_files, r'\[aFSMSTATE\]') +test.file_grep_any(tree_files, r'\[aFSMRESETARC\]') +test.file_grep_any(tree_files, r'\[aFSMARCCOND\]') + +test.file_grep_any(json_files, r'"attrFsmState":true') +test.file_grep_any(json_files, r'"attrFsmResetArc":true') +test.file_grep_any(json_files, r'"attrFsmArcInclCond":true') + +for filename in json_files: + with open(filename, 'r', encoding="utf8") as fh: + json.load(fh) + +test.passes() diff --git a/test_regress/t/t_fsm_metacmt_dump.v b/test_regress/t/t_fsm_metacmt_dump.v new file mode 100644 index 000000000..6206125e1 --- /dev/null +++ b/test_regress/t/t_fsm_metacmt_dump.v @@ -0,0 +1,34 @@ +// DESCRIPTION: Verilator: FSM metacomment dump test +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input logic clk, + input logic rst +); + + typedef enum logic [0:0] { + S0 = 1'b0, + S1 = 1'b1 + } state_t; + + state_t state_reset /*verilator fsm_reset_arc*/; + state_t state_cond /*verilator fsm_arc_include_cond*/; + logic forced_state /*verilator fsm_state*/; + + always_ff @(posedge clk) begin + if (rst) begin + state_reset <= S0; + state_cond <= S0; + forced_state <= 1'b0; + end else begin + state_reset <= S1; + if (state_cond) state_cond <= S0; + else state_cond <= S1; + forced_state <= ~forced_state; + end + end + +endmodule diff --git a/test_regress/t/t_fsmmulti_same_bad.out b/test_regress/t/t_fsmmulti_same_bad.out new file mode 100644 index 000000000..4ffe2d1c5 --- /dev/null +++ b/test_regress/t/t_fsmmulti_same_bad.out @@ -0,0 +1,6 @@ +%Warning-COVERIGN: t/t_fsmmulti_same_bad.v:30:5: Ignoring unsupported: FSM coverage on multiple supported case statements found in the same always block. Only the first candidate will be instrumented. + 30 | case (state) + | ^~~~ + ... 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_fsmmulti_same_bad.py b/test_regress/t/t_fsmmulti_same_bad.py new file mode 100755 index 000000000..a89e2ec97 --- /dev/null +++ b/test_regress/t/t_fsmmulti_same_bad.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: same-state multi-candidate FSM error 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') + +# Multiple supported case candidates on the same state variable in one +# always_ff now warn and keep only the first candidate instrumented. Different- +# state multi-candidate cases still use the existing FSMMULTI warning path; this +# test locks down only the same-state unsupported form. +test.lint(verilator_flags2=["--coverage-fsm"], fails=True) + +test.file_grep( + test.compile_log_filename, + r'%Warning-COVERIGN: t/t_fsmmulti_same_bad.v:30:5: Ignoring unsupported: FSM coverage on ' + r'multiple supported case statements found in the same always block. Only the first ' + r'candidate will be instrumented.') + +test.passes() diff --git a/test_regress/t/t_fsmmulti_same_bad.v b/test_regress/t/t_fsmmulti_same_bad.v new file mode 100644 index 000000000..ef99d5812 --- /dev/null +++ b/test_regress/t/t_fsmmulti_same_bad.v @@ -0,0 +1,36 @@ +// DESCRIPTION: Verilator: same-state multi-candidate FSM error test +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input logic clk, + input logic rst +); + + typedef enum logic [1:0] { + S0, S1, S2 + } state_t; + + state_t state; + + // This is intentionally non-idiomatic RTL. The detector sees one supported + // candidate in the reset-if else branch and a second supported top-level + // case on the same state variable. That same-state duplicate is rejected. + always_ff @(posedge clk) begin + if (rst) begin + state <= S0; + end else begin + case (state) + S0: state <= S1; + default: ; + endcase + end + case (state) + S1: state <= S2; + default: ; + endcase + end + +endmodule diff --git a/test_regress/t/t_fsmmulti_warn_bad.out b/test_regress/t/t_fsmmulti_warn_bad.out new file mode 100644 index 000000000..00c98c6ad --- /dev/null +++ b/test_regress/t/t_fsmmulti_warn_bad.out @@ -0,0 +1,6 @@ +%Warning-FSMMULTI: t/t_fsmmulti_warn_bad.v:27:5: FSM coverage: multiple enum-typed case statements found in the same always block. Only the first candidate will be instrumented. + 27 | case (state_b) + | ^~~~ + ... For warning description see https://verilator.org/warn/FSMMULTI?v=latest + ... Use "/* verilator lint_off FSMMULTI */" and lint_on around source to disable this message. +%Error: Exiting due to diff --git a/test_regress/t/t_fsmmulti_warn_bad.py b/test_regress/t/t_fsmmulti_warn_bad.py new file mode 100755 index 000000000..5f224eae6 --- /dev/null +++ b/test_regress/t/t_fsmmulti_warn_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSMMULTI warning 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(verilator_flags2=["--coverage-fsm"], fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_fsmmulti_warn_bad.v b/test_regress/t/t_fsmmulti_warn_bad.v new file mode 100644 index 000000000..1c385d142 --- /dev/null +++ b/test_regress/t/t_fsmmulti_warn_bad.v @@ -0,0 +1,33 @@ +// DESCRIPTION: Verilator: FSMMULTI warning test +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input logic clk +); + + typedef enum logic [1:0] { + A0, A1 + } a_state_t; + + typedef enum logic [1:0] { + B0, B1 + } b_state_t; + + a_state_t state_a; + b_state_t state_b; + + always_ff @(posedge clk) begin + case (state_a) + A0: state_a <= A1; + default: state_a <= A0; + endcase + case (state_b) + B0: state_b <= B1; + default: state_b <= B0; + endcase + end + +endmodule diff --git a/test_regress/t/t_fsmmulti_warn_off.py b/test_regress/t/t_fsmmulti_warn_off.py new file mode 100755 index 000000000..018b61822 --- /dev/null +++ b/test_regress/t/t_fsmmulti_warn_off.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSMMULTI warning disabled without --coverage-fsm +# +# 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(verilator_flags2=["--coverage-line"]) +test.file_grep_not(test.compile_log_filename, r"FSMMULTI") + +test.passes() diff --git a/test_regress/t/t_fsmmulti_warn_off.v b/test_regress/t/t_fsmmulti_warn_off.v new file mode 100644 index 000000000..4cfb1242d --- /dev/null +++ b/test_regress/t/t_fsmmulti_warn_off.v @@ -0,0 +1,42 @@ +// DESCRIPTION: Verilator: FSMMULTI warning disabled without --coverage-fsm +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t; + typedef enum logic [1:0] { + A0 = 2'd0, + A1 = 2'd1, + A2 = 2'd2 + } state_a_t; + + typedef enum logic [1:0] { + B0 = 2'd0, + B1 = 2'd1, + B2 = 2'd2 + } state_b_t; + + logic clk; + logic rst; + state_a_t a_state; + state_b_t b_state; + + always_ff @(posedge clk) begin + if (rst) begin + a_state <= A0; + b_state <= B0; + end else begin + case (a_state) + A0: a_state <= A1; + A1: a_state <= A2; + default: a_state <= A0; + endcase + case (b_state) + B0: b_state <= B1; + B1: b_state <= B2; + default: b_state <= B0; + endcase + end + end +endmodule diff --git a/test_regress/t/t_gate_array_multidim_bad.out b/test_regress/t/t_gate_array_multidim_bad.out new file mode 100644 index 000000000..78c1a33ab --- /dev/null +++ b/test_regress/t/t_gate_array_multidim_bad.out @@ -0,0 +1,5 @@ +%Error-UNSUPPORTED: t/t_gate_array_multidim_bad.v:12:14: Unsupported: Multidimensional gate instances. + 12 | and g [1:0][1:0] (y, a, b); + | ^ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_gate_array_multidim_bad.py b/test_regress/t/t_gate_array_multidim_bad.py new file mode 100755 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_gate_array_multidim_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_gate_array_multidim_bad.v b/test_regress/t/t_gate_array_multidim_bad.v new file mode 100644 index 000000000..509636c7e --- /dev/null +++ b/test_regress/t/t_gate_array_multidim_bad.v @@ -0,0 +1,13 @@ +// 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 + +// Verify multi-dim gate primitive arrays are rejected. + +module t; + wire a, b; + wire [1:0][1:0] y; + and g [1:0][1:0] (y, a, b); +endmodule diff --git a/test_regress/t/t_iface_array_multidim.py b/test_regress/t/t_iface_array_multidim.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_iface_array_multidim.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_iface_array_multidim.v b/test_regress/t/t_iface_array_multidim.v new file mode 100644 index 000000000..bb7cbd43b --- /dev/null +++ b/test_regress/t/t_iface_array_multidim.v @@ -0,0 +1,54 @@ +// 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 + +// Multi-dimensional interface instance arrays: minimal 2D reproducer. +// Exercises declaration, dotted access, multi-dim port connection and +// VCD-relevant naming. + +interface simple_if; + logic [7:0] data; +endinterface + +module t; + localparam int A = 2; + localparam int B = 3; + + simple_if bus [A-1:0][B-1:0] (); + + genvar gi, gj; + generate + for (gi = 0; gi < A; gi++) begin : g_a + for (gj = 0; gj < B; gj++) begin : g_b + initial bus[gi][gj].data = 8'(gi * B + gj + 1); + end + end + endgenerate + + // Runtime check via a chk array populated by the same genvar generate block. + logic [7:0] chk [A-1:0][B-1:0]; + generate + for (gi = 0; gi < A; gi++) begin : g_a_chk + for (gj = 0; gj < B; gj++) begin : g_b_chk + always_comb chk[gi][gj] = bus[gi][gj].data; + end + end + endgenerate + + initial begin + #1; + for (int i = 0; i < A; i++) begin + for (int j = 0; j < B; j++) begin + if (chk[i][j] !== 8'(i * B + j + 1)) begin + $write("%%Error: bus[%0d][%0d].data=%0d expected %0d\n", + i, j, chk[i][j], i * B + j + 1); + $stop; + end + end + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_array_multidim_3d.py b/test_regress/t/t_iface_array_multidim_3d.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_3d.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_iface_array_multidim_3d.v b/test_regress/t/t_iface_array_multidim_3d.v new file mode 100644 index 000000000..26fcc510b --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_3d.v @@ -0,0 +1,58 @@ +// 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 + +// 3D interface instance arrays. Ensures arbitrary rank, not just 2D. + +interface simple_if; + logic [15:0] data; +endinterface + +module t; + localparam int A = 2; + localparam int B = 2; + localparam int C = 3; + + simple_if bus [A-1:0][B-1:0][C-1:0] (); + + genvar gi, gj, gk; + generate + for (gi = 0; gi < A; gi++) begin : g_a + for (gj = 0; gj < B; gj++) begin : g_b + for (gk = 0; gk < C; gk++) begin : g_c + initial bus[gi][gj][gk].data = 16'(gi * B * C + gj * C + gk + 1); + end + end + end + endgenerate + + logic [15:0] chk [A-1:0][B-1:0][C-1:0]; + generate + for (gi = 0; gi < A; gi++) begin : g_a_chk + for (gj = 0; gj < B; gj++) begin : g_b_chk + for (gk = 0; gk < C; gk++) begin : g_c_chk + always_comb chk[gi][gj][gk] = bus[gi][gj][gk].data; + end + end + end + endgenerate + + initial begin + #1; + for (int i = 0; i < A; i++) begin + for (int j = 0; j < B; j++) begin + for (int k = 0; k < C; k++) begin + if (chk[i][j][k] !== 16'(i * B * C + j * C + k + 1)) begin + $write("%%Error: bus[%0d][%0d][%0d].data=%0d expected %0d\n", + i, j, k, chk[i][j][k], i * B * C + j * C + k + 1); + $stop; + end + end + end + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_array_multidim_3d_port.py b/test_regress/t/t_iface_array_multidim_3d_port.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_3d_port.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_iface_array_multidim_3d_port.v b/test_regress/t/t_iface_array_multidim_3d_port.v new file mode 100644 index 000000000..029abf42d --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_3d_port.v @@ -0,0 +1,59 @@ +// 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 + +// 3D iface-array port passed to a submodule. t_iface_array_multidim_3d +// covers 3D local declaration; this covers 3D through a port. + +interface simple_if; + logic [15:0] data; +endinterface + +module sink (simple_if b [1:0][1:0][2:0]); + logic [15:0] chk [1:0][1:0][2:0]; + genvar gi, gj, gk; + generate + for (gi = 0; gi < 2; gi++) begin : g_a + for (gj = 0; gj < 2; gj++) begin : g_b + for (gk = 0; gk < 3; gk++) begin : g_c + always_comb chk[gi][gj][gk] = b[gi][gj][gk].data; + end + end + end + endgenerate +endmodule + +module t; + simple_if bus [1:0][1:0][2:0] (); + sink inst (.b(bus)); + + genvar gi, gj, gk; + generate + for (gi = 0; gi < 2; gi++) begin : g_drive_a + for (gj = 0; gj < 2; gj++) begin : g_drive_b + for (gk = 0; gk < 3; gk++) begin : g_drive_c + initial bus[gi][gj][gk].data = 16'(gi * 6 + gj * 3 + gk + 1); + end + end + end + endgenerate + + initial begin + #1; + for (int i = 0; i < 2; i++) begin + for (int j = 0; j < 2; j++) begin + for (int k = 0; k < 3; k++) begin + if (inst.chk[i][j][k] !== 16'(i * 6 + j * 3 + k + 1)) begin + $write("%%Error: chk[%0d][%0d][%0d]=%0d expected %0d\n", + i, j, k, inst.chk[i][j][k], i * 6 + j * 3 + k + 1); + $stop; + end + end + end + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_array_multidim_hier.py b/test_regress/t/t_iface_array_multidim_hier.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_hier.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_iface_array_multidim_hier.v b/test_regress/t/t_iface_array_multidim_hier.v new file mode 100644 index 000000000..40b0a100d --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_hier.v @@ -0,0 +1,58 @@ +// 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 + +// Three-level hierarchy passing a multi-dim iface array by port at each +// boundary. Top reads leaf's chk array via dotted cross-hier reference, +// which also exercises the multi-dim dotted-access resolver. + +interface simple_if; + logic [7:0] data; +endinterface + +module leaf (simple_if b [1:0][2:0]); + logic [7:0] chk [1:0][2:0]; + genvar gi, gj; + generate + for (gi = 0; gi < 2; gi++) begin : g_a + for (gj = 0; gj < 3; gj++) begin : g_b + always_comb chk[gi][gj] = b[gi][gj].data; + end + end + endgenerate +endmodule + +module mid (simple_if b [1:0][2:0]); + leaf leaf_inst (.b(b)); +endmodule + +module t; + simple_if bus [1:0][2:0] (); + mid mid_inst (.b(bus)); + + genvar gi, gj; + generate + for (gi = 0; gi < 2; gi++) begin : g_drive_a + for (gj = 0; gj < 3; gj++) begin : g_drive_b + initial bus[gi][gj].data = 8'(gi * 3 + gj + 7); + end + end + endgenerate + + initial begin + #1; + for (int i = 0; i < 2; i++) begin + for (int j = 0; j < 3; j++) begin + if (mid_inst.leaf_inst.chk[i][j] !== 8'(i * 3 + j + 7)) begin + $write("%%Error: leaf.chk[%0d][%0d]=%0d expected %0d\n", + i, j, mid_inst.leaf_inst.chk[i][j], i * 3 + j + 7); + $stop; + end + end + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_array_multidim_modport.py b/test_regress/t/t_iface_array_multidim_modport.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_modport.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_iface_array_multidim_modport.v b/test_regress/t/t_iface_array_multidim_modport.v new file mode 100644 index 000000000..d30703f2d --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_modport.v @@ -0,0 +1,59 @@ +// 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 + +// Multi-dim iface array ports typed with a modport (both source and sink). +// Mirrors 1D modport-port coverage in t_interface_array_loop for the +// multi-dim pin-fanout path. + +interface simple_if; + logic [7:0] data; + modport source(output data); + modport sink(input data); +endinterface + +module src (simple_if.source b [1:0][2:0]); + genvar gi, gj; + generate + for (gi = 0; gi < 2; gi++) begin : g_a + for (gj = 0; gj < 3; gj++) begin : g_b + initial b[gi][gj].data = 8'(gi * 3 + gj + 20); + end + end + endgenerate +endmodule + +module snk (simple_if.sink b [1:0][2:0]); + logic [7:0] chk [1:0][2:0]; + genvar gi, gj; + generate + for (gi = 0; gi < 2; gi++) begin : g_a + for (gj = 0; gj < 3; gj++) begin : g_b + always_comb chk[gi][gj] = b[gi][gj].data; + end + end + endgenerate +endmodule + +module t; + simple_if bus [1:0][2:0] (); + src src_inst (.b(bus)); + snk snk_inst (.b(bus)); + + initial begin + #1; + for (int i = 0; i < 2; i++) begin + for (int j = 0; j < 3; j++) begin + if (snk_inst.chk[i][j] !== 8'(i * 3 + j + 20)) begin + $write("%%Error: chk[%0d][%0d]=%0d expected %0d\n", + i, j, snk_inst.chk[i][j], i * 3 + j + 20); + $stop; + end + end + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_array_multidim_multi_inst.py b/test_regress/t/t_iface_array_multidim_multi_inst.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_multi_inst.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_iface_array_multidim_multi_inst.v b/test_regress/t/t_iface_array_multidim_multi_inst.v new file mode 100644 index 000000000..1318c0fa7 --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_multi_inst.v @@ -0,0 +1,64 @@ +// 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 + +// Two instances of the same submodule with a multi-dim iface-array port. +// Exercises the subsequent-instance path in V3Inst::visit(AstPin) multi-dim: +// the first instance de-arrays sink's port var and unlinks it; the second +// instance finds pinVarp already unlinked and reuses the per-element vars +// cached in m_deModVars. + +interface simple_if; + logic [7:0] data; +endinterface + +module sink (simple_if b [1:0][2:0]); + logic [7:0] chk [1:0][2:0]; + genvar gi, gj; + generate + for (gi = 0; gi < 2; gi++) begin : g_a + for (gj = 0; gj < 3; gj++) begin : g_b + always_comb chk[gi][gj] = b[gi][gj].data; + end + end + endgenerate +endmodule + +module t; + simple_if bus1 [1:0][2:0] (); + simple_if bus2 [1:0][2:0] (); + sink inst1 (.b(bus1)); + sink inst2 (.b(bus2)); + + genvar gi, gj; + generate + for (gi = 0; gi < 2; gi++) begin : g_drive_a + for (gj = 0; gj < 3; gj++) begin : g_drive_b + initial begin + bus1[gi][gj].data = 8'(gi * 3 + gj + 1); + bus2[gi][gj].data = 8'(gi * 3 + gj + 100); + end + end + end + endgenerate + + initial begin + #1; + if (inst1.chk[0][0] !== 8'd1) begin $write("%%Error inst1[0][0]=%0d\n", inst1.chk[0][0]); $stop; end + if (inst1.chk[0][1] !== 8'd2) begin $write("%%Error inst1[0][1]=%0d\n", inst1.chk[0][1]); $stop; end + if (inst1.chk[0][2] !== 8'd3) begin $write("%%Error inst1[0][2]=%0d\n", inst1.chk[0][2]); $stop; end + if (inst1.chk[1][0] !== 8'd4) begin $write("%%Error inst1[1][0]=%0d\n", inst1.chk[1][0]); $stop; end + if (inst1.chk[1][1] !== 8'd5) begin $write("%%Error inst1[1][1]=%0d\n", inst1.chk[1][1]); $stop; end + if (inst1.chk[1][2] !== 8'd6) begin $write("%%Error inst1[1][2]=%0d\n", inst1.chk[1][2]); $stop; end + if (inst2.chk[0][0] !== 8'd100) begin $write("%%Error inst2[0][0]=%0d\n", inst2.chk[0][0]); $stop; end + if (inst2.chk[0][1] !== 8'd101) begin $write("%%Error inst2[0][1]=%0d\n", inst2.chk[0][1]); $stop; end + if (inst2.chk[0][2] !== 8'd102) begin $write("%%Error inst2[0][2]=%0d\n", inst2.chk[0][2]); $stop; end + if (inst2.chk[1][0] !== 8'd103) begin $write("%%Error inst2[1][0]=%0d\n", inst2.chk[1][0]); $stop; end + if (inst2.chk[1][1] !== 8'd104) begin $write("%%Error inst2[1][1]=%0d\n", inst2.chk[1][1]); $stop; end + if (inst2.chk[1][2] !== 8'd105) begin $write("%%Error inst2[1][2]=%0d\n", inst2.chk[1][2]); $stop; end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_array_multidim_nested.py b/test_regress/t/t_iface_array_multidim_nested.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_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"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_iface_array_multidim_nested.v b/test_regress/t/t_iface_array_multidim_nested.v new file mode 100644 index 000000000..6a134d92c --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_nested.v @@ -0,0 +1,68 @@ +// 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 + +// Multi-dim iface array where outer iface contains an inner iface. +// outer_if oarr[A][B](), outer_if holds a single inner_if instance. +// Access via oarr[i][j].inner.data. + +interface inner_if; + logic [7:0] data; +endinterface + +interface outer_if; + inner_if inner(); + logic [7:0] tag; +endinterface + +module t; + localparam int A = 2; + localparam int B = 2; + + outer_if oarr [A-1:0][B-1:0] (); + + genvar gi, gj; + generate + for (gi = 0; gi < A; gi++) begin : g_a + for (gj = 0; gj < B; gj++) begin : g_b + initial begin + oarr[gi][gj].tag = 8'(gi * 16 + gj); + oarr[gi][gj].inner.data = 8'(gi * B + gj + 100); + end + end + end + endgenerate + + logic [7:0] chk_tag [A-1:0][B-1:0]; + logic [7:0] chk_inner [A-1:0][B-1:0]; + generate + for (gi = 0; gi < A; gi++) begin : g_a_chk + for (gj = 0; gj < B; gj++) begin : g_b_chk + always_comb chk_tag[gi][gj] = oarr[gi][gj].tag; + always_comb chk_inner[gi][gj] = oarr[gi][gj].inner.data; + end + end + endgenerate + + initial begin + #1; + for (int i = 0; i < A; i++) begin + for (int j = 0; j < B; j++) begin + if (chk_tag[i][j] !== 8'(i * 16 + j)) begin + $write("%%Error: oarr[%0d][%0d].tag=%0d expected %0d\n", + i, j, chk_tag[i][j], i * 16 + j); + $stop; + end + if (chk_inner[i][j] !== 8'(i * B + j + 100)) begin + $write("%%Error: oarr[%0d][%0d].inner.data=%0d expected %0d\n", + i, j, chk_inner[i][j], i * B + j + 100); + $stop; + end + end + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_array_multidim_nested_port.py b/test_regress/t/t_iface_array_multidim_nested_port.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_nested_port.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_iface_array_multidim_nested_port.v b/test_regress/t/t_iface_array_multidim_nested_port.v new file mode 100644 index 000000000..443b4255b --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_nested_port.v @@ -0,0 +1,69 @@ +// 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 + +// outer_if contains inner_if; an array of outer_if is passed as a port to +// a submodule which reaches through to inner_if's signal. Exercises +// hierarchical iface reference across the fanned-out multi-dim port. + +interface inner_if; + logic [7:0] data; +endinterface + +interface outer_if; + inner_if inner(); + logic [7:0] tag; +endinterface + +module sink (outer_if b [1:0][1:0]); + logic [7:0] chk_tag [1:0][1:0]; + logic [7:0] chk_inner [1:0][1:0]; + genvar gi, gj; + generate + for (gi = 0; gi < 2; gi++) begin : g_a + for (gj = 0; gj < 2; gj++) begin : g_b + always_comb chk_tag[gi][gj] = b[gi][gj].tag; + always_comb chk_inner[gi][gj] = b[gi][gj].inner.data; + end + end + endgenerate +endmodule + +module t; + outer_if oarr [1:0][1:0] (); + sink inst (.b(oarr)); + + genvar gi, gj; + generate + for (gi = 0; gi < 2; gi++) begin : g_drive_a + for (gj = 0; gj < 2; gj++) begin : g_drive_b + initial begin + oarr[gi][gj].tag = 8'(gi * 16 + gj); + oarr[gi][gj].inner.data = 8'(gi * 2 + gj + 100); + end + end + end + endgenerate + + initial begin + #1; + for (int i = 0; i < 2; i++) begin + for (int j = 0; j < 2; j++) begin + if (inst.chk_tag[i][j] !== 8'(i * 16 + j)) begin + $write("%%Error: chk_tag[%0d][%0d]=%0d expected %0d\n", + i, j, inst.chk_tag[i][j], i * 16 + j); + $stop; + end + if (inst.chk_inner[i][j] !== 8'(i * 2 + j + 100)) begin + $write("%%Error: chk_inner[%0d][%0d]=%0d expected %0d\n", + i, j, inst.chk_inner[i][j], i * 2 + j + 100); + $stop; + end + end + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_array_multidim_port.py b/test_regress/t/t_iface_array_multidim_port.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_port.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_iface_array_multidim_port.v b/test_regress/t/t_iface_array_multidim_port.v new file mode 100644 index 000000000..7ceb44155 --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_port.v @@ -0,0 +1,55 @@ +// 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 + +// Multi-dim iface array passed as a port to a submodule (the point of +// multi-dim iface arrays). Exercises the multi-dim pin-fanout branch in +// V3Inst::visit(AstPin) and the multi-dim branch in V3Inst::visit(AstVar) +// on the sink's port var. + +interface simple_if; + logic [7:0] data; +endinterface + +module sink (simple_if b [1:0][2:0]); + logic [7:0] chk [1:0][2:0]; + genvar gi, gj; + generate + for (gi = 0; gi < 2; gi++) begin : g_a + for (gj = 0; gj < 3; gj++) begin : g_b + always_comb chk[gi][gj] = b[gi][gj].data; + end + end + endgenerate +endmodule + +module t; + simple_if bus [1:0][2:0] (); + sink inst (.b(bus)); + + genvar gi, gj; + generate + for (gi = 0; gi < 2; gi++) begin : g_drive_a + for (gj = 0; gj < 3; gj++) begin : g_drive_b + initial bus[gi][gj].data = 8'(gi * 3 + gj + 1); + end + end + endgenerate + + initial begin + #1; + for (int i = 0; i < 2; i++) begin + for (int j = 0; j < 3; j++) begin + if (inst.chk[i][j] !== 8'(i * 3 + j + 1)) begin + $write("%%Error: inst.chk[%0d][%0d]=%0d expected %0d\n", + i, j, inst.chk[i][j], i * 3 + j + 1); + $stop; + end + end + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_array_multidim_port_write.py b/test_regress/t/t_iface_array_multidim_port_write.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_port_write.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_iface_array_multidim_port_write.v b/test_regress/t/t_iface_array_multidim_port_write.v new file mode 100644 index 000000000..161ea9b52 --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_port_write.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 + +// Multi-dim iface array port, sink WRITES into the iface signals, top reads. +// Complements t_iface_array_multidim_port (which has sink reading). + +interface simple_if; + logic [7:0] data; +endinterface + +module src (simple_if b [1:0][2:0]); + genvar gi, gj; + generate + for (gi = 0; gi < 2; gi++) begin : g_a + for (gj = 0; gj < 3; gj++) begin : g_b + initial b[gi][gj].data = 8'(gi * 3 + gj + 50); + end + end + endgenerate +endmodule + +module t; + simple_if bus [1:0][2:0] (); + src inst (.b(bus)); + + initial begin + #1; + if (bus[0][0].data !== 8'd50) begin $write("%%Error: bus[0][0]=%0d\n", bus[0][0].data); $stop; end + if (bus[0][1].data !== 8'd51) begin $write("%%Error: bus[0][1]=%0d\n", bus[0][1].data); $stop; end + if (bus[0][2].data !== 8'd52) begin $write("%%Error: bus[0][2]=%0d\n", bus[0][2].data); $stop; end + if (bus[1][0].data !== 8'd53) begin $write("%%Error: bus[1][0]=%0d\n", bus[1][0].data); $stop; end + if (bus[1][1].data !== 8'd54) begin $write("%%Error: bus[1][1]=%0d\n", bus[1][1].data); $stop; end + if (bus[1][2].data !== 8'd55) begin $write("%%Error: bus[1][2]=%0d\n", bus[1][2].data); $stop; end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_array_multidim_ranges.py b/test_regress/t/t_iface_array_multidim_ranges.py new file mode 100755 index 000000000..c39bdad54 --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_ranges.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: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +# -Wno-ASCRANGE: this test intentionally declares ascending iface array ranges +# ([0:N-1]) to exercise the ascending() branch in V3Inst. +test.compile(verilator_flags2=["--binary", "-Wno-ASCRANGE"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_iface_array_multidim_ranges.v b/test_regress/t/t_iface_array_multidim_ranges.v new file mode 100644 index 000000000..865630b09 --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_ranges.v @@ -0,0 +1,81 @@ +// 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 + +// Multi-dim iface arrays with ascending (leftModClass_Co_Dead link gets Deadified endclass endmodule -//TODO dead check with class extends +module Mod_Empty_Dead; +endmodule -module t; +module Mod_Parent_Empty_Dead; + Mod_Empty_Dead sub (); +endmodule + +interface If_Dead; + function void if_func_Dead; + endfunction + modport modport_Dead(import if_func_Dead); +endinterface + +package Pkg_public_kpt; + parameter int public_int_Keep /*verilator public_flat_rd*/ = 5; +endpackage + +package Pkg_Keep; + import "DPI-C" function void dpii_Keep(); + + export "DPI-C" function dpix_Keep; + function void dpix_Keep; + endfunction +endpackage + +module t ( /*AUTOARG*/); + + typedef struct {int struct_member_Dead;} struct_Dead_t; + struct_Dead_t var_struct_Dead; + + typedef int typedef_Dead1_t; + typedef typedef_Dead1_t typedef_Dead2_t; + + function void func_Dead; + endfunction generate if (0) begin - Mod_Dead cell_dead (); + Mod_Dead cell_nogen_Dead (); + If_Dead if_nogen_Dead (); end endgenerate + Mod_Empty_Dead cell_empty_Dead (); + Mod_Parent_Empty_Dead cell_parent_empty_Dead (); + + typedef_Dead1_t assigned_to_Dead1; + typedef_Dead2_t assigned_to_Dead2; + typedef_Dead2_t assigned_to_Dead3; + typedef_Dead2_t assigned_to_Dead4; + typedef_Dead2_t assigned_to_Dead5; + typedef_Dead2_t assigned_to_Dead6; + + always_comb assigned_to_Dead6 = assigned_to_Dead5; + always_comb assigned_to_Dead5 = assigned_to_Dead4; + always_comb assigned_to_Dead4 = assigned_to_Dead3; + always_comb assigned_to_Dead3 = assigned_to_Dead2; + always_comb assigned_to_Dead2 = assigned_to_Dead1; + initial begin + assigned_to_Dead1 = 1; + assigned_to_Dead1 = 2; $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_param_type_struct_member.py b/test_regress/t/t_param_type_struct_member.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_param_type_struct_member.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_param_type_struct_member.v b/test_regress/t/t_param_type_struct_member.v new file mode 100644 index 000000000..2d678c756 --- /dev/null +++ b/test_regress/t/t_param_type_struct_member.v @@ -0,0 +1,50 @@ +// 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 + +// Method/member access on a value-parameter whose type is +// the enclosing module's type-parameter. + +typedef struct packed { + logic [7:0] S_TIMER; + logic [7:0] M_TIMER; + logic [7:0] M_EXT; +} my_irq_t; + +module leaf #( + parameter type interrupts_t = logic, + parameter interrupts_t INTERRUPTS = '0 +) (); + logic [7:0] observed; + always_comb observed = INTERRUPTS.M_TIMER; +endmodule + +module mid #( + parameter type interrupts_t = logic, + parameter interrupts_t INTERRUPTS = '0 +) (); + leaf #( + .interrupts_t(interrupts_t), + .INTERRUPTS(INTERRUPTS) + ) l (); +endmodule + +module t; + localparam type irq_t = my_irq_t; + localparam irq_t IRQ = '{S_TIMER: 8'hAA, M_TIMER: 8'h55, M_EXT: 8'hCC}; + mid #( + .interrupts_t(irq_t), + .INTERRUPTS(IRQ) + ) m (); + initial begin + #1; + if (m.l.observed !== 8'h55) begin + $write("%%Error: observed=%h expected 55\n", m.l.observed); + $stop; + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_param_type_struct_member2.py b/test_regress/t/t_param_type_struct_member2.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_param_type_struct_member2.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_param_type_struct_member2.v b/test_regress/t/t_param_type_struct_member2.v new file mode 100644 index 000000000..acaf67a9a --- /dev/null +++ b/test_regress/t/t_param_type_struct_member2.v @@ -0,0 +1,34 @@ +// 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='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +package pkg; + typedef struct packed {logic [1:0][31:0] bar;} T; + localparam T t = 64'h87654321_deadbeef; +endpackage + +module foo #( + parameter type T = int, + parameter T t = 0 +) (); + initial begin + `checkh(t.bar[0], 32'hdeadbeef); + `checkh(t.bar[1], 32'h87654321); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule + +module top; + foo #( + .T(pkg::T), + .t(pkg::t) + ) u_foo (); +endmodule diff --git a/test_regress/t/t_randomize_std_inside_queue.py b/test_regress/t/t_randomize_std_inside_queue.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_randomize_std_inside_queue.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_randomize_std_inside_queue.v b/test_regress/t/t_randomize_std_inside_queue.v new file mode 100644 index 000000000..763cf0fe3 --- /dev/null +++ b/test_regress/t/t_randomize_std_inside_queue.v @@ -0,0 +1,57 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +class Cls; + int x; + int int_q[$]; + int dyn[]; + + function new(); + x = 0; + int_q = '{12, 13, 17, 19}; + dyn = '{21, 22, 23}; + endfunction + + task rand_with_queue(); + int ok; + ok = std::randomize(x) with {x inside {int_q};}; + `checkd(ok, 1); + `checkd(int_q.size(), 4); + `checkd(int_q[0], 12); + `checkd(int_q[1], 13); + `checkd(int_q[2], 17); + `checkd(int_q[3], 19); + if (!(x inside {int_q})) `stop; + endtask + + task rand_with_dyn(); + int ok; + ok = std::randomize(x) with {x inside {dyn};}; + `checkd(ok, 1); + `checkd(dyn.size(), 3); + `checkd(dyn[0], 21); + `checkd(dyn[1], 22); + `checkd(dyn[2], 23); + if (!(x inside {dyn})) `stop; + endtask +endclass + +module t; + Cls obj; + + initial begin + obj = new(); + repeat (20) obj.rand_with_queue(); + repeat (20) obj.rand_with_dyn(); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_trace_vif_class_clk.py b/test_regress/t/t_trace_vif_class_clk.py new file mode 100755 index 000000000..33e5bb7a9 --- /dev/null +++ b/test_regress/t/t_trace_vif_class_clk.py @@ -0,0 +1,22 @@ +#!/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 --trace-vcd --timing']) + +test.execute() + +# Expect 5 posedges and 5 low samples in VCD for the class-driven interface clock. +test.file_grep_count(test.trace_filename, r'(?m)^1[!-~]$', 5) +test.file_grep_count(test.trace_filename, r'(?m)^0[!-~]$', 5) + +test.passes() diff --git a/test_regress/t/t_trace_vif_class_clk.v b/test_regress/t/t_trace_vif_class_clk.v new file mode 100644 index 000000000..6679b0ba9 --- /dev/null +++ b/test_regress/t/t_trace_vif_class_clk.v @@ -0,0 +1,45 @@ +// 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 + +`timescale 1ns/1ns +`define STRINGIFY(x) `"x`" + +interface clk_iface; + bit clk; +endinterface + +class clk_driver; + virtual clk_iface vif; + function new(virtual clk_iface vif); + this.vif = vif; + endfunction + + task run(); + vif.clk = 1'b0; + forever #5 vif.clk = ~vif.clk; + endtask +endclass + +module t; + clk_iface ci(); + clk_driver drv; + + int x = 0; + always @(posedge ci.clk) x = x + 1; + + initial begin + drv = new(ci); + drv.run(); + end + + initial begin + $dumpfile(`STRINGIFY(`TEST_DUMPFILE)); + $dumpvars(0, t); + repeat (5) @(posedge ci.clk); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_trace_vif_class_clk_multi.py b/test_regress/t/t_trace_vif_class_clk_multi.py new file mode 100755 index 000000000..47372212b --- /dev/null +++ b/test_regress/t/t_trace_vif_class_clk_multi.py @@ -0,0 +1,22 @@ +#!/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 --trace-vcd --timing']) + +test.execute() + +# Two class-driven clocks toggle in lockstep: 10 highs and 10 lows total. +test.file_grep_count(test.trace_filename, r'(?m)^1[!-~]$', 10) +test.file_grep_count(test.trace_filename, r'(?m)^0[!-~]$', 10) + +test.passes() diff --git a/test_regress/t/t_trace_vif_class_clk_multi.v b/test_regress/t/t_trace_vif_class_clk_multi.v new file mode 100644 index 000000000..716d88fed --- /dev/null +++ b/test_regress/t/t_trace_vif_class_clk_multi.v @@ -0,0 +1,53 @@ +// 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 + +`timescale 1ns/1ns +`define STRINGIFY(x) `"x`" + +interface clk_iface; + bit clk; +endinterface + +class clk_driver; + virtual clk_iface vif; + function new(virtual clk_iface vif); + this.vif = vif; + endfunction + + task run(); + vif.clk = 1'b0; + forever #5 vif.clk = ~vif.clk; + endtask +endclass + +module t; + clk_iface ci0(); + clk_iface ci1(); + clk_driver drv0; + clk_driver drv1; + + int x0 = 0; + int x1 = 0; + always @(posedge ci0.clk) x0 = x0 + 1; + always @(posedge ci1.clk) x1 = x1 + 1; + + initial begin + drv0 = new(ci0); + drv1 = new(ci1); + fork + drv0.run(); + drv1.run(); + join_none + end + + initial begin + $dumpfile(`STRINGIFY(`TEST_DUMPFILE)); + $dumpvars(0, t); + repeat (5) @(posedge ci0.clk); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_verilated_all.v b/test_regress/t/t_verilated_all.v index 2d706bdd6..b9bc4ff61 100644 --- a/test_regress/t/t_verilated_all.v +++ b/test_regress/t/t_verilated_all.v @@ -17,7 +17,7 @@ module t ( int cyc; integer rand_result; integer seed = 123; - + integer frc; always @(posedge clk) begin cyc <= cyc + 1; if (cyc != 0) begin @@ -27,6 +27,7 @@ module t ( c = new; rand_result = c.randomize(); $display("rand: %x x: %x ", rand_result, c.x); // Get verilated_random.cpp + force frc=42; // Get verilated_force.h $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_vlcov_fsm_report.out b/test_regress/t/t_vlcov_fsm_report.out new file mode 100644 index 000000000..ab2a70ea6 --- /dev/null +++ b/test_regress/t/t_vlcov_fsm_report.out @@ -0,0 +1,102 @@ +// // verilator_coverage annotation + // DESCRIPTION: Verilator: FSM reporting coverage test + // + // This file ONLY is placed under the Creative Commons Public Domain. + // SPDX-FileCopyrightText: 2026 Wilson Snyder + // SPDX-License-Identifier: CC0-1.0 + + module t ( +%000007 input clk + ); + + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2, + S3 = 2'd3 + } state_t; + + integer cyc; +%000001 logic rst; +%000001 logic start; +%000003 state_t state_default /*verilator fsm_arc_include_cond*/; +%000001 state_t state_reset_incl /*verilator fsm_reset_arc*/; +%000001 state_t state_reset_excl; + +%000001 initial begin +%000001 rst = 1'b1; +%000001 start = 1'b0; +%000001 cyc = 0; + end + +%000007 always @(posedge clk) begin +%000007 cyc <= cyc + 1; +%000006 if (cyc == 1) rst <= 1'b0; +%000006 if (cyc == 2) start <= 1'b1; +%000006 if (cyc == 3) start <= 1'b0; +%000006 if (cyc == 6) begin +%000001 $write("*-* All Finished *-*\n"); +%000001 $finish; + end + end + + // This FSM gives the reporting path both ordinary arcs and a synthetic + // default arc so annotate/write-info exercise FSM-arc filtering. +%000007 always_ff @(posedge clk) begin +%000005 if (rst) begin +%000002 state_default <= S0; +%000005 end else begin +%000005 case (state_default) + // [FSM coverage] +%000001 // [fsm_arc t.state_default::ANY->S0[reset]] [reset arc, excluded from %] +%000000 // [SYNTHETIC DEFAULT ARC: t.state_default::default->S0] +%000002 // [fsm_state t.state_default::S0] +%000000 // [fsm_state t.state_default::S1] *** UNCOVERED *** +%000003 // [fsm_state t.state_default::S2] +%000000 // [fsm_state t.state_default::S3] *** UNCOVERED *** +%000003 S0: if (start) state_default <= S1; else state_default <= S2; +%000002 default: state_default <= S0; + endcase + end + end + + // These two FSMs give reporting both reset-include and reset-exclude arcs so + // annotate can exercise the reset-arc filtering wording in both modes. +%000007 always_ff @(posedge clk) begin +%000005 if (rst) begin +%000002 state_reset_incl <= S0; +%000005 end else begin +%000005 case (state_reset_incl) + // [FSM coverage] +%000001 // [fsm_arc t.state_reset_incl::ANY->S0[reset_include]] [reset arc, excluded from %] +%000001 // [fsm_arc t.state_reset_incl::S0->S1] +%000000 // [fsm_state t.state_reset_incl::S0] *** UNCOVERED *** +%000001 // [fsm_state t.state_reset_incl::S1] +%000000 // [fsm_state t.state_reset_incl::S2] *** UNCOVERED *** +%000000 // [fsm_state t.state_reset_incl::S3] *** UNCOVERED *** +%000001 S0: state_reset_incl <= S1; +%000004 default: state_reset_incl <= S1; + endcase + end + end + +%000007 always_ff @(posedge clk) begin +%000005 if (rst) begin +%000002 state_reset_excl <= S0; +%000005 end else begin +%000005 case (state_reset_excl) + // [FSM coverage] +%000001 // [fsm_arc t.state_reset_excl::ANY->S0[reset]] [reset arc, excluded from %] +%000001 // [fsm_arc t.state_reset_excl::S0->S1] +%000000 // [fsm_state t.state_reset_excl::S0] *** UNCOVERED *** +%000001 // [fsm_state t.state_reset_excl::S1] +%000000 // [fsm_state t.state_reset_excl::S2] *** UNCOVERED *** +%000000 // [fsm_state t.state_reset_excl::S3] *** UNCOVERED *** +%000001 S0: state_reset_excl <= S1; +%000004 default: state_reset_excl <= S1; + endcase + end + end + + endmodule + diff --git a/test_regress/t/t_vlcov_fsm_report.py b/test_regress/t/t_vlcov_fsm_report.py new file mode 100755 index 000000000..ccfc706ca --- /dev/null +++ b/test_regress/t/t_vlcov_fsm_report.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM reporting coverage 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') + +# This regression targets the reporting side of FSM coverage rather than the +# detector itself. The generated coverage.dat contains state points, ordinary +# arcs, default arcs, reset arcs, and reset-include arcs so verilator_coverage +# exercises the FSM-specific filtering and annotation code paths. +test.compile(verilator_flags2=['--cc --coverage']) + +test.execute() + +test.run(cmd=[ + os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage", + "--write-info", + test.obj_dir + "/coverage.info", + test.obj_dir + "/coverage.dat", +], + verilator_run=True) + +test.file_grep(test.obj_dir + "/coverage.info", r"TN:verilator_coverage") +test.file_grep(test.obj_dir + "/coverage.info", r"BRF:") +test.file_grep(test.obj_dir + "/coverage.info", r"BRH:") + +test.run(cmd=[os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage", + "--annotate", test.obj_dir + "/annotated", + test.obj_dir + "/coverage.dat"], + verilator_run=True) # yapf:disable + +test.run(cmd=[os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage", + "--include-reset-arcs", + "--annotate", test.obj_dir + "/annotated-incl", + test.obj_dir + "/coverage.dat"], + verilator_run=True) # yapf:disable + +annotated = test.obj_dir + "/annotated/t_vlcov_fsm_report.v" +annotated_incl = test.obj_dir + "/annotated-incl/t_vlcov_fsm_report.v" + +test.files_identical(annotated, "t/t_vlcov_fsm_report.out") +test.files_identical(annotated_incl, "t/t_vlcov_fsm_report_incl.out") + +test.passes() diff --git a/test_regress/t/t_vlcov_fsm_report.v b/test_regress/t/t_vlcov_fsm_report.v new file mode 100644 index 000000000..a2c3498bb --- /dev/null +++ b/test_regress/t/t_vlcov_fsm_report.v @@ -0,0 +1,79 @@ +// DESCRIPTION: Verilator: FSM reporting coverage test +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk +); + + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2, + S3 = 2'd3 + } state_t; + + integer cyc; + logic rst; + logic start; + state_t state_default /*verilator fsm_arc_include_cond*/; + state_t state_reset_incl /*verilator fsm_reset_arc*/; + state_t state_reset_excl; + + 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 == 6) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + // This FSM gives the reporting path both ordinary arcs and a synthetic + // default arc so annotate/write-info exercise FSM-arc filtering. + always_ff @(posedge clk) begin + if (rst) begin + state_default <= S0; + end else begin + case (state_default) + S0: if (start) state_default <= S1; else state_default <= S2; + default: state_default <= S0; + endcase + end + end + + // These two FSMs give reporting both reset-include and reset-exclude arcs so + // annotate can exercise the reset-arc filtering wording in both modes. + always_ff @(posedge clk) begin + if (rst) begin + state_reset_incl <= S0; + end else begin + case (state_reset_incl) + S0: state_reset_incl <= S1; + default: state_reset_incl <= S1; + endcase + end + end + + always_ff @(posedge clk) begin + if (rst) begin + state_reset_excl <= S0; + end else begin + case (state_reset_excl) + S0: state_reset_excl <= S1; + default: state_reset_excl <= S1; + endcase + end + end + +endmodule diff --git a/test_regress/t/t_vlcov_fsm_report_incl.out b/test_regress/t/t_vlcov_fsm_report_incl.out new file mode 100644 index 000000000..a9a1c89d8 --- /dev/null +++ b/test_regress/t/t_vlcov_fsm_report_incl.out @@ -0,0 +1,102 @@ +// // verilator_coverage annotation + // DESCRIPTION: Verilator: FSM reporting coverage test + // + // This file ONLY is placed under the Creative Commons Public Domain. + // SPDX-FileCopyrightText: 2026 Wilson Snyder + // SPDX-License-Identifier: CC0-1.0 + + module t ( +%000007 input clk + ); + + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2, + S3 = 2'd3 + } state_t; + + integer cyc; +%000001 logic rst; +%000001 logic start; +%000003 state_t state_default /*verilator fsm_arc_include_cond*/; +%000001 state_t state_reset_incl /*verilator fsm_reset_arc*/; +%000001 state_t state_reset_excl; + +%000001 initial begin +%000001 rst = 1'b1; +%000001 start = 1'b0; +%000001 cyc = 0; + end + +%000007 always @(posedge clk) begin +%000007 cyc <= cyc + 1; +%000006 if (cyc == 1) rst <= 1'b0; +%000006 if (cyc == 2) start <= 1'b1; +%000006 if (cyc == 3) start <= 1'b0; +%000006 if (cyc == 6) begin +%000001 $write("*-* All Finished *-*\n"); +%000001 $finish; + end + end + + // This FSM gives the reporting path both ordinary arcs and a synthetic + // default arc so annotate/write-info exercise FSM-arc filtering. +%000007 always_ff @(posedge clk) begin +%000005 if (rst) begin +%000002 state_default <= S0; +%000005 end else begin +%000005 case (state_default) + // [FSM coverage] +%000001 // [fsm_arc t.state_default::ANY->S0[reset]] +%000000 // [SYNTHETIC DEFAULT ARC: t.state_default::default->S0] +%000002 // [fsm_state t.state_default::S0] +%000000 // [fsm_state t.state_default::S1] *** UNCOVERED *** +%000003 // [fsm_state t.state_default::S2] +%000000 // [fsm_state t.state_default::S3] *** UNCOVERED *** +%000003 S0: if (start) state_default <= S1; else state_default <= S2; +%000002 default: state_default <= S0; + endcase + end + end + + // These two FSMs give reporting both reset-include and reset-exclude arcs so + // annotate can exercise the reset-arc filtering wording in both modes. +%000007 always_ff @(posedge clk) begin +%000005 if (rst) begin +%000002 state_reset_incl <= S0; +%000005 end else begin +%000005 case (state_reset_incl) + // [FSM coverage] +%000001 // [fsm_arc t.state_reset_incl::ANY->S0[reset_include]] +%000001 // [fsm_arc t.state_reset_incl::S0->S1] +%000000 // [fsm_state t.state_reset_incl::S0] *** UNCOVERED *** +%000001 // [fsm_state t.state_reset_incl::S1] +%000000 // [fsm_state t.state_reset_incl::S2] *** UNCOVERED *** +%000000 // [fsm_state t.state_reset_incl::S3] *** UNCOVERED *** +%000001 S0: state_reset_incl <= S1; +%000004 default: state_reset_incl <= S1; + endcase + end + end + +%000007 always_ff @(posedge clk) begin +%000005 if (rst) begin +%000002 state_reset_excl <= S0; +%000005 end else begin +%000005 case (state_reset_excl) + // [FSM coverage] +%000001 // [fsm_arc t.state_reset_excl::ANY->S0[reset]] +%000001 // [fsm_arc t.state_reset_excl::S0->S1] +%000000 // [fsm_state t.state_reset_excl::S0] *** UNCOVERED *** +%000001 // [fsm_state t.state_reset_excl::S1] +%000000 // [fsm_state t.state_reset_excl::S2] *** UNCOVERED *** +%000000 // [fsm_state t.state_reset_excl::S3] *** UNCOVERED *** +%000001 S0: state_reset_excl <= S1; +%000004 default: state_reset_excl <= S1; + endcase + end + end + + endmodule + diff --git a/test_regress/t/t_vlcov_summary_typed.out b/test_regress/t/t_vlcov_summary_typed.out new file mode 100644 index 000000000..a059cd732 --- /dev/null +++ b/test_regress/t/t_vlcov_summary_typed.out @@ -0,0 +1,5 @@ +Coverage Summary: + line : 88.6% ( 39/ 44) + toggle : 33.3% ( 35/105) + branch : 78.1% ( 50/ 64) + expr : 66.7% ( 8/ 12) diff --git a/test_regress/t/t_vlcov_summary_typed.py b/test_regress/t/t_vlcov_summary_typed.py new file mode 100755 index 000000000..41579145f --- /dev/null +++ b/test_regress/t/t_vlcov_summary_typed.py @@ -0,0 +1,24 @@ +#!/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('dist') + +test.run(cmd=[ + os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage", + "t/t_vlcov_data_e.dat", +], + logfile=test.obj_dir + "/vlcov.log", + tee=False, + verilator_run=True) + +test.files_identical(test.obj_dir + "/vlcov.log", test.golden_filename) + +test.passes() diff --git a/test_regress/t/trace_hier_block_common.py b/test_regress/t/trace_hier_block_common.py index c92175c61..e634b5e28 100644 --- a/test_regress/t/trace_hier_block_common.py +++ b/test_regress/t/trace_hier_block_common.py @@ -93,8 +93,7 @@ def run(test, *, verilator_flags2=()): if "enddefinitions" in la: break - # The two models must match ignoring enum attributes which can differ - test.trace_identical(trace_hier, trace_nonh, ignore_attr=True) + test.trace_identical(trace_hier, trace_nonh) # The hierarchical must match the reference test.trace_identical(trace_hier, test.golden_filename) diff --git a/test_regress/t/trace_lib_common.py b/test_regress/t/trace_lib_common.py index 785cf38cb..dc99a1867 100644 --- a/test_regress/t/trace_lib_common.py +++ b/test_regress/t/trace_lib_common.py @@ -107,8 +107,7 @@ def run(test, *, verilator_flags2=()): if "enddefinitions" in la: break - # The two models must match ignoring enum attributes which can differ - test.trace_identical(trace_libs, trace_nonl, ignore_attr=True) + test.trace_identical(trace_libs, trace_nonl) # The --lib-create must match the reference test.trace_identical(trace_libs, test.golden_filename) diff --git a/test_regress/t/trace_struct_alias_common.py b/test_regress/t/trace_struct_alias_common.py index e5e418f68..df38a1266 100644 --- a/test_regress/t/trace_struct_alias_common.py +++ b/test_regress/t/trace_struct_alias_common.py @@ -7,6 +7,55 @@ # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +def _vcd_extract_codes(filename): + """Parse VCD header and return dict mapping 'scope.signal' -> code.""" + codes = {} + scope_stack = [] + with open(filename, 'r', encoding='latin-1') as fh: + for line in fh: + line = line.strip() + if line.startswith("$scope"): + parts = line.split() + # $scope $end + scope_stack.append(parts[2]) + elif line.startswith("$var"): + parts = line.split() + # $var ... $end + code = parts[3] + name = parts[4] + full = '.'.join(scope_stack) + '.' + name + codes[full] = code + elif line.startswith("$upscope"): + scope_stack.pop() + elif line.startswith("$enddefinitions"): + break + return codes + + +def _check_aliased(test, codes, sig_a, sig_b): + code_a = codes.get(sig_a) + code_b = codes.get(sig_b) + if code_a is None: + test.error(f"Signal '{sig_a}' not found in VCD") + if code_b is None: + test.error(f"Signal '{sig_b}' not found in VCD") + if code_a != code_b: + test.error(f"Expected '{sig_a}' (code {code_a}) to alias " + f"'{sig_b}' (code {code_b})") + + +def _check_not_aliased(test, codes, sig_a, sig_b): + code_a = codes.get(sig_a) + code_b = codes.get(sig_b) + if code_a is None: + test.error(f"Signal '{sig_a}' not found in VCD") + if code_b is None: + test.error(f"Signal '{sig_b}' not found in VCD") + if code_a == code_b: + test.error(f"Expected '{sig_a}' and '{sig_b}' to have different codes, " + f"both have code {code_a}") + + def run(test): (fmt, ) = test.parse_name(r"t_trace_struct_alias_([a-z]+)") @@ -20,16 +69,16 @@ def run(test): test.execute() if fmt == "vcd": - codes = test.vcd_extract_codes(test.trace_filename) + codes = _vcd_extract_codes(test.trace_filename) - test.vcd_check_not_aliased(codes, "top.t.s1.a", "top.t.s2.a") + _check_not_aliased(test, codes, "top.t.s1.a", "top.t.s2.a") - test.vcd_check_aliased(codes, "top.t.s3.a", "top.t.alias_of_s3a") + _check_aliased(test, codes, "top.t.s3.a", "top.t.alias_of_s3a") - test.vcd_check_aliased(codes, "top.t.s4.a", "top.t.s5.a") - test.vcd_check_aliased(codes, "top.t.s4.b", "top.t.s5.b") + _check_aliased(test, codes, "top.t.s4.a", "top.t.s5.a") + _check_aliased(test, codes, "top.t.s4.b", "top.t.s5.b") - test.vcd_check_aliased(codes, "top.t.s6.a", "top.t.source_val") + _check_aliased(test, codes, "top.t.s6.a", "top.t.source_val") test.trace_identical(test.trace_filename, test.golden_filename) diff --git a/test_regress/t/trace_struct_array_multi_inst_common.py b/test_regress/t/trace_struct_array_multi_inst_common.py index e22d1f83c..937d399ff 100644 --- a/test_regress/t/trace_struct_array_multi_inst_common.py +++ b/test_regress/t/trace_struct_array_multi_inst_common.py @@ -74,10 +74,8 @@ def run(test): test.trace_identical(test.trace_filename, test.golden_filename) - if fmt == "fst": - _check_empty_scopes_vcd(test, test.trace_filename + ".vcd") - elif fmt == "vcd": - _check_empty_scopes_vcd(test, test.trace_filename) + if fmt in ("fst", "vcd"): + _check_empty_scopes_vcd(test, test.golden_filename) elif fmt == "saif": _check_empty_scopes_saif(test, test.trace_filename)