diff --git a/Changes b/Changes index 495f905f5..c609790cc 100644 --- a/Changes +++ b/Changes @@ -19,6 +19,7 @@ Verilator 5.031 devel * Support `pure constraint`. * Add `--no-std-package` as subset-alias of `--no-std`. * Add `--waiver-multiline` for context-sensitive `--waiver-output`. +* Add `lint_off --contents` in configuration files. (#5606) * Add error on illegal enum base type (#3010). [Iztok Jeras] * Add error on `wait` with missing `.triggered` (#4457). * Add error when improperly storing to parameter (#5147). [Gökçe Aydos] diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 5128cd553..c2bec656b 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -1364,7 +1364,7 @@ Summary: .. option:: --no-std Prevents parsing standard input files, alias for - :vlopt:`--no-std-package`. This may be extended to prevent reading other + :opt:`--no-std-package`. This may be extended to prevent reading other standardized files in future versions. .. option:: --no-std-package @@ -1651,7 +1651,7 @@ Summary: .. option:: --waiver-multiline - When using :vlopt:`--waiver-output`, include a match + When using :vlopt:`--waiver-output \`, include a match expression that includes the entire multiline error message as a match regular expression, as opposed to the default of only matching the first line of the error message. This provides a starting point for creating @@ -2088,7 +2088,7 @@ The grammar of configuration commands is as follows: .. option:: lint_off [-rule ] [-file "" [-lines [ - ]]] -.. option:: lint_off [-rule ] [-file ""] [-match ""] +.. option:: lint_off [-rule ] [-file ""] [-contents ""] [-match ""] Enable/disables the specified lint warning, in the specified filename (or wildcard with '\*' or '?', or all files if omitted) and range of @@ -2101,9 +2101,19 @@ The grammar of configuration commands is as follows: :vlopt:`-Wno-lint`) are enabled/disabled. This will override all later lint warning enables for the specified region. + If :code:`-contents` is provided, the input files must contain the given + wildcard (with '\*' or '?'), and are waived in case they match, provided + the :code:`-rule`, :code:`-file`, and :code:`-contents` also match. The + wildcard should be designed to match a single line; it is unspecified if + the wildcard is allowed to match across multiple lines. The input + contents does not include :vlopt:`--std` standard files, nor + configuration files (with :code:`verilator_config`). Typical use for + this is to match a version number present in the Verilog sources, so + that the waiver will only apply to that version of the sources. + If :code:`-match` is provided, the linter warnings are matched against the given wildcard (with '\*' or '?'), and are waived in case they - match, provided the :code:`-rule` and :code:`-file` + match, provided the :code:`-rule`, :code:`-file`, and :code:`-contents` also match. The wildcard is compared across the entire multi-line message; see :vlopt:`--waiver-multiline`. diff --git a/src/V3Config.cpp b/src/V3Config.cpp index d561570f5..b3e82248e 100644 --- a/src/V3Config.cpp +++ b/src/V3Config.cpp @@ -118,6 +118,70 @@ public: using V3ConfigVarResolver = V3ConfigWildcardResolver; +//====================================================================== + +class WildcardContents final { + // Not mutex protected, current calling from V3Config::waive is protected by error's mutex + // MEMBERS + std::map m_mapPatterns; // Pattern match results + std::deque m_lines; // Source text lines + + // METHODS + static WildcardContents& s() { // Singleton + static WildcardContents s_s; + return s_s; + } + void clearCacheImp() { m_mapPatterns.clear(); } + void pushTextImp(const string& text) { + // Similar code in VFileContent::pushText() + // Any leftover text is stored on largest line (might be "") + const string leftover = m_lines.back() + text; + m_lines.pop_back(); + + // Insert line-by-line + string::size_type line_start = 0; + while (true) { + const string::size_type line_end = leftover.find('\n', line_start); + if (line_end != string::npos) { + const string oneline(leftover, line_start, line_end - line_start + 1); + if (oneline.size() > 1) m_lines.push_back(oneline); // Keeps newline + UINFO(9, "Push[+" << (m_lines.size() - 1) << "]: " << oneline); + line_start = line_end + 1; + } else { + break; + } + } + // Keep leftover for next time + m_lines.emplace_back(string(leftover, line_start)); // Might be "" + clearCacheImp(); + } + + bool resolveUncachedImp(const string& name) { + for (const string& i : m_lines) { + if (VString::wildmatch(i, name)) return true; + } + return false; + } + bool resolveCachedImp(const string& name) { + // Lookup if it was resolved before, typically is + const auto pair = m_mapPatterns.emplace(name, false); + bool& entryr = pair.first->second; + // Resolve entry when first requested, cache the result + if (pair.second) entryr = resolveUncachedImp(name); + return entryr; + } + +public: + WildcardContents() { + m_lines.emplace_back(""); // start with no leftover + } + ~WildcardContents() = default; + // Return true iff name in parsed contents + static bool resolve(const string& name) { return s().resolveCachedImp(name); } + // Add arbitrary text (need not be line-by-line) + static void pushText(const string& text) { s().pushTextImp(text); } +}; + //###################################################################### // Function or task: Have variables and properties @@ -256,11 +320,28 @@ std::ostream& operator<<(std::ostream& os, const V3ConfigIgnoresLine& rhs) { // and multiple attributes can be attached to a line using V3ConfigLineAttribute = std::bitset; +class WaiverSetting final { +public: + V3ErrorCode m_code; // Error code + string m_contents; // --contents regexp + string m_match; // --match regexp + WaiverSetting(V3ErrorCode code, const string& contents, const string& match) + : m_code{code} + , m_contents{contents} + , m_match{match} {} + ~WaiverSetting() = default; + WaiverSetting& operator=(const WaiverSetting& rhs) { + m_code = rhs.m_code; + m_contents = rhs.m_contents; + m_match = rhs.m_match; + return *this; + } +}; + // File entity class V3ConfigFile final { using LineAttrMap = std::map; // Map line->bitset of attributes using IgnLines = std::multiset; // list of {line,code,on} - using WaiverSetting = std::pair; // Waive code if string matches using Waivers = std::vector; // List of {code,wildcard string} LineAttrMap m_lineAttrs; // Attributes to line mapping @@ -299,12 +380,12 @@ public: m_ignLines.insert(V3ConfigIgnoresLine{code, lineno, on}); m_lastIgnore.it = m_ignLines.begin(); } - void addIgnoreMatch(V3ErrorCode code, const string& match) { + void addIgnoreMatch(V3ErrorCode code, const string& contents, const string& match) { // Since Verilator 5.031 the error message compared has context, so // allow old rules to still match using a final '*' string newMatch = match; if (newMatch.empty() || newMatch.back() != '*') newMatch += '*'; - m_waivers.emplace_back(code, newMatch); + m_waivers.emplace_back(WaiverSetting{code, contents, newMatch}); } void applyBlock(AstNodeBlock* nodep) { @@ -342,8 +423,9 @@ public: bool waive(V3ErrorCode code, const string& match) { if (code.hardError()) return false; for (const auto& itr : m_waivers) { - if ((code.isUnder(itr.first) || (itr.first == V3ErrorCode::I_LINT)) - && VString::wildmatch(match, itr.second)) { + if ((code.isUnder(itr.m_code) || (itr.m_code == V3ErrorCode::I_LINT)) + && VString::wildmatch(match, itr.m_match) + && WildcardContents::resolve(itr.m_contents)) { return true; } } @@ -516,8 +598,9 @@ void V3Config::addIgnore(V3ErrorCode code, bool on, const string& filename, int } } -void V3Config::addIgnoreMatch(V3ErrorCode code, const string& filename, const string& match) { - V3ConfigResolver::s().files().at(filename).addIgnoreMatch(code, match); +void V3Config::addIgnoreMatch(V3ErrorCode code, const string& filename, const string& contents, + const string& match) { + V3ConfigResolver::s().files().at(filename).addIgnoreMatch(code, contents, match); } void V3Config::addInline(FileLine* fl, const string& module, const string& ftask, bool on) { @@ -651,6 +734,8 @@ bool V3Config::getScopeTraceOn(const string& scope) { return V3ConfigResolver::s().scopeTraces().getScopeTraceOn(scope); } +void V3Config::contentsPushText(const string& text) { return WildcardContents::pushText(text); } + bool V3Config::waive(FileLine* filelinep, V3ErrorCode code, const string& message) { V3ConfigFile* filep = V3ConfigResolver::s().files().resolve(filelinep->filename()); if (!filep) return false; diff --git a/src/V3Config.h b/src/V3Config.h index f5e0668b0..c0516cedc 100644 --- a/src/V3Config.h +++ b/src/V3Config.h @@ -34,7 +34,8 @@ public: static void addCoverageBlockOff(const string& file, int lineno); static void addCoverageBlockOff(const string& module, const string& blockname); static void addIgnore(V3ErrorCode code, bool on, const string& filename, int min, int max); - static void addIgnoreMatch(V3ErrorCode code, const string& filename, const string& match); + static void addIgnoreMatch(V3ErrorCode code, const string& filename, const string& contents, + const string& match); static void addInline(FileLine* fl, const string& module, const string& ftask, bool on); static void addModulePragma(const string& module, VPragmaType pragma); static void addProfileData(FileLine* fl, const string& model, const string& key, @@ -53,6 +54,9 @@ public: static uint64_t getProfileData(const string& model, const string& key); static FileLine* getProfileDataFileLine(); static bool getScopeTraceOn(const string& scope); + + static void contentsPushText(const string& text); + static bool waive(FileLine* filelinep, V3ErrorCode code, const string& message); }; diff --git a/src/V3FileLine.cpp b/src/V3FileLine.cpp index fd3c04b08..f7ac9d65c 100644 --- a/src/V3FileLine.cpp +++ b/src/V3FileLine.cpp @@ -147,6 +147,7 @@ FileLineSingleton::msgEnSetIdx_t FileLineSingleton::msgEnAnd(msgEnSetIdx_t lhsId // VFileContents class functions void VFileContent::pushText(const string& text) { + // Similar code in WildcardContents::pushText() if (m_lines.size() == 0) { m_lines.emplace_back(""); // no such thing as line [0] m_lines.emplace_back(""); // start with no leftover diff --git a/src/V3PreProc.cpp b/src/V3PreProc.cpp index 31bd2dccc..6cb95aa28 100644 --- a/src/V3PreProc.cpp +++ b/src/V3PreProc.cpp @@ -824,6 +824,21 @@ void V3PreProcImp::openFile(FileLine*, VInFilter* filterp, const string& filenam flsp->newContent(); for (const string& i : wholefile) flsp->contentp()->pushText(i); + // Save contents for V3Config --contents + if (filename != V3Options::getStdPackagePath()) { + bool containsVlt = false; + for (const string& i : wholefile) { + // TODO this is overly sensitive, might be in a comment + if (i.find("`verilator_config") != string::npos) { + containsVlt = true; + break; + } + } + if (!containsVlt) { + for (const string& i : wholefile) V3Config::contentsPushText(i); + } + } + // Create new stream structure m_lexp->scanNewFile(flsp); addLineComment(1); // Enter diff --git a/src/verilog.l b/src/verilog.l index 7c11ebc67..1092bedcb 100644 --- a/src/verilog.l +++ b/src/verilog.l @@ -137,6 +137,7 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5} "tracing_on" { FL; return yVLT_TRACING_ON; } -?"-block" { FL; return yVLT_D_BLOCK; } + -?"-contents" { FL; return yVLT_D_CONTENTS; } -?"-cost" { FL; return yVLT_D_COST; } -?"-file" { FL; return yVLT_D_FILE; } -?"-function" { FL; return yVLT_D_FUNCTION; } diff --git a/src/verilog.y b/src/verilog.y index 3819a316a..47741bcfc 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -495,6 +495,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"}) %token yVLT_TRACING_ON "tracing_on" %token yVLT_D_BLOCK "--block" +%token yVLT_D_CONTENTS "--contents" %token yVLT_D_COST "--cost" %token yVLT_D_FILE "--file" %token yVLT_D_FUNCTION "--function" @@ -7572,7 +7573,19 @@ vltItem: { if (($1 == V3ErrorCode::I_COVERAGE) || ($1 == V3ErrorCode::I_TRACING)) { $1->v3error("Argument -match only supported for lint_off"); } else { - V3Config::addIgnoreMatch($1, *$2, *$3); + V3Config::addIgnoreMatch($1, *$2, "", *$3); + }} + | vltOffFront vltDFile vltDContents + { if (($1 == V3ErrorCode::I_COVERAGE) || ($1 == V3ErrorCode::I_TRACING)) { + $1->v3error("Argument -match only supported for lint_off"); + } else { + V3Config::addIgnoreMatch($1, *$2, *$3, "*"); + }} + | vltOffFront vltDFile vltDContents vltDMatch + { if (($1 == V3ErrorCode::I_COVERAGE) || ($1 == V3ErrorCode::I_TRACING)) { + $1->v3error("Argument -match only supported for lint_off"); + } else { + V3Config::addIgnoreMatch($1, *$2, *$3, *$4); }} | vltOffFront vltDScope { if ($1 != V3ErrorCode::I_TRACING) { @@ -7660,6 +7673,10 @@ vltDBlock: // --block yVLT_D_BLOCK str { $$ = $2; } ; +vltDContents: + yVLT_D_CONTENTS str { $$ = $2; } + ; + vltDCost: // --cost yVLT_D_COST yaINTNUM { $$ = $2; } ; diff --git a/test_regress/t/t_uvm_todo.vlt b/test_regress/t/t_uvm_todo.vlt index 06d6a2395..30d06a2d9 100644 --- a/test_regress/t/t_uvm_todo.vlt +++ b/test_regress/t/t_uvm_todo.vlt @@ -4,28 +4,38 @@ // any use, without warranty, 2024 by Wilson Snyder. // SPDX-License-Identifier: CC0-1.0 +`ifdef _T_UVM_TODO_VLT_ `else +`define _T_UVM_TODO_VLT_ + `verilator_config +// Apply these rules to only UVM base files +`define VLT_UVM_FILES -file "*/uvm_*.svh" -contents "*UVM_VERSION_STRING*" + // Whole-file waivers -lint_off -rule WIDTHEXPAND -file "*/uvm_*.svh" -lint_off -rule WIDTHTRUNC -file "*/uvm_*.svh" +lint_off -rule WIDTHEXPAND `VLT_UVM_FILES +lint_off -rule WIDTHTRUNC `VLT_UVM_FILES // Context-sensitive waivers -lint_off -rule CASEINCOMPLETE -file "*/uvm_*.svh" -match "* case ({is_R, is_W})*" -lint_off -rule CASEINCOMPLETE -file "*/uvm_*.svh" -match "* case(orig_severity)*" -lint_off -rule CASTCONST -file "*/uvm_*.svh" -match "*class{}uvm_callback*" -lint_off -rule CASTCONST -file "*/uvm_*.svh" -match "*class{}uvm_component*" -lint_off -rule CASTCONST -file "*/uvm_*.svh" -match "*class{}uvm_event*" -lint_off -rule CASTCONST -file "*/uvm_*.svh" -match "*class{}uvm_report_object*" -lint_off -rule CASTCONST -file "*/uvm_*.svh" -match "*class{}uvm_sequence_item*" -lint_off -rule MISINDENT -file "*/uvm_*.svh" -match "* foreach (abstractions[i])*" -lint_off -rule MISINDENT -file "*/uvm_*.svh" -match "* foreach (lock_list[i])*" -lint_off -rule MISINDENT -file "*/uvm_*.svh" -match "* rw_access.data=*" -lint_off -rule MISINDENT -file "*/uvm_*.svh" -match "* uvm_cmdline_proc =*" -lint_off -rule REALCVT -file "*/uvm_*.svh" -match "* m_time *" -lint_off -rule REALCVT -file "*/uvm_*.svh" -match "*$realtime*" -lint_off -rule SYMRSVDWORD -file "*/uvm_*.svh" -match "*'delete'*" -lint_off -rule SYMRSVDWORD -file "*/uvm_*.svh" -match "*'list'*" -lint_off -rule SYMRSVDWORD -file "*/uvm_*.svh" -match "*'map'*" -lint_off -rule SYMRSVDWORD -file "*/uvm_*.svh" -match "*'override'*" -lint_off -rule SYMRSVDWORD -file "*/uvm_*.svh" -match "*'volatile'*" +lint_off -rule CASEINCOMPLETE `VLT_UVM_FILES -match "* case ({is_R, is_W})*" +lint_off -rule CASEINCOMPLETE `VLT_UVM_FILES -match "* case(orig_severity)*" +lint_off -rule CASTCONST `VLT_UVM_FILES -match "*class{}uvm_callback*" +lint_off -rule CASTCONST `VLT_UVM_FILES -match "*class{}uvm_component*" +lint_off -rule CASTCONST `VLT_UVM_FILES -match "*class{}uvm_event*" +lint_off -rule CASTCONST `VLT_UVM_FILES -match "*class{}uvm_report_object*" +lint_off -rule CASTCONST `VLT_UVM_FILES -match "*class{}uvm_sequence_item*" +lint_off -rule MISINDENT `VLT_UVM_FILES -match "* foreach (abstractions[i])*" +lint_off -rule MISINDENT `VLT_UVM_FILES -match "* foreach (lock_list[i])*" +lint_off -rule MISINDENT `VLT_UVM_FILES -match "* rw_access.data=*" +lint_off -rule MISINDENT `VLT_UVM_FILES -match "* uvm_cmdline_proc =*" +lint_off -rule REALCVT `VLT_UVM_FILES -match "* m_time *" +lint_off -rule REALCVT `VLT_UVM_FILES -match "*$realtime*" +lint_off -rule SYMRSVDWORD `VLT_UVM_FILES -match "*'delete'*" +lint_off -rule SYMRSVDWORD `VLT_UVM_FILES -match "*'list'*" +lint_off -rule SYMRSVDWORD `VLT_UVM_FILES -match "*'map'*" +lint_off -rule SYMRSVDWORD `VLT_UVM_FILES -match "*'override'*" +lint_off -rule SYMRSVDWORD `VLT_UVM_FILES -match "*'volatile'*" + +`undef VLT_UVM_FILES + +`endif // Guard diff --git a/test_regress/t/t_vlt_match_contents.out b/test_regress/t/t_vlt_match_contents.out new file mode 100644 index 000000000..4867ea0bc --- /dev/null +++ b/test_regress/t/t_vlt_match_contents.out @@ -0,0 +1,7 @@ +%Warning-UNUSEDSIGNAL: t/t_vlt_match_contents.v:11:10: Signal is not driven, nor used: 'usignal_contents_mismatch' + : ... note: In instance 't' + 11 | logic usignal_contents_mismatch; + | ^~~~~~~~~~~~~~~~~~~~~~~~~ + ... For warning description see https://verilator.org/warn/UNUSEDSIGNAL?v=latest + ... Use "/* verilator lint_off UNUSEDSIGNAL */" and lint_on around source to disable this message. +%Error: Exiting due to diff --git a/test_regress/t/t_vlt_match_contents.py b/test_regress/t/t_vlt_match_contents.py new file mode 100755 index 000000000..3c4174c36 --- /dev/null +++ b/test_regress/t/t_vlt_match_contents.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2024 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.lint(verilator_flags2=["--lint-only -Wall t/t_vlt_match_contents.vlt"], + fails=True, + expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_vlt_match_contents.v b/test_regress/t/t_vlt_match_contents.v new file mode 100644 index 000000000..dc1c5d6fe --- /dev/null +++ b/test_regress/t/t_vlt_match_contents.v @@ -0,0 +1,12 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Ethan Sifferman. +// SPDX-License-Identifier: CC0-1.0 + +string MATCH_VERSION = "10.20"; + +module t; + logic usignal_contents_suppress; // Suppressed with -contents + logic usignal_contents_mismatch; // Doesn't match -contents +endmodule diff --git a/test_regress/t/t_vlt_match_contents.vlt b/test_regress/t/t_vlt_match_contents.vlt new file mode 100644 index 000000000..e568f5af0 --- /dev/null +++ b/test_regress/t/t_vlt_match_contents.vlt @@ -0,0 +1,12 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Ethan Sifferman. +// SPDX-License-Identifier: CC0-1.0 + +`verilator_config + +lint_off -rule DECLFILENAME -file "*/t_vlt_match_contents.v" -contents "* MATCH_VERSION*" +lint_off -rule UNUSEDSIGNAL -file "*/t_vlt_match_contents.v" -contents "* MATCH_VERSION*" -match "*MATCH_VERSION*" +lint_off -rule UNUSEDSIGNAL -file "*/t_vlt_match_contents.v" -contents "* MATCH_VERSION*" -match "*usignal_contents_suppress*" +lint_off -rule UNUSEDSIGNAL -file "*/t_vlt_match_contents.v" -contents "* NOT_VERSION*" -match "*usignal_contents_mismatch*"