diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 614320e8e..66e822bdd 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -2424,17 +2424,23 @@ The grammar of control commands is as follows: .. option:: public [-module ""] [-task/-function ""] [-var ""] -.. option:: public_flat [-module ""] [-task/-function ""] [-var ""] +.. option:: public_flat [-module ""] [-task/-function ""] [(-param | -port | -var) ""] -.. option:: public_flat_rd [-module ""] [-task/-function ""] [-var ""] +.. option:: public_flat_rd [-module ""] [-task/-function ""] [(-param | -port | -var) ""] -.. option:: public_flat_rw [-module ""] [-task/-function ""] [-var ""] ["@(edge)"] +.. option:: public_flat_rw [-module ""] [-task/-function ""] [(-param | -port | -var) ""] ["@(edge)"] - Sets the variable to be public. Same as + Sets the specified signal to be public. Same as :option:`/*verilator&32;public*/` or :option:`/*verilator&32;public_flat*/`, etc., metacomments. See also :ref:`VPI Example`. + Using :code:`-port` only selects matching ports, :code:`-param` matches + parameters and localparams, and :code:`-var` matches any signal (including + ports, parameters, and regular variables or nets). In all three, the + following :code:`` can contain :code:`*` and :code:`?` wildcard + characters that match any substring or any single character respectively. + .. option:: sc_bv -module "" [-function ""] -var "" .. option:: sc_bv -module "" [-task ""] -var "" diff --git a/src/V3Control.cpp b/src/V3Control.cpp index 40367cd6b..fb0f88d09 100644 --- a/src/V3Control.cpp +++ b/src/V3Control.cpp @@ -176,6 +176,8 @@ public: // Function or task: Have variables and properties class V3ControlFTask final { + V3ControlVarResolver m_params; // Parameters in function/task + V3ControlVarResolver m_ports; // Ports in function/task V3ControlVarResolver m_vars; // Variables in function/task bool m_isolate = false; // Isolate function return bool m_noinline = false; // Don't inline function/task @@ -188,9 +190,13 @@ public: if (f.m_isolate) m_isolate = true; if (f.m_noinline) m_noinline = true; if (f.m_public) m_public = true; + m_params.update(f.m_params); + m_ports.update(f.m_ports); m_vars.update(f.m_vars); } + V3ControlVarResolver& params() { return m_params; } + V3ControlVarResolver& ports() { return m_ports; } V3ControlVarResolver& vars() { return m_vars; } void setIsolate(bool set) { m_isolate = set; } @@ -214,6 +220,8 @@ using V3ControlFTaskResolver = V3ControlWildcardResolver; class V3ControlModule final { V3ControlFTaskResolver m_tasks; // Functions/tasks in module + V3ControlVarResolver m_params; // Parameters in module + V3ControlVarResolver m_ports; // Ports in module V3ControlVarResolver m_vars; // Variables in module std::unordered_set m_coverageOffBlocks; // List of block names for coverage_off std::set m_modPragmas; // List of Pragmas for modules @@ -225,6 +233,8 @@ public: void update(const V3ControlModule& m) { m_tasks.update(m.m_tasks); + m_params.update(m.m_params); + m_ports.update(m.m_ports); m_vars.update(m.m_vars); for (const string& i : m.m_coverageOffBlocks) m_coverageOffBlocks.insert(i); if (!m_inline) { @@ -237,6 +247,8 @@ public: } V3ControlFTaskResolver& ftasks() { return m_tasks; } + V3ControlVarResolver& params() { return m_params; } + V3ControlVarResolver& ports() { return m_ports; } V3ControlVarResolver& vars() { return m_vars; } void addCoverageBlockOff(const string& name) { m_coverageOffBlocks.insert(name); } @@ -700,7 +712,8 @@ void V3Control::addScopeTraceOn(bool on, const string& scope, int levels) { } void V3Control::addVarAttr(FileLine* fl, const string& module, const string& ftask, - const string& var, VAttrType attr, AstSenTree* sensep) { + VarSpecKind kind, const string& pattern, VAttrType attr, + AstSenTree* sensep) { if (sensep) { FileLine* const flp = sensep->fileline(); // Historical, not actually needed, only parsed for compatibility, delete it @@ -711,8 +724,20 @@ void V3Control::addVarAttr(FileLine* fl, const string& module, const string& fta return; } } + + if (kind != VarSpecKind::VAR) { + switch (attr) { + case VAttrType::VAR_PUBLIC_FLAT: + case VAttrType::VAR_PUBLIC_FLAT_RD: + case VAttrType::VAR_PUBLIC_FLAT_RW: break; + default: + fl->v3error("'"s + attr.ascii() + "' attribute does not accept -param/-port"); + return; + } + } + // Semantics: Most of the attributes operate on signals - if (var.empty()) { + if (pattern.empty()) { if (attr == VAttrType::VAR_ISOLATE_ASSIGNMENTS) { if (ftask.empty()) { fl->v3error("isolate_assignments only applies to signals or functions/tasks"); @@ -737,15 +762,23 @@ void V3Control::addVarAttr(FileLine* fl, const string& module, const string& fta } else if (!ftask.empty()) { fl->v3error("Signals inside functions/tasks cannot be marked forceable"); } else { - V3ControlResolver::s().modules().at(module).vars().at(var).add(attr); + V3ControlResolver::s().modules().at(module).vars().at(pattern).add(attr); } } else { V3ControlModule& mod = V3ControlResolver::s().modules().at(module); - if (ftask.empty()) { - mod.vars().at(var).add(attr); - } else { - mod.ftasks().at(ftask).vars().at(var).add(attr); - } + V3ControlVar& controlVar = [&]() -> V3ControlVar& { + if (ftask.empty()) { + if (kind == VarSpecKind::PARAM) return mod.params().at(pattern); + if (kind == VarSpecKind::PORT) return mod.ports().at(pattern); + UASSERT_OBJ(kind == VarSpecKind::VAR, fl, "Unexpected VarSpecKind"); + return mod.vars().at(pattern); + } + if (kind == VarSpecKind::PARAM) return mod.ftasks().at(ftask).params().at(pattern); + if (kind == VarSpecKind::PORT) return mod.ftasks().at(ftask).ports().at(pattern); + UASSERT_OBJ(kind == VarSpecKind::VAR, fl, "Unexpected VarSpecKind"); + return mod.ftasks().at(ftask).vars().at(pattern); + }(); + controlVar.add(attr); } } } @@ -794,20 +827,30 @@ void V3Control::applyFTask(AstNodeModule* modulep, AstNodeFTask* ftaskp) { if (ftp) ftp->apply(ftaskp); } +template +static void resolveThenApply(T_Resolver& resolver, AstVar* varp) { + const std::string name = varp->prettyDehashOrigOrName(); + if (const V3ControlVar* const vp = resolver.vars().resolve(name)) vp->apply(varp); + if (varp->isParam()) { + if (const V3ControlVar* const vp = resolver.params().resolve(name)) vp->apply(varp); + } + if (varp->isIO()) { + if (const V3ControlVar* const vp = resolver.ports().resolve(name)) vp->apply(varp); + } +} + void V3Control::applyVarAttr(const AstNodeModule* modulep, const AstNodeFTask* ftaskp, AstVar* varp) { - const V3ControlVar* vp; V3ControlModule* const modp = V3ControlResolver::s().modules().resolve(modulep->prettyDehashOrigOrName()); if (!modp) return; if (ftaskp) { V3ControlFTask* const ftp = modp->ftasks().resolve(ftaskp->prettyDehashOrigOrName()); if (!ftp) return; - vp = ftp->vars().resolve(varp->prettyDehashOrigOrName()); + resolveThenApply(*ftp, varp); } else { - vp = modp->vars().resolve(varp->prettyDehashOrigOrName()); + resolveThenApply(*modp, varp); } - if (vp) vp->apply(varp); } int V3Control::getHierWorkers(const string& model) { diff --git a/src/V3Control.h b/src/V3Control.h index 1d1685f46..8a7a9070f 100644 --- a/src/V3Control.h +++ b/src/V3Control.h @@ -29,6 +29,12 @@ class V3Control final { public: + enum class VarSpecKind { + PARAM, // Select only matching parameters + PORT, // Select only matching ports + VAR // Select any matching AstVar (including params and ports) + }; + static void addCaseFull(const string& file, int lineno); static void addCaseParallel(const string& file, int lineno); static void addCoverageBlockOff(const string& file, int lineno); @@ -44,7 +50,8 @@ public: uint64_t cost); static void addScopeTraceOn(bool on, const string& scope, int levels); static void addVarAttr(FileLine* fl, const string& module, const string& ftask, - const string& signal, VAttrType type, AstSenTree* nodep); + VarSpecKind kind, const string& pattern, VAttrType type, + AstSenTree* nodep); static void applyCase(AstCase* nodep); static void applyCoverageBlock(AstNodeModule* modulep, AstBegin* nodep); diff --git a/src/V3ParseGrammar.h b/src/V3ParseGrammar.h index 173f5aa56..5c0e40c7a 100644 --- a/src/V3ParseGrammar.h +++ b/src/V3ParseGrammar.h @@ -37,6 +37,7 @@ public: VVarType m_varDecl = VVarType::UNKNOWN; // Type for next signal declaration (reg/wire/etc) VDirection m_varIO = VDirection::NONE; // Direction for next signal declaration (reg/wire/etc) VLifetime m_varLifetime; // Static/Automatic for next signal + V3Control::VarSpecKind m_vltVarSpecKind = V3Control::VarSpecKind::VAR; bool m_impliedDecl = false; // Allow implied wire declarations bool m_varDeclTyped = false; // Var got reg/wire for dedup check bool m_pinAnsi = false; // In ANSI parameter or port list diff --git a/src/V3ParseImp.h b/src/V3ParseImp.h index df829f61e..3bbd111c1 100644 --- a/src/V3ParseImp.h +++ b/src/V3ParseImp.h @@ -113,7 +113,7 @@ struct V3ParseBisonYYSType final { bool flag = false; // Passed up some rules union { V3Number* nump; - string* strp; + std::string* strp; int cint; double cdouble; bool cbool; diff --git a/src/verilog.l b/src/verilog.l index 49651ec30..80969768b 100644 --- a/src/verilog.l +++ b/src/verilog.l @@ -155,6 +155,8 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5} -?"-model" { FL; return yVLT_D_MODEL; } -?"-module" { FL; return yVLT_D_MODULE; } -?"-mtask" { FL; return yVLT_D_MTASK; } + -?"-param" { FL; return yVLT_D_PARAM; } + -?"-port" { FL; return yVLT_D_PORT; } -?"-rule" { FL; return yVLT_D_RULE; } -?"-scope" { FL; return yVLT_D_SCOPE; } -?"-task" { FL; return yVLT_D_TASK; } diff --git a/src/verilog.y b/src/verilog.y index b2e58496b..f89451784 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -282,6 +282,8 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"}) %token yVLT_D_MODEL "--model" %token yVLT_D_MODULE "--module" %token yVLT_D_MTASK "--mtask" +%token yVLT_D_PARAM "--param" +%token yVLT_D_PORT "--port" %token yVLT_D_RULE "--rule" %token yVLT_D_SCOPE "--scope" %token yVLT_D_TASK "--task" @@ -995,6 +997,14 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"}) // Blank lines for type insertion // Blank lines for type insertion // Blank lines for type insertion +// Blank lines for type insertion +// Blank lines for type insertion +// Blank lines for type insertion +// Blank lines for type insertion +// Blank lines for type insertion +// Blank lines for type insertion +// Blank lines for type insertion +// Blank lines for type insertion %start source_text @@ -7991,9 +8001,9 @@ vltItem: } else { V3Control::addScopeTraceOn(true, *$2, $3->toUInt()); }} - | vltVarAttrFront vltDModuleE vltDFTaskE vltVarAttrVarE attr_event_controlE - { V3Control::addVarAttr($1, *$2, *$3, *$4, $1, $5); } - | vltVarAttrFrontDeprecated vltDModuleE vltDFTaskE vltVarAttrVarE + | vltVarAttrFront vltDModuleE vltDFTaskE vltVarAttrSpecE attr_event_controlE + { V3Control::addVarAttr($1, *$2, *$3, GRAMMARP->m_vltVarSpecKind, *$4, $1, $5); } + | vltVarAttrFrontDeprecated vltDModuleE vltDFTaskE vltVarAttrSpecE { /* Historical, now has no effect */ } | vltInlineFront vltDModuleE vltDFTaskE { V3Control::addInline($1, *$2, *$3, $1); } @@ -8115,9 +8125,15 @@ vltInlineFront: | yVLT_NO_INLINE { $$ = false; } ; -vltVarAttrVarE: - /* empty */ { static string empty; $$ = ∅ } - | yVLT_D_VAR str { $$ = $2; } +vltVarAttrSpecE: + /* empty */ + { GRAMMARP->m_vltVarSpecKind = V3Control::VarSpecKind::VAR; static std::string empty; $$ = ∅ } + | yVLT_D_PARAM str + { GRAMMARP->m_vltVarSpecKind = V3Control::VarSpecKind::PARAM; $$ = $2; } + | yVLT_D_PORT str + { GRAMMARP->m_vltVarSpecKind = V3Control::VarSpecKind::PORT; $$ = $2; } + | yVLT_D_VAR str + { GRAMMARP->m_vltVarSpecKind = V3Control::VarSpecKind::VAR; $$ = $2; } ; vltVarAttrFront: diff --git a/test_regress/t/t_vlt_public_spec.out b/test_regress/t/t_vlt_public_spec.out new file mode 100644 index 000000000..f88d2fd36 --- /dev/null +++ b/test_regress/t/t_vlt_public_spec.out @@ -0,0 +1,35 @@ + scopesDump: + SCOPE 0x#: TOP + VAR 0x#: top_port_i + VAR 0x#: top_port_o + SCOPE 0x#: top + VAR 0x#: TOP_LOCALPARAM + VAR 0x#: TOP_PARAM + VAR 0x#: top_port_i + VAR 0x#: top_port_o + SCOPE 0x#: top.i_mid_0 + VAR 0x#: MID_LOCALPARAM + VAR 0x#: MID_PARM + VAR 0x#: mid_tmp_b + SCOPE 0x#: top.i_mid_0.i_sub_0 + VAR 0x#: f__Vstatic__SUB_F_LOCALPARAM + VAR 0x#: sub_port_i + VAR 0x#: sub_port_o + SCOPE 0x#: top.i_mid_0.i_sub_1 + VAR 0x#: f__Vstatic__SUB_F_LOCALPARAM + VAR 0x#: sub_port_i + VAR 0x#: sub_port_o + SCOPE 0x#: top.i_mid_1 + VAR 0x#: MID_LOCALPARAM + VAR 0x#: MID_PARM + VAR 0x#: mid_tmp_b + SCOPE 0x#: top.i_mid_1.i_sub_0 + VAR 0x#: f__Vstatic__SUB_F_LOCALPARAM + VAR 0x#: sub_port_i + VAR 0x#: sub_port_o + SCOPE 0x#: top.i_mid_1.i_sub_1 + VAR 0x#: f__Vstatic__SUB_F_LOCALPARAM + VAR 0x#: sub_port_i + VAR 0x#: sub_port_o + +*-* All Finished *-* diff --git a/test_regress/t/t_vlt_public_spec.py b/test_regress/t/t_vlt_public_spec.py new file mode 100755 index 000000000..01e358441 --- /dev/null +++ b/test_regress/t/t_vlt_public_spec.py @@ -0,0 +1,20 @@ +#!/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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--binary", "--vpi", test.name + ".vlt"]) + +test.execute() + +test.files_identical(test.run_log_filename, test.golden_filename, is_logfile=True, strip_hex=True) + +test.passes() diff --git a/test_regress/t/t_vlt_public_spec.v b/test_regress/t/t_vlt_public_spec.v new file mode 100644 index 000000000..7a166825e --- /dev/null +++ b/test_regress/t/t_vlt_public_spec.v @@ -0,0 +1,74 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + + +module top #( + parameter int TOP_PARAM = 42 +) ( + input int top_port_i, + output int top_port_o +); + + localparam int TOP_LOCALPARAM = 111; + int top_tmp; + + mid #(.MID_PARM(TOP_PARAM + TOP_LOCALPARAM)) i_mid_0(top_port_i, top_tmp); + mid #(.MID_PARM(TOP_PARAM + TOP_LOCALPARAM)) i_mid_1(top_tmp, top_port_o); + + function static void f(input int top_f_port_i, output int top_f_port_o); + localparam int TOP_F_LOCALPARAM = 1; + top_f_port_o = top_f_port_i + TOP_F_LOCALPARAM; + endfunction + + initial begin + $c("Verilated::scopesDump();"); + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule + +module mid #( + parameter int MID_PARM = 42 +) ( + input int mid_port_i, + output int mid_port_o +); + + localparam int MID_LOCALPARAM = 11; + int mid_tmp_a; + int mid_tmp_b; + + sub #(.SUB_PARAM(MID_PARM + MID_LOCALPARAM)) i_sub_0(mid_port_i, mid_tmp_a); + assign mid_tmp_b = mid_tmp_a; + sub #(.SUB_PARAM(MID_PARM + MID_LOCALPARAM)) i_sub_1(mid_tmp_b, mid_port_o); + + function static void f(input int mid_f_port_i, output int mid_f_port_o); + localparam int MID_F_LOCALPARAM = 1; + mid_f_port_o = mid_f_port_i + MID_F_LOCALPARAM; + endfunction + +endmodule + +module sub #( + parameter int SUB_PARAM = 42 +) ( + input int sub_port_i, + output int sub_port_o +); + + localparam int SUB_LOCALPARAM = 1; + int sub_tmp; + + assign sub_tmp = sub_port_i + SUB_PARAM; + assign sub_port_o = sub_tmp + SUB_LOCALPARAM; + + function static void f(input int sub_f_port_i, output int sub_f_port_o); + localparam int SUB_F_LOCALPARAM = 1; + sub_f_port_o = sub_f_port_i + SUB_F_LOCALPARAM; + endfunction + +endmodule diff --git a/test_regress/t/t_vlt_public_spec.vlt b/test_regress/t/t_vlt_public_spec.vlt new file mode 100644 index 000000000..fc1a202d3 --- /dev/null +++ b/test_regress/t/t_vlt_public_spec.vlt @@ -0,0 +1,17 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +`verilator_config + +public_flat_rd -module "top" -param "*" +public_flat_rw -module "top" -port "*" + +public_flat_rd -module "mid" -param "*" +public_flat_rw -module "mid" -function "*" -port "top_f_port_o" +public_flat -module "mid" -var "mid_tmp_b" + +public_flat -module "sub" -port "*" +public_flat_rd -module "sub" -function "*" -param "SUB_F_LOCALPARAM" diff --git a/test_regress/t/t_vlt_var_spec_bad.out b/test_regress/t/t_vlt_var_spec_bad.out new file mode 100644 index 000000000..cd9d24355 --- /dev/null +++ b/test_regress/t/t_vlt_var_spec_bad.out @@ -0,0 +1,8 @@ +%Error: t/t_vlt_var_spec_bad.vlt:9:1: 'VAR_SC_BV' attribute does not accept -param/-port + 9 | sc_bv -module "top" -param "*" + | ^~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_vlt_var_spec_bad.vlt:10:1: 'VAR_SC_BV' attribute does not accept -param/-port + 10 | sc_bv -module "top" -port "*" + | ^~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_vlt_var_spec_bad.py b/test_regress/t/t_vlt_var_spec_bad.py new file mode 100755 index 000000000..f9df35643 --- /dev/null +++ b/test_regress/t/t_vlt_var_spec_bad.py @@ -0,0 +1,21 @@ +#!/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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +# Doesn't matter which one +test.top_filename = "t/t_vlt_public_spec.v" + +test.compile(verilator_flags2=[test.name + ".vlt"], + fails=True, + expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_vlt_var_spec_bad.vlt b/test_regress/t/t_vlt_var_spec_bad.vlt new file mode 100644 index 000000000..85b26f6a7 --- /dev/null +++ b/test_regress/t/t_vlt_var_spec_bad.vlt @@ -0,0 +1,10 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +`verilator_config + +sc_bv -module "top" -param "*" +sc_bv -module "top" -port "*"