diff --git a/src/V3Width.cpp b/src/V3Width.cpp index d34252725..a4a8c0c2d 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -241,6 +241,7 @@ class WidthVisitor final : public VNVisitor { std::map m_containingClassp; // Containing class cache for containingClass() function std::unordered_set m_aliasedVars; // Variables referenced in alias + std::unordered_set m_curModVars; // Variables declared in current module static constexpr int ENUM_LOOKUP_BITS = 16; // Maximum # bits to make enum lookup table @@ -2835,7 +2836,18 @@ class WidthVisitor final : public VNVisitor { && (!m_ftaskp || !m_ftaskp->isConstructor()) && !VN_IS(m_procedurep, InitialAutomatic) && !VN_IS(m_procedurep, InitialStatic) && !VN_IS(nodep->abovep(), AssignForce) && !VN_IS(nodep->abovep(), Release)) { - nodep->v3warn(ASSIGNIN, "Assigning to input/const variable: " << nodep->prettyNameQ()); + // Skip ASSIGNIN for continuous assignments to net-type input ports + // via hierarchical reference. Net ports allow multiple continuous + // drivers (IEEE 1800-2023 23.3.3.3). Input ports default to net + // when port kind is omitted (23.2.2.3, PORT type). + const bool hierRef = !m_curModVars.count(nodep->varp()); + const bool netPort + = nodep->varp()->isNet() || nodep->varp()->varType() == VVarType::PORT; + const bool contAssign = VN_IS(nodep->abovep(), AssignW); + if (!(hierRef && netPort && contAssign)) { + nodep->v3warn(ASSIGNIN, + "Assigning to input/const variable: " << nodep->prettyNameQ()); + } } else if (nodep->access().isWriteOrRW() && nodep->varp()->isConst() && !m_paramsOnly && (!m_ftaskp || !m_ftaskp->isConstructor()) && !VN_IS(m_procedurep, InitialAutomatic) @@ -7374,6 +7386,9 @@ class WidthVisitor final : public VNVisitor { } else { VL_RESTORER(m_modep); m_modep = nodep; + // Collect local variables for ASSIGNIN hierarchical reference check + m_curModVars.clear(); + nodep->foreach([this](const AstVar* varp) { m_curModVars.insert(varp); }); userIterateChildren(nodep, nullptr); } } diff --git a/src/verilog.y b/src/verilog.y index cb94e8531..71454ffbb 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -1437,16 +1437,16 @@ port: // ==IEEE: port { $$ = $3; VARDTYPE($2); VARIOANSI(); if (AstVar* vp = VARDONEP($$, $4, $5)) { addNextNull($$, vp); vp->valuep($7); } } | portDirNetE yVAR data_type portSig variable_dimensionListE sigAttrListE - { $$ = $4; VARDTYPE($3); VARIOANSI(); + { $$ = $4; VARDECL(VAR); VARDTYPE($3); VARIOANSI(); addNextNull($$, VARDONEP($$, $5, $6)); } | portDirNetE yVAR data_type portSig variable_dimensionListE sigAttrListE '=' constExpr - { $$ = $4; VARDTYPE($3); VARIOANSI(); + { $$ = $4; VARDECL(VAR); VARDTYPE($3); VARIOANSI(); if (AstVar* vp = VARDONEP($$, $5, $6)) { addNextNull($$, vp); vp->valuep($8); } } | portDirNetE yVAR implicit_typeE portSig variable_dimensionListE sigAttrListE - { $$ = $4; VARDTYPE($3); VARIOANSI(); + { $$ = $4; VARDECL(VAR); VARDTYPE($3); VARIOANSI(); addNextNull($$, VARDONEP($$, $5, $6)); } | portDirNetE yVAR implicit_typeE portSig variable_dimensionListE sigAttrListE '=' constExpr - { $$ = $4; VARDTYPE($3); VARIOANSI(); + { $$ = $4; VARDECL(VAR); VARDTYPE($3); VARIOANSI(); if (AstVar* vp = VARDONEP($$, $5, $6)) { addNextNull($$, vp); vp->valuep($8); } } | portDirNetE signing portSig variable_dimensionListE sigAttrListE { $$ = $3; @@ -1955,10 +1955,10 @@ port_declaration: // ==IEEE: port_declaration /*mid*/ { VARDTYPE($3); } /*cont*/ list_of_variable_decl_assignments { $$ = $5; } | port_directionReset port_declNetE yVAR data_type - /*mid*/ { VARDTYPE($4); } + /*mid*/ { VARDECL(VAR); VARDTYPE($4); } /*cont*/ list_of_variable_decl_assignments { $$ = $6; } | port_directionReset port_declNetE yVAR implicit_typeE - /*mid*/ { VARDTYPE($4); } + /*mid*/ { VARDECL(VAR); VARDTYPE($4); } /*cont*/ list_of_variable_decl_assignments { $$ = $6; } | port_directionReset port_declNetE signingE rangeList /*mid*/ { AstNodeDType* const dtp = GRAMMARP->addRange( diff --git a/test_regress/t/t_interface_input_port_assign.py b/test_regress/t/t_interface_input_port_assign.py new file mode 100755 index 000000000..1ddad07d5 --- /dev/null +++ b/test_regress/t/t_interface_input_port_assign.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(timing_loop=True, verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_input_port_assign.v b/test_regress/t/t_interface_input_port_assign.v new file mode 100644 index 000000000..3c40f5788 --- /dev/null +++ b/test_regress/t/t_interface_input_port_assign.v @@ -0,0 +1,68 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +// Driving input ports from the instantiating scope via continuous assign +// is legal when port kind defaults to net (IEEE 1800-2023 23.2.2.3). +// All three forms below default to net for input ports. + +// Scenario 1: bare input (defaults to net) +interface bare_if (input clk); + logic data; +endinterface + +// Scenario 2: input with explicit data type (still net for input) +interface logic_if (input logic clk); + logic data; +endinterface + +// Scenario 3: input with explicit net kind +interface wire_if (input wire clk); + logic data; +endinterface + +module consumer (bare_if cif); + logic sampled; + always @(posedge cif.clk) sampled <= cif.data; +endmodule + +module t; + logic clk = 0; + always #5 clk = ~clk; + integer cyc = 0; + + bare_if bif(.clk()); + assign bif.clk = clk; + assign bif.data = 1'b1; + + logic_if lif(.clk()); + assign lif.clk = clk; + assign lif.data = 1'b1; + + wire_if wif(.clk()); + assign wif.clk = clk; + assign wif.data = 1'b1; + + consumer cons(.cif(bif)); + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 10) begin + `checkh(bif.clk, clk); + `checkh(bif.data, 1'b1); + `checkh(lif.clk, clk); + `checkh(lif.data, 1'b1); + `checkh(wif.clk, clk); + `checkh(wif.data, 1'b1); + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_interface_input_port_assign_bad.out b/test_regress/t/t_interface_input_port_assign_bad.out new file mode 100644 index 000000000..92b0952f9 --- /dev/null +++ b/test_regress/t/t_interface_input_port_assign_bad.out @@ -0,0 +1,10 @@ +%Error-ASSIGNIN: t/t_interface_input_port_assign_bad.v:16:10: Assigning to input/const variable: 'clk' + : ... note: In instance 't' + 16 | assign clk = 1'b0; + | ^~~ + ... For error description see https://verilator.org/warn/ASSIGNIN?v=latest +%Error-ASSIGNIN: t/t_interface_input_port_assign_bad.v:23:14: Assigning to input/const variable: 'clk' + : ... note: In instance 't' + 23 | assign vif.clk = sig; + | ^~~ +%Error: Exiting due to diff --git a/test_regress/t/t_interface_input_port_assign_bad.py b/test_regress/t/t_interface_input_port_assign_bad.py new file mode 100755 index 000000000..a00127d05 --- /dev/null +++ b/test_regress/t/t_interface_input_port_assign_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=test.vlt_all, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_interface_input_port_assign_bad.v b/test_regress/t/t_interface_input_port_assign_bad.v new file mode 100644 index 000000000..485b41c8c --- /dev/null +++ b/test_regress/t/t_interface_input_port_assign_bad.v @@ -0,0 +1,28 @@ +// 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 + +// Case 1: Assigning to explicit var input port from OUTSIDE (illegal). +// Only 'input var' ports are variable kind (IEEE 1800-2023 23.2.2.3). +// Variable input ports cannot be assigned (IEEE 1800-2023 23.3.3.2). +interface var_if (input var logic clk); +endinterface + +// Case 2: Assigning to net-type input port from INSIDE (illegal). +// Internal assign creates a second driver within the port's own scope. +interface internal_if (input wire clk); + assign clk = 1'b0; // ASSIGNIN: internal assign to net input +endinterface + +module t; + logic sig; + internal_if iif(.clk(sig)); + var_if vif(.clk()); + assign vif.clk = sig; // ASSIGNIN: external assign to var input + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_interface_input_port_assign_noinl.py b/test_regress/t/t_interface_input_port_assign_noinl.py new file mode 100755 index 000000000..602304799 --- /dev/null +++ b/test_regress/t/t_interface_input_port_assign_noinl.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') +test.top_filename = "t/t_interface_input_port_assign.v" + +test.compile(timing_loop=True, verilator_flags2=['--timing'], v_flags2=['-fno-inline']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_var_port_json_only.out b/test_regress/t/t_var_port_json_only.out index bde4aead9..5a80ae68e 100644 --- a/test_regress/t/t_var_port_json_only.out +++ b/test_regress/t/t_var_port_json_only.out @@ -10,11 +10,11 @@ ]}, {"type":"MODULE","name":"mh6","addr":"(K)","loc":"d,26:8,26:11","origName":"mh6","verilogName":"mh6","level":1,"timeunit":"1ps","inlinesp": [], "stmtsp": [ - {"type":"VAR","name":"x_input_var_logic","addr":"(L)","loc":"d,26:23,26:40","dtypep":"(J)","origName":"x_input_var_logic","verilogName":"x_input_var_logic","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []} + {"type":"VAR","name":"x_input_var_logic","addr":"(L)","loc":"d,26:23,26:40","dtypep":"(J)","origName":"x_input_var_logic","verilogName":"x_input_var_logic","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []} ]}, {"type":"MODULE","name":"mh7","addr":"(M)","loc":"d,28:8,28:11","origName":"mh7","verilogName":"mh7","level":1,"timeunit":"1ps","inlinesp": [], "stmtsp": [ - {"type":"VAR","name":"x_input_var_integer","addr":"(N)","loc":"d,28:31,28:50","dtypep":"(G)","origName":"x_input_var_integer","verilogName":"x_input_var_integer","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"integer","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []} + {"type":"VAR","name":"x_input_var_integer","addr":"(N)","loc":"d,28:31,28:50","dtypep":"(G)","origName":"x_input_var_integer","verilogName":"x_input_var_integer","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"integer","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []} ]}, {"type":"MODULE","name":"mh8","addr":"(O)","loc":"d,30:8,30:11","origName":"mh8","verilogName":"mh8","level":1,"timeunit":"1ps","inlinesp": [], "stmtsp": [ @@ -22,7 +22,7 @@ ]}, {"type":"MODULE","name":"mh9","addr":"(Q)","loc":"d,32:8,32:11","origName":"mh9","verilogName":"mh9","level":1,"timeunit":"1ps","inlinesp": [], "stmtsp": [ - {"type":"VAR","name":"x_output_var_logic","addr":"(R)","loc":"d,32:24,32:42","dtypep":"(J)","origName":"x_output_var_logic","verilogName":"x_output_var_logic","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []} + {"type":"VAR","name":"x_output_var_logic","addr":"(R)","loc":"d,32:24,32:42","dtypep":"(J)","origName":"x_output_var_logic","verilogName":"x_output_var_logic","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []} ]}, {"type":"MODULE","name":"mh10","addr":"(S)","loc":"d,34:8,34:12","origName":"mh10","verilogName":"mh10","level":1,"timeunit":"1ps","inlinesp": [], "stmtsp": [ @@ -42,12 +42,12 @@ ]}, {"type":"MODULE","name":"mh17","addr":"(DB)","loc":"d,50:8,50:12","origName":"mh17","verilogName":"mh17","level":1,"timeunit":"1ps","inlinesp": [], "stmtsp": [ - {"type":"VAR","name":"x_input_var_integer","addr":"(EB)","loc":"d,50:31,50:50","dtypep":"(G)","origName":"x_input_var_integer","verilogName":"x_input_var_integer","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"integer","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"x_input_var_integer","addr":"(EB)","loc":"d,50:31,50:50","dtypep":"(G)","origName":"x_input_var_integer","verilogName":"x_input_var_integer","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"integer","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"y_input_wire_logic","addr":"(FB)","loc":"d,50:57,50:75","dtypep":"(J)","origName":"y_input_wire_logic","verilogName":"y_input_wire_logic","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []} ]}, {"type":"MODULE","name":"mh18","addr":"(GB)","loc":"d,52:8,52:12","origName":"mh18","verilogName":"mh18","level":1,"timeunit":"1ps","inlinesp": [], "stmtsp": [ - {"type":"VAR","name":"x_output_var_logic","addr":"(HB)","loc":"d,52:24,52:42","dtypep":"(J)","origName":"x_output_var_logic","verilogName":"x_output_var_logic","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"x_output_var_logic","addr":"(HB)","loc":"d,52:24,52:42","dtypep":"(J)","origName":"x_output_var_logic","verilogName":"x_output_var_logic","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"y_input_wire_logic","addr":"(IB)","loc":"d,52:50,52:68","dtypep":"(J)","origName":"y_input_wire_logic","verilogName":"y_input_wire_logic","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []} ]}, {"type":"MODULE","name":"mh19","addr":"(JB)","loc":"d,54:8,54:12","origName":"mh19","verilogName":"mh19","level":1,"timeunit":"1ps","inlinesp": [],