blob: 44fee0531d277a18891d571c7f8872cac3fee917 [file] [log] [blame]
// Copyright 2017-2020 The Verible Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "verilog/analysis/checkers/explicit_begin_rule.h"
#include <set>
#include "absl/base/attributes.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "common/analysis/lint_rule_status.h"
#include "common/analysis/token_stream_lint_rule.h"
#include "common/text/config_utils.h"
#include "common/text/token_info.h"
#include "verilog/analysis/descriptions.h"
#include "verilog/analysis/lint_rule_registry.h"
#include "verilog/parser/verilog_token_enum.h"
namespace verilog {
namespace analysis {
using verible::LintRuleStatus;
using verible::LintViolation;
using verible::TokenInfo;
using verible::TokenStreamLintRule;
// Register the lint rule
VERILOG_REGISTER_LINT_RULE(ExplicitBeginRule);
static const char kMessage[] =
" block constructs shall explicitly use begin/end.";
const LintRuleDescriptor &ExplicitBeginRule::GetDescriptor() {
static const LintRuleDescriptor d{
.name = "explicit-begin",
.topic = "explicit-begin",
.desc =
"Checks that a Verilog ``begin`` directive follows all "
"if, else, always, always_comb, always_latch, always_ff, "
"for, forever, foreach, while and initial statements.",
.param =
{
{"if_enable", "true",
"All if statements require an explicit begin-end block"},
{"else_enable", "true",
"All else statements require an explicit begin-end block"},
{"always_enable", "true",
"All always statements require an explicit begin-end block"},
{"always_comb_enable", "true",
"All always_comb statements require an explicit begin-end "
"block"},
{"always_latch_enable", "true",
"All always_latch statements require an explicit begin-end "
"block"},
{"always_ff_enable", "true",
"All always_ff statements require an explicit begin-end block"},
{"for_enable", "true",
"All for statements require an explicit begin-end block"},
{"forever_enable", "true",
"All forever statements require an explicit begin-end block"},
{"foreach_enable", "true",
"All foreach statements require an explicit begin-end block"},
{"while_enable", "true",
"All while statements require an explicit begin-end block"},
{"initial_enable", "true",
"All initial statements require an explicit begin-end block"},
},
};
return d;
}
absl::Status ExplicitBeginRule::Configure(absl::string_view configuration) {
using verible::config::SetBool;
return verible::ParseNameValues(
configuration,
{
{"if_enable", SetBool(&if_enable_)},
{"else_enable", SetBool(&else_enable_)},
{"always_enable", SetBool(&always_enable_)},
{"always_comb_enable", SetBool(&always_comb_enable_)},
{"always_latch_enable", SetBool(&always_latch_enable_)},
{"always_ff_enable", SetBool(&always_ff_enable_)},
{"for_enable", SetBool(&for_enable_)},
{"forever_enable", SetBool(&forever_enable_)},
{"foreach_enable", SetBool(&foreach_enable_)},
{"while_enable", SetBool(&while_enable_)},
{"initial_enable", SetBool(&initial_enable_)},
});
}
bool ExplicitBeginRule::IsTokenEnabled(const TokenInfo &token) {
switch (token.token_enum()) {
case TK_if:
return if_enable_;
case TK_else:
return else_enable_;
case TK_always:
return always_enable_;
case TK_always_comb:
return always_comb_enable_;
case TK_always_latch:
return always_latch_enable_;
case TK_always_ff:
return always_ff_enable_;
case TK_for:
return for_enable_;
case TK_forever:
return forever_enable_;
case TK_foreach:
return foreach_enable_;
case TK_while:
return while_enable_;
case TK_initial:
return initial_enable_;
default:
return false;
}
}
bool ExplicitBeginRule::HandleTokenStateMachine(const TokenInfo &token) {
// Responds to a token by updating the state of the analysis.
bool raise_violation = false;
switch (state_) {
case State::kNormal: {
// Special handling for constraints
switch (token.token_enum()) {
case TK_constraint: {
constraint_expr_level_ = 0;
state_ = State::kConstraint;
return false;
}
case TK_with: {
constraint_expr_level_ = 0;
state_ = State::kInlineConstraint;
return false;
}
default: {
// Do nothing
break;
}
}
if (!IsTokenEnabled(token)) {
break;
}
switch (token.token_enum()) {
// After token expect "begin"
case TK_always_comb:
ABSL_FALLTHROUGH_INTENDED;
case TK_always_latch:
ABSL_FALLTHROUGH_INTENDED;
case TK_forever:
ABSL_FALLTHROUGH_INTENDED;
case TK_initial:
start_token_ = token;
state_ = State::kExpectBegin;
break;
// After token expect a "condition" followed by "begin". NOTE: there may
// be tokens prior to the condition (like in an "always_ff" statement)
// and these are all ignored.
case TK_if:
ABSL_FALLTHROUGH_INTENDED;
case TK_always_ff:
ABSL_FALLTHROUGH_INTENDED;
case TK_for:
ABSL_FALLTHROUGH_INTENDED;
case TK_foreach:
ABSL_FALLTHROUGH_INTENDED;
case TK_while:
condition_expr_level_ = 0;
start_token_ = token;
state_ = State::kInCondition;
break;
// always gets special handling, as somtimes there is a "condition" or
// not before a "begin".
case TK_always:
condition_expr_level_ = 0;
start_token_ = token;
state_ = State::kInAlways;
break;
// else is also special as "if" or "begin" can follow
case TK_else:
start_token_ = token;
state_ = State::kInElse;
break;
default:
// Do nothing
break;
}
break;
}
case State::kInAlways: {
// always is a little more complicated in that it can be immediately
// followed by a "begin" or followed by some special characters ("@" or
// "*") and maybe a condition.
switch (token.token_enum()) {
case '@':
break;
case '*':
break;
case TK_begin:
state_ = State::kNormal;
break;
case '(':
condition_expr_level_++;
state_ = State::kInCondition;
break;
default:
raise_violation = true;
break;
}
break;
}
case State::kInElse: {
// An else statement can be followed by either a begin or an if.
switch (token.token_enum()) {
case TK_if:
if (if_enable_) {
condition_expr_level_ = 0;
start_token_ = token;
state_ = State::kInCondition;
} else {
state_ = State::kNormal;
}
break;
case TK_begin:
state_ = State::kNormal;
break;
default:
raise_violation = true;
break;
}
break;
}
case State::kInCondition: {
// The last token expects a condition statement enclosed in a pair of
// parentheses "()". This process also ignores any tokens between the last
// token and the opening parentheses which simplifies "always_ff".
switch (token.token_enum()) {
case '(': {
condition_expr_level_++;
break;
}
case ')': {
condition_expr_level_--;
if (condition_expr_level_ == 0) {
state_ = State::kExpectBegin;
}
break;
}
default: {
// throw away everything else
break;
}
}
break;
}
case State::kExpectBegin: {
// The next token must be a "begin"
switch (token.token_enum()) {
case TK_begin:
// If we got our begin token, we go back to normal status
state_ = State::kNormal;
break;
default: {
raise_violation = true;
break;
}
}
break;
}
case State::kInlineConstraint: {
switch (token.token_enum()) {
case '{': {
// An InlineConstraint
constraint_expr_level_++;
state_ = State::kConstraint;
break;
}
default: {
// throw away everything else
state_ = State::kNormal;
break;
}
}
}
ABSL_FALLTHROUGH_INTENDED;
case State::kConstraint: {
// System Verilog constraints are special and use curly braces {}
// instead of begin-end. So ignore all constraints
switch (token.token_enum()) {
case '{': {
constraint_expr_level_++;
break;
}
case '}': {
constraint_expr_level_--;
if (constraint_expr_level_ == 0) {
state_ = State::kNormal;
}
break;
}
default: {
// throw away everything else
break;
}
}
break;
}
default: {
// Do nothing
break;
}
} // switch (state_)
if (raise_violation) {
violations_.insert(LintViolation(
start_token_, absl::StrCat(start_token_.text(), kMessage,
" Expected begin, got ", token.text())));
// Once the violation is raised, we go back to a normal, default, state
state_ = State::kNormal;
}
return raise_violation;
}
void ExplicitBeginRule::HandleToken(const TokenInfo &token) {
// Ignore all white space and comments and return immediately
switch (token.token_enum()) {
case TK_SPACE:
return;
case TK_NEWLINE:
return;
case TK_COMMENT_BLOCK:
return;
case TK_EOL_COMMENT:
return;
default:
break;
}
bool retry = HandleTokenStateMachine(token);
// If this token raises a violation, it was because the statemachine was
// expecting a begin token. This token may expect a begin token too, so
// check it.
// Consider: forever if(a) #10; else #20; // 3 violations could be raised
// [forever, if, else].
if (retry) {
HandleTokenStateMachine(token);
}
}
LintRuleStatus ExplicitBeginRule::Report() const {
return LintRuleStatus(violations_, GetDescriptor());
}
} // namespace analysis
} // namespace verilog