diff --git a/scripts/diagnostics.txt b/scripts/diagnostics.txt index fba88b868..10ee1e9ea 100644 --- a/scripts/diagnostics.txt +++ b/scripts/diagnostics.txt @@ -665,7 +665,6 @@ error InOutVarPortConn "'{}' cannot be connected to 'inout' port (only nets are error InOutUWireConn "'uwire' net '{}' cannot be connected to 'inout' port" error MixedVarAssigns "cannot mix continuous and procedural assignments to variable '{}'" error MultipleContAssigns "cannot have multiple continuous assignments to variable '{}'" -error MultipleAlwaysAssigns "variable '{}' driven by {} procedure cannot be written to by any other process" error MultipleUWireDrivers "'uwire' net '{}' cannot have multiple drivers" error MultipleUDNTDrivers "net '{}' with user-defined nettype '{}' cannot have multiple drivers because it does not specify a resolution function" error InputPortAssign "cannot assign to input port '{}'" @@ -705,6 +704,7 @@ warning sign-conversion SignConversion "implicit conversion changes signedness f warning float-bool-conv FloatBoolConv "implicit conversion from floating point type {} to boolean value" warning int-bool-conv IntBoolConv "implicit conversion from {} to boolean value" warning useless-cast UselessCast "useless cast from {} to the same type" +warning multiple-always-assigns MultipleAlwaysAssigns "variable '{}' driven by {} procedure cannot be written to by any other process" subsystem Statements error ReturnNotInSubroutine "return statement is only valid inside task and function blocks" @@ -1060,7 +1060,7 @@ group default = { real-underflow real-overflow vector-overflow int-overflow unco raw-protect-eof protected-envelope specify-param dup-timing-path invalid-pulsestyle negative-timing-limit bad-procedural-force duplicate-defparam implicit-port-type-mismatch split-distweight-op dpi-pure-task multibit-edge unknown-sys-name unknown-library - dup-config-rule } + dup-config-rule multiple-always-assigns } group extra = { empty-member empty-stmt dup-import pointless-void-cast case-gen-none case-gen-dup unused-result format-real ignored-slice task-ignored width-trunc dup-attr event-const diff --git a/scripts/warning_docs.txt b/scripts/warning_docs.txt index 2a8f08270..b4cda2c30 100644 --- a/scripts/warning_docs.txt +++ b/scripts/warning_docs.txt @@ -1162,3 +1162,18 @@ config cfg; instance d.foo use baz; endconfig ``` + +-Wmultiple-always-assigns +Detects that variables on the left-hand side of assignments within an procedure blocks, +including variables from the contents of a called functions, shall not be written to by any other process. +``` +module m(input clk, input rst); + logic i; + always_ff @(posedge clk) begin + i <= 1; + end + always_ff @(posedge rst) begin + i <= 2; + end +endmodule +``` diff --git a/source/ast/expressions/CallExpression.cpp b/source/ast/expressions/CallExpression.cpp index a0c37d08d..a7e3853ae 100644 --- a/source/ast/expressions/CallExpression.cpp +++ b/source/ast/expressions/CallExpression.cpp @@ -372,8 +372,7 @@ Expression& CallExpression::fromArgs(Compilation& compilation, const Subroutine& // If this subroutine is invoked from a procedure, register drivers for this // particular procedure to detect multiple driver violations. if (!thisClass) { - if (auto proc = context.getProceduralBlock(); - proc && proc->isSingleDriverBlock() && !context.scope->isUninstantiated()) { + if (auto proc = context.getProceduralBlock(); proc && !context.scope->isUninstantiated()) { addSubroutineDrivers(*proc, symbol, *result); } } diff --git a/tests/unittests/ast/MemberTests.cpp b/tests/unittests/ast/MemberTests.cpp index 520668612..5d8c090bf 100644 --- a/tests/unittests/ast/MemberTests.cpp +++ b/tests/unittests/ast/MemberTests.cpp @@ -1915,7 +1915,7 @@ endmodule auto& diags = compilation.getAllDiagnostics(); std::string result = "\n" + report(diags); CHECK(result == R"( -source:14:17: error: variable 'foo' driven by always_comb procedure cannot be written to by any other process +source:14:17: warning: variable 'foo' driven by always_comb procedure cannot be written to by any other process [-Wmultiple-always-assigns] always_comb i.foo = 1; ^~~~~ note: from 'm.n2' and 'm.n1' diff --git a/tests/unittests/ast/SubroutineTests.cpp b/tests/unittests/ast/SubroutineTests.cpp index 21a5336df..fe8794614 100644 --- a/tests/unittests/ast/SubroutineTests.cpp +++ b/tests/unittests/ast/SubroutineTests.cpp @@ -708,3 +708,83 @@ endmodule CHECK(diags[0].code == diag::MultipleAlwaysAssigns); CHECK(diags[1].code == diag::MultipleAlwaysAssigns); } + +TEST_CASE("driver checking applied to static function args (always_ff and always_ff)") { + auto tree = SyntaxTree::fromText(R"( +module m(input clk, input rst); + function logic f(logic a); + logic b; + b = a; + return b; + endfunction + + logic i, j; + always_ff @(posedge clk) begin + i <= f(i); + end + always_ff @(posedge rst) begin + j <= f(j); + end +endmodule +)"); + + Compilation compilation; + compilation.addSyntaxTree(tree); + + auto& diags = compilation.getAllDiagnostics(); + REQUIRE(diags.size() == 1); + CHECK(diags[0].code == diag::MultipleAlwaysAssigns); +} + +TEST_CASE("driver checking applied to static function local variables (always_ff and always)") { + auto tree = SyntaxTree::fromText(R"( +module m(input clk, input rst); + function logic f(logic a); + logic b; + b = a; + return b; + endfunction + + logic i, j; + always @(posedge clk) begin + i <= f(i); + end + always_ff @(posedge rst) begin + j <= f(j); + end +endmodule +)"); + + Compilation compilation; + compilation.addSyntaxTree(tree); + + auto& diags = compilation.getAllDiagnostics(); + REQUIRE(diags.size() == 1); + CHECK(diags[0].code == diag::MultipleAlwaysAssigns); +} + +TEST_CASE("driver checking applied to static function local variables (always and always)") { + auto tree = SyntaxTree::fromText(R"( +module m(input clk, input rst); + function logic f(logic a); + logic b; + b = a; + return b; + endfunction + + logic i, j; + always @(posedge clk) begin + i <= f(i); + end + always @(posedge rst) begin + j <= f(j); + end +endmodule +)"); + + Compilation compilation; + compilation.addSyntaxTree(tree); + + auto& diags = compilation.getAllDiagnostics(); + REQUIRE(diags.size() == 0); +}