Skip to content

Commit db7c12c

Browse files
committed
verilog: analysis: checkers: add autofixes for always_ff_non_blocking_rule
Offer autofixes without modifying program behaviour by taking care of corner cases such as: always_ff @(posedge clk) begin x++; y <= x; end which can't be converted to always_ff @(posedge clk) begin x <= x + 1; y <= x; end Similarly, parenthesis are inserted where they might be needed: a *= b + 1; becomes a <= b * (k + 1);
1 parent 1aa1e75 commit db7c12c

File tree

4 files changed

+297
-16
lines changed

4 files changed

+297
-16
lines changed

verible/verilog/analysis/checkers/BUILD

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,13 +1229,17 @@ cc_library(
12291229
"//verible/common/text:config-utils",
12301230
"//verible/common/text:symbol",
12311231
"//verible/common/text:syntax-tree-context",
1232+
"//verible/common/text:tree-utils",
12321233
"//verible/common/util:casts",
12331234
"//verible/common/util:logging",
1235+
"//verible/verilog/CST:expression",
1236+
"//verible/verilog/CST:statement",
12341237
"//verible/verilog/CST:verilog-matchers",
12351238
"//verible/verilog/CST:verilog-nonterminals",
12361239
"//verible/verilog/analysis:descriptions",
12371240
"//verible/verilog/analysis:lint-rule-registry",
12381241
"@com_google_absl//absl/status",
1242+
"@com_google_absl//absl/strings",
12391243
"@com_google_absl//absl/strings:string_view",
12401244
],
12411245
alwayslink = 1,

verible/verilog/analysis/checkers/always-ff-non-blocking-rule.cc

Lines changed: 171 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
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.
@@ -15,10 +15,14 @@
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"
@@ -29,8 +33,11 @@
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"
@@ -39,6 +46,7 @@
3946
namespace verilog {
4047
namespace analysis {
4148

49+
using verible::AutoFix;
4250
using verible::LintRuleStatus;
4351
using verible::LintViolation;
4452
using 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

166230
bool 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

verible/verilog/analysis/checkers/always-ff-non-blocking-rule.h

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
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.
@@ -24,6 +24,7 @@
2424
#include "absl/strings/string_view.h"
2525
#include "verible/common/analysis/lint-rule-status.h"
2626
#include "verible/common/analysis/syntax-tree-lint-rule.h"
27+
#include "verible/common/analysis/syntax_tree_search.h"
2728
#include "verible/common/text/symbol.h"
2829
#include "verible/common/text/syntax-tree-context.h"
2930
#include "verible/verilog/analysis/descriptions.h"
@@ -48,6 +49,16 @@ class AlwaysFFNonBlockingRule : public verible::SyntaxTreeLintRule {
4849
bool InsideBlock(const verible::Symbol &symbol, int depth);
4950
// Processes local declarations
5051
bool LocalDeclaration(const verible::Symbol &symbol);
52+
// Check the need of parenthesis in certain autofixes
53+
// Might have false positives. Example:
54+
// Fixing 'x *= y + 1' requires adding parenthesis
55+
// 'x <= x & (y + 1)'
56+
bool NeedsParenthesis(const verible::Symbol &rhs) const;
57+
// Collect local references inside an always_ff block
58+
void CollectLocalReferences(const verible::Symbol &root);
59+
// Check if is safe to autofix
60+
bool IsAutoFixSafe(const verible::Symbol &faulting_assignment,
61+
absl::string_view lhs_id) const;
5162

5263
private:
5364
// Collected violations.
@@ -72,6 +83,16 @@ class AlwaysFFNonBlockingRule : public verible::SyntaxTreeLintRule {
7283

7384
// In-order stack of local variable names
7485
std::vector<absl::string_view> locals_;
86+
87+
// NodekReference + string representation
88+
// of the ID they're referring to
89+
struct ReferenceWithId {
90+
verible::TreeSearchMatch match;
91+
absl::string_view id;
92+
};
93+
// Collection of references to variables
94+
// assumed to be in program order
95+
std::vector<ReferenceWithId> references;
7596
};
7697

7798
} // namespace analysis

0 commit comments

Comments
 (0)