feat(lint): add SYNTHUNPACKED warning for unpacked array ports

Add a new warning code SYNTHUNPACKED (disabled by default) that fires
when a module IO port uses an unpacked array. Synthesis tools accept
the construct but typically flatten the unpacked dimensions to packed
bits in the netlist, so the post-synthesis port signature no longer
matches the pre-synthesis source. A testbench that drove the RTL
through such ports cannot connect to the netlist without modification,
making gate-level simulation (GLS) harder.

This commit:

- Adds SYNTHUNPACKED to V3ErrorCode enum and names[] (alphabetical)
- Adds SYNTHUNPACKED to defaultsOff()
- Emits the warning in V3Width::visit(AstVar*) for non-interface IO
  ports whose dtype is an UnpackArrayDType
- Documents the warning in docs/guide/warnings.rst
- Adds a lint regression test (t_lint_synthunpacked_bad)

Signed-off-by: Lucas Amaral <lucaaamaral@gmail.com>
This commit is contained in:
Lucas Amaral 2026-05-20 11:40:13 -03:00
parent c5798f902b
commit 821e86eb98
6 changed files with 77 additions and 6 deletions

View File

@ -2141,6 +2141,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

View File

@ -167,6 +167,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
@ -237,16 +238,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); }

View File

@ -2983,6 +2983,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);
}

View File

@ -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

View File

@ -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()

View File

@ -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