diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index 880b5608a..c85c53b87 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -325,3 +325,4 @@ Yogish Sekhar 24bit-xjkp Zubin Jain Muzaffer Kal +Lucas Amaral diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index d1818afd5..70096d135 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -2191,6 +2191,34 @@ List Of Warnings correctly. +.. option:: SYNTHUNPACKED + + Warns that a module IO port uses an unpacked array. Synthesis tools + (e.g., Yosys, Cadence Genus, Synopsys Design Compiler) accept the + construct but typically flatten the unpacked dimensions to packed bits + in the netlist. The resulting post-synthesis port signature no longer + matches the pre-synthesis source, so a testbench that drove the RTL + through these ports cannot connect to the netlist without modification, + making gate-level simulation (GLS) harder. + + For example: + + .. code-block:: sv + + module dut(input wire [7:0] data [0:3]); // Will get SYNTHUNPACKED + ... + endmodule + + To keep the pre- and post-synthesis port signatures aligned, flatten + the port to a packed vector at the module boundary, or wrap the array + in a packed struct. + + Disabled by default since not all flows go through synthesis. Enable + with ``-Wwarn-SYNTHUNPACKED`` to surface it. Ignoring this warning does + not affect Verilator simulation; it only flags a downstream GLS + compatibility hazard. + + .. option:: TASKNSVAR Error when a call to a task or function has an inout from that task tied diff --git a/src/V3Error.h b/src/V3Error.h index 6d4914329..8dc7ef0b5 100644 --- a/src/V3Error.h +++ b/src/V3Error.h @@ -168,6 +168,7 @@ public: SUPERNFIRST, // Super.new must be first statement SYMRSVDWORD, // Symbol is Reserved Word SYNCASYNCNET, // Mixed sync + async reset + SYNTHUNPACKED, // Unpacked array on IO port (synth packs these, breaking GLS) TICKCOUNT, // Too large tick count TIMESCALEMOD, // Need timescale for module UNDRIVEN, // No drivers @@ -238,16 +239,17 @@ public: "PROCASSWIRE", "PROFOUTOFDATE", "PROTECTED", "PROTOTYPEMIS", "RANDC", "REALCVT", "REDEFMACRO", "RISEFALLDLY", "SELRANGE", "SHORTREAL", "SIDEEFFECT", "SPECIFYIGN", "SPLITVAR", "STATICVAR", "STMTDLY", "SUPERNFIRST", "SYMRSVDWORD", "SYNCASYNCNET", - "TICKCOUNT", "TIMESCALEMOD", "UNDRIVEN", "UNOPT", "UNOPTFLAT", "UNOPTTHREADS", - "UNPACKED", "UNSATCONSTR", "UNSIGNED", "UNUSED", "UNUSEDGENVAR", "UNUSEDLOOP", - "UNUSEDPARAM", "UNUSEDSIGNAL", "USERERROR", "USERFATAL", "USERINFO", "USERWARN", - "VARHIDDEN", "WAITCONST", "WIDTH", "WIDTHCONCAT", "WIDTHEXPAND", "WIDTHTRUNC", - "WIDTHXZEXPAND", "ZERODLY", "ZEROREPL", " MAX"}; + "SYNTHUNPACKED", "TICKCOUNT", "TIMESCALEMOD", "UNDRIVEN", "UNOPT", "UNOPTFLAT", + "UNOPTTHREADS", "UNPACKED", "UNSATCONSTR", "UNSIGNED", "UNUSED", "UNUSEDGENVAR", + "UNUSEDLOOP", "UNUSEDPARAM", "UNUSEDSIGNAL", "USERERROR", "USERFATAL", "USERINFO", + "USERWARN", "VARHIDDEN", "WAITCONST", "WIDTH", "WIDTHCONCAT", "WIDTHEXPAND", + "WIDTHTRUNC", "WIDTHXZEXPAND", "ZERODLY", "ZEROREPL", " MAX"}; return names[m_e]; } // Warnings that default to off bool defaultsOff() const VL_MT_SAFE { - return (m_e == IMPERFECTSCH || m_e == I_CELLDEFINE || styleError()); + return (m_e == IMPERFECTSCH || m_e == I_CELLDEFINE || m_e == SYNTHUNPACKED + || styleError()); } // Warnings that warn about nasty side effects bool dangerous() const VL_MT_SAFE { return (m_e == COMBDLY); } diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 5a24539b2..ff47dfce0 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -3045,6 +3045,19 @@ class WidthVisitor final : public VNVisitor { userIterateAndNext(nodep->delayp(), WidthVP{nodep->dtypep(), PRELIM}.p()); UINFO(4, "varWidthed " << nodep); // UINFOTREE(1, nodep, "", "InitOut"); + // Warn about unpacked arrays on module IO ports. Synthesis tools + // typically flatten these to packed bits in the netlist, which breaks + // pre-synthesis testbench connectivity for gate-level simulation. + if (nodep->isIO() && !nodep->isIfaceRef()) { + if (VN_IS(nodep->dtypep()->skipRefp(), UnpackArrayDType)) { + nodep->v3warn(SYNTHUNPACKED, + "Unpacked array on port " + << nodep->prettyNameQ() + << " is typically flattened to packed bits by synthesis," + " breaking pre-synthesis testbench connectivity for" + " gate-level simulation"); + } + } nodep->didWidth(true); nodep->doingWidth(false); } diff --git a/test_regress/t/t_lint_synthunpacked_bad.out b/test_regress/t/t_lint_synthunpacked_bad.out new file mode 100644 index 000000000..29c268ce1 --- /dev/null +++ b/test_regress/t/t_lint_synthunpacked_bad.out @@ -0,0 +1,7 @@ +%Warning-SYNTHUNPACKED: t/t_lint_synthunpacked_bad.v:6:28: Unpacked array on port 'data' is typically flattened to packed bits by synthesis, breaking pre-synthesis testbench connectivity for gate-level simulation + : ... note: In instance 't' + 6 | module t (input wire [7:0] data [0:3]); + | ^~~~ + ... For warning description see https://verilator.org/warn/SYNTHUNPACKED?v=latest + ... Use "/* verilator lint_off SYNTHUNPACKED */" and lint_on around source to disable this message. +%Error: Exiting due to diff --git a/test_regress/t/t_lint_synthunpacked_bad.py b/test_regress/t/t_lint_synthunpacked_bad.py new file mode 100755 index 000000000..93b50b562 --- /dev/null +++ b/test_regress/t/t_lint_synthunpacked_bad.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Lint test for SYNTHUNPACKED warning +# +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: CC0-1.0 + +import vltest_bootstrap + +test.scenarios('linter') + +test.lint(fails=True, v_flags2=['-Wwarn-SYNTHUNPACKED'], expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_lint_synthunpacked_bad.v b/test_regress/t/t_lint_synthunpacked_bad.v new file mode 100644 index 000000000..e59dc9f8c --- /dev/null +++ b/test_regress/t/t_lint_synthunpacked_bad.v @@ -0,0 +1,8 @@ +// DESCRIPTION: Verilator: Test of SYNTHUNPACKED warning on unpacked port +// +// SPDX-FileCopyrightText: 2026 Lucas Amaral +// SPDX-License-Identifier: CC0-1.0 + +module t (input wire [7:0] data [0:3]); + initial $display("data[0]=%h", data[0]); +endmodule