1- // Copyright 2017-2020 The Verible Authors.
1+ // Copyright 2017-2023 The Verible Authors.
22//
33// Licensed under the Apache License, Version 2.0 (the "License");
44// you may not use this file except in compliance with the License.
1515#include " verible/verilog/analysis/checkers/always-ff-non-blocking-rule.h"
1616
1717#include < algorithm>
18+ #include < iterator>
1819#include < ostream>
1920#include < set>
21+ #include < string>
22+ #include < vector>
2023
2124#include " absl/status/status.h"
25+ #include " absl/strings/str_cat.h"
2226#include " absl/strings/string_view.h"
2327#include " verible/common/analysis/lint-rule-status.h"
2428#include " verible/common/analysis/matcher/bound-symbol-manager.h"
2933#include " verible/common/text/config-utils.h"
3034#include " verible/common/text/symbol.h"
3135#include " verible/common/text/syntax-tree-context.h"
36+ #include " verible/common/text/tree_utils.h"
3237#include " verible/common/util/casts.h"
3338#include " verible/common/util/logging.h"
39+ #include " verible/verilog/CST/expression.h"
40+ #include " verible/verilog/CST/statement.h"
3441#include " verible/verilog/CST/verilog-matchers.h"
3542#include " verible/verilog/CST/verilog-nonterminals.h"
3643#include " verible/verilog/analysis/descriptions.h"
3946namespace verilog {
4047namespace analysis {
4148
49+ using verible::AutoFix;
4250using verible::LintRuleStatus;
4351using verible::LintViolation;
4452using verible::SearchSyntaxTree;
@@ -102,31 +110,78 @@ void AlwaysFFNonBlockingRule::HandleSymbol(const verible::Symbol &symbol,
102110 static const Matcher asgn_incdec_matcher{NodekIncrementDecrementExpression ()};
103111 static const Matcher ident_matcher{NodekUnqualifiedId ()};
104112
113+ std::vector<AutoFix> autofixes;
114+
105115 // Rule may be waived if complete lhs consists of local variables
106116 // -> determine root of lhs
107117 const verible::Symbol *check_root = nullptr ;
118+ absl::string_view lhs_id;
108119
109120 verible::matcher::BoundSymbolManager symbol_man;
110121 if (asgn_blocking_matcher.Matches (symbol, &symbol_man)) {
111- if (const auto *const node =
112- verible::down_cast<const verible::SyntaxTreeNode *>(&symbol)) {
113- check_root =
114- /* lhs */ verible::down_cast<const verible::SyntaxTreeNode *>(
115- node->front ().get ());
116- }
122+ const verible::SyntaxTreeNode *node = &verible::SymbolCastToNode (symbol);
123+ check_root = verilog::GetNetVariableAssignmentLhs (*node);
124+ lhs_id = verible::StringSpanOfSymbol (*check_root);
125+
126+ const verible::SyntaxTreeLeaf *equals =
127+ verilog::GetNetVariableAssignmentOperator (*node);
128+
129+ autofixes.emplace_back (AutoFix (
130+ " Substitute blocking assignment '=' for nonblocking assignment '<='" ,
131+ {equals->get (), " <=" }));
117132 } else {
118133 // Not interested in any other blocking assignments unless flagged
119134 if (!catch_modifying_assignments_) return ;
120135
136+ // These autofixes require substituting the whole expression
137+ absl::string_view original = verible::StringSpanOfSymbol (symbol);
121138 if (asgn_modify_matcher.Matches (symbol, &symbol_man)) {
122- if (const auto *const node =
123- verible::down_cast<const verible::SyntaxTreeNode *>(&symbol)) {
124- check_root =
125- /* lhs */ verible::down_cast<const verible::SyntaxTreeNode *>(
126- node->front ().get ());
127- }
139+ const verible::SyntaxTreeNode *node = &verible::SymbolCastToNode (symbol);
140+ const verible::SyntaxTreeNode &lhs = *GetAssignModifyLhs (*node);
141+ const verible::SyntaxTreeNode &rhs = *GetAssignModifyRhs (*node);
142+
143+ lhs_id = verible::StringSpanOfSymbol (lhs);
144+ check_root = &lhs;
145+
146+ bool needs_parenthesis = NeedsParenthesis (rhs);
147+ absl::string_view start_rhs_expr = needs_parenthesis ? " (" : " " ;
148+ absl::string_view end_rhs_expr = needs_parenthesis ? " );" : " ;" ;
149+
150+ // Extract just the operation. Just '+' from '+='
151+ const absl::string_view op =
152+ verible::StringSpanOfSymbol (*GetAssignModifyOperator (*node))
153+ .substr (0 , 1 );
154+
155+ const std::string fix =
156+ absl::StrCat (lhs_id, " <= " , lhs_id, " " , op, start_rhs_expr,
157+ verible::StringSpanOfSymbol (rhs), end_rhs_expr);
158+
159+ autofixes.emplace_back (
160+ AutoFix (" Substitute assignment operator for equivalent "
161+ " nonblocking assignment" ,
162+ {original, fix}));
128163 } else if (asgn_incdec_matcher.Matches (symbol, &symbol_man)) {
129- check_root = &symbol;
164+ const verible::SyntaxTreeNode *operand =
165+ GetIncrementDecrementOperand (symbol);
166+
167+ check_root = operand;
168+ lhs_id = verible::StringSpanOfSymbol (*operand);
169+
170+ // Extract just the operation. Just '+' from '++'
171+ const absl::string_view op =
172+ verible::StringSpanOfSymbol (*GetIncrementDecrementOperator (symbol))
173+ .substr (0 , 1 );
174+
175+ // Equivalent nonblocking assignment
176+ // {'x++', '++x'} become 'x <= x + 1'
177+ // {'x--', '--x'} become 'x <= x - 1'
178+ const std::string fix =
179+ absl::StrCat (lhs_id, " <= " , lhs_id, " " , op, " 1;" );
180+
181+ autofixes.emplace_back (
182+ AutoFix (" Substitute increment/decrement operator for "
183+ " equivalent nonblocking assignment" ,
184+ {original, fix}));
130185 } else {
131186 // Not a blocking assignment
132187 return ;
@@ -160,7 +215,16 @@ void AlwaysFFNonBlockingRule::HandleSymbol(const verible::Symbol &symbol,
160215 }
161216
162217 // Enqueue detected violation unless waived
163- if (!waived) violations_.insert (LintViolation (symbol, kMessage , context));
218+ if (waived) return ;
219+
220+ // Dont autofix if the faulting expression is inside an expression
221+ // Example: "p <= k++"
222+ if (IsAutoFixSafe (symbol, lhs_id) &&
223+ !context.IsInside (NodeEnum::kExpression )) {
224+ violations_.insert (LintViolation (symbol, kMessage , context, autofixes));
225+ } else {
226+ violations_.insert (LintViolation (symbol, kMessage , context));
227+ }
164228} // HandleSymbol()
165229
166230bool AlwaysFFNonBlockingRule::InsideBlock (const verible::Symbol &symbol,
@@ -185,6 +249,7 @@ bool AlwaysFFNonBlockingRule::InsideBlock(const verible::Symbol &symbol,
185249 if (always_ff_matcher.Matches (symbol, &symbol_man)) {
186250 VLOG (4 ) << " always_ff @DEPTH=" << depth << std::endl;
187251 inside_ = depth;
252+ CollectLocalReferences (symbol);
188253 }
189254 return false ;
190255 }
@@ -227,5 +292,96 @@ bool AlwaysFFNonBlockingRule::LocalDeclaration(const verible::Symbol &symbol) {
227292 return false ;
228293} // LocalDeclaration()
229294
295+ bool AlwaysFFNonBlockingRule::NeedsParenthesis (
296+ const verible::Symbol &rhs) const {
297+ // Avoid inserting parenthesis for simple expressions
298+ // For example: x &= 1 -> x <= x & 1, and not x <= x & (1)
299+ // This could be more precise, but checking every specific
300+ // case where parenthesis are needed is hard. Adding them
301+ // doesn't hurt and the user can remove them if needed.
302+ const bool complex_rhs_expr =
303+ verible::GetLeftmostLeaf (rhs) != verible::GetRightmostLeaf (rhs);
304+ if (!complex_rhs_expr) return false ;
305+
306+ // Check if expression is wrapped in parenthesis
307+ const verible::Symbol *inner = verilog::UnwrapExpression (rhs);
308+ if (inner->Kind () == verible::SymbolKind::kLeaf ) return false ;
309+
310+ return !verible::SymbolCastToNode (*inner).MatchesTag (NodeEnum::kParenGroup );
311+ }
312+
313+ void AlwaysFFNonBlockingRule::CollectLocalReferences (
314+ const verible::Symbol &root) {
315+ const Matcher reference_matcher{NodekReference ()};
316+
317+ const std::vector<verible::TreeSearchMatch> references_ =
318+ verible::SearchSyntaxTree (root, reference_matcher);
319+
320+ // Precompute the StringSpan of every identifier referenced to.
321+ // Avoid recomputing it several times
322+ references.resize (references_.size ());
323+ std::transform (references_.cbegin (), references_.cend (), references.begin (),
324+ [](const verible::TreeSearchMatch &match) {
325+ return ReferenceWithId{
326+ match, verible::StringSpanOfSymbol (*match.match )};
327+ });
328+ }
329+
330+ bool AlwaysFFNonBlockingRule::IsAutoFixSafe (
331+ const verible::Symbol &faulting_assignment,
332+ absl::string_view lhs_id) const {
333+ std::vector<ReferenceWithId>::const_iterator itr = references.end ();
334+
335+ // Let's assume that 'x' is the variable affected by the faulting
336+ // assignment. In order to ensure that the autofix is safe we have
337+ // to ensure that there is no later reference to 'x'
338+ //
339+ // Can't autofix Can autofix
340+ // begin begin
341+ // x = x + 1; x = x + 1;
342+ // y = x; y <= y + 1;
343+ // end end
344+ //
345+ // In practical terms: we'll scan the 'references' vector for kReferences
346+ // to 'x' that appear after the faulting assignment in the always_ff block
347+
348+ const Matcher reference_matcher{NodekReference ()};
349+
350+ // Extract kReferences inside the faulting expression
351+ const std::vector<verible::TreeSearchMatch> references_ =
352+ verible::SearchSyntaxTree (faulting_assignment, reference_matcher);
353+
354+ // ref points to the latest reference to 'x' in our faulting expression
355+ // 'x++', 'x = x + 1', 'x &= x + 1'
356+ // ^ ^ ^
357+ // We need this to know from where to start searching for later references
358+ // to 'x', and decide whether the AutoFix is safe or not
359+ const verible::Symbol *ref =
360+ std::find_if (std::rbegin (references_), std::rend (references_),
361+ [&](const verible::TreeSearchMatch &m) {
362+ return verible::StringSpanOfSymbol (*m.match ) == lhs_id;
363+ })
364+ ->match ;
365+
366+ // Shouldn't happen, sanity check to avoid crashes
367+ if (!ref) return false ;
368+
369+ // Find the last reference to 'x' in the faulting assignment
370+ itr = std::find_if (
371+ std::begin (references), std::end (references),
372+ [&](const ReferenceWithId &r) { return r.match .match == ref; });
373+
374+ // Search from the last reference to 'x' onwards. If there is any,
375+ // we can't apply the autofix safely
376+ itr = std::find_if (
377+ std::next (itr), std::end (references),
378+ [&](const ReferenceWithId &ref) { return ref.id == lhs_id; });
379+
380+ // Let's say 'x' is affected by a flagged operation { 'x = 1', 'x++', ... }
381+ // We can safely autofix if after the flagged operation there are no
382+ // more references to 'x'
383+ return references.end () == itr;
384+ }
385+
230386} // namespace analysis
231387} // namespace verilog
0 commit comments