diff --git a/docs/gen/ex_MULTIDRIVEN_alwaysff_faulty.rst b/docs/gen/ex_MULTIDRIVEN_alwaysff_faulty.rst new file mode 100644 index 000000000..27818e008 --- /dev/null +++ b/docs/gen/ex_MULTIDRIVEN_alwaysff_faulty.rst @@ -0,0 +1,12 @@ +.. code-block:: sv + :linenos: + :emphasize-lines: 3,6 + + module t(input logic clk, input logic d, output logic q); + + initial q = 1'b0; // <--- Warning + + always_ff @(posedge clk) begin + q <= d; // <--- Warning + end + endmodule diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index 4b0d1472b..d1818afd5 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -1414,9 +1414,11 @@ List Of Warnings .. option:: MULTIDRIVEN - Warns that the specified signal comes from multiple ``always`` - blocks, each with different clocking. This warning does not look at - individual bits (see the example below). + Warns that the specified signal has multiple procedural drivers. + + One case is when the signal comes from multiple ``always`` blocks, + each with different clocking. This warning does not look at individual + bits (see the example below). This is considered bad style, as the consumer of a given signal may be unaware of the inconsistent clocking, causing clock domain crossing @@ -1430,8 +1432,28 @@ List Of Warnings .. include:: ../../docs/gen/ex_MULTIDRIVEN_msg.rst - Ignoring this warning will only slow simulations; it will simulate - correctly. It may, however, cause longer simulation runtimes due to + Another case is when a variable written in an ``always_comb`` or + ``always_ff`` block is also written by another process. IEEE 1800 + requires variables written in these specialized always blocks to be + written only by that process. + + Faulty ``always_ff`` example: + + .. include:: ../../docs/gen/ex_MULTIDRIVEN_alwaysff_faulty.rst + + To retain an initialization without adding another process, use a + declaration initializer (or, for synthesizable code, add a reset term): + + .. code-block:: sv + + logic q = 1'b0; + + always_ff @(posedge clk) begin + q <= d; + end + + Ignoring this warning may hide clock domain crossing, timing, or + portability bugs. It may also cause longer simulation runtimes due to reduced optimizations. @@ -1798,12 +1820,18 @@ List Of Warnings .. include:: ../../docs/gen/ex_PROCASSINIT_fixed.rst - Alternatively, use an initial block for the initialization: + For non-``always_ff`` logic, alternatively use an initial block for + the initialization: .. code-block:: sv initial flop_out = 1; // <--- Fixed + Do not use a separate initial block for a variable assigned by an + ``always_ff`` process, as IEEE 1800 requires an ``always_ff`` variable + to be written only by that process. Use a declaration initializer, or + initialize the variable from reset logic inside the ``always_ff``. + Disabled by default as this is a code-style warning; it will simulate correctly. diff --git a/src/V3Undriven.cpp b/src/V3Undriven.cpp index 0eba3be5b..14396bc1f 100644 --- a/src/V3Undriven.cpp +++ b/src/V3Undriven.cpp @@ -47,6 +47,7 @@ class UndrivenVarEntry final { = nullptr; // always_comb of var if driven within always_comb, else nullptr const FileLine* m_alwCombFileLinep = nullptr; // File line of always_comb of var if driven // within always_comb, else nullptr + const AstAlways* m_alwFFp = nullptr; // always_ff of var if driven within always_ff const AstNodeVarRef* m_nodep = nullptr; // varref if driven, else nullptr const AstNode* m_initStaticp = nullptr; // varref if in InitialStatic driven const AstNode* m_initialp = nullptr; // varref if driven in an explicit initial block @@ -58,7 +59,13 @@ class UndrivenVarEntry final { const AstNodeFTaskRef* m_callNodep = nullptr; // Call node if driven via writeSummary - enum : uint8_t { FLAG_USED = 0, FLAG_DRIVEN = 1, FLAG_DRIVEN_ALWCOMB = 2, FLAGS_PER_BIT = 3 }; + enum : uint8_t { + FLAG_USED = 0, // Signal or bit has been read/observed + FLAG_DRIVEN = 1, // Signal or bit has been written/driven + FLAG_DRIVEN_ALWCOMB = 2, // Whole signal has been driven from always_comb + FLAG_DRIVEN_ALWFF = 3, // Whole signal has been driven from always_ff + FLAGS_PER_BIT = 4 // Number of flags stored for each tracked bit + }; public: // CONSTRUCTORS @@ -151,6 +158,10 @@ public: m_alwCombp = alwCombp; m_alwCombFileLinep = fileLinep; } + void drivenAlwaysFFWhole(const AstAlways* alwFFp, const AstVar* varp) { + m_wholeFlags[FLAG_DRIVEN_ALWFF] = true; + m_alwFFp = alwFFp; + } const AstNode* initStaticp() const { return m_initStaticp; } void initStaticp(const AstNode* nodep) { m_initStaticp = nodep; } @@ -164,11 +175,13 @@ public: bool isUnderGen() const { return m_underGen; } bool isDrivenWhole() const { return m_wholeFlags[FLAG_DRIVEN]; } bool isDrivenAlwaysCombWhole() const { return m_wholeFlags[FLAG_DRIVEN_ALWCOMB]; } + bool isDrivenAlwaysFFWhole() const { return m_wholeFlags[FLAG_DRIVEN_ALWFF]; } bool isFtaskDriven() const { return m_ftaskDriven; } const AstNodeVarRef* getNodep() const { return m_nodep; } const FileLine* getNodeFileLinep() const { return m_nodeFileLinep; } const AstAlways* getAlwCombp() const { return m_alwCombp; } const FileLine* getAlwCombFileLinep() const { return m_alwCombFileLinep; } + const AstAlways* getAlwFFp() const { return m_alwFFp; } void usedBit(int bit, int width, const AstNode* nodep) { UINFO(9, "set u[" << (bit + width - 1) << ":" << bit << "] " << m_varp->name()); for (int i = 0; i < width; i++) { @@ -366,6 +379,7 @@ class UndrivenVisitor final : public VNVisitorConst { const AstNodeFTask* m_taskp = nullptr; // Current task const AstAlways* m_alwaysp = nullptr; // Current always of either type const AstAlways* m_alwaysCombp = nullptr; // Current always if combo, otherwise nullptr + const AstAlways* m_alwaysFFp = nullptr; // Current always if ff, otherwise nullptr V3UndrivenCapture* const m_capturep = nullptr; // Capture object. 'nullptr' if disabled. @@ -523,6 +537,8 @@ class UndrivenVisitor final : public VNVisitorConst { const AstNode* const otherWritep = entryp->getNodep() ? static_cast(entryp->getNodep()) : entryp->callNodep(); + const bool otherWriteIsStaticInit + = nodep->varp()->hasUserInit() && otherWritep == entryp->initStaticp(); if (m_alwaysCombp && (!entryp->isDrivenAlwaysCombWhole() @@ -545,6 +561,31 @@ class UndrivenVisitor final : public VNVisitorConst { << nodep->warnContextPrimary() << '\n' << otherWritep->warnOther() << "... Location of always_comb write\n" + << otherWritep->warnContextSecondary()); + } + if (m_alwaysFFp + && !otherWriteIsStaticInit + && (!entryp->isDrivenAlwaysFFWhole() + || (m_alwaysFFp != entryp->getAlwFFp() + && m_alwaysFFp->fileline() + != entryp->getAlwFFp()->fileline()))) { + nodep->v3warn( + MULTIDRIVEN, + "Variable written to in always_ff also written by other process" + << " (IEEE 1800-2023 9.2.2.4): " << nodep->prettyNameQ() << '\n' + << nodep->warnOther() << '\n' + << nodep->warnContextPrimary() << '\n' + << otherWritep->warnOther() << "... Location of other write\n" + << otherWritep->warnContextSecondary()); + } + if (!m_alwaysFFp && !m_inInitialStatic && entryp->isDrivenAlwaysFFWhole()) { + nodep->v3warn(MULTIDRIVEN, "Variable also written to in always_ff" + << " (IEEE 1800-2023 9.2.2.4): " + << nodep->prettyNameQ() << '\n' + << nodep->warnOther() << '\n' + << nodep->warnContextPrimary() << '\n' + << otherWritep->warnOther() + << "... Location of always_ff write\n" << otherWritep->warnContextSecondary()); } } @@ -558,6 +599,8 @@ class UndrivenVisitor final : public VNVisitorConst { entryp->underGenerate(); if (m_alwaysCombp) entryp->drivenAlwaysCombWhole(m_alwaysCombp, m_alwaysCombp->fileline()); + if (m_alwaysFFp) + entryp->drivenAlwaysFFWhole(m_alwaysFFp, nodep->varp()); } if (nodep->access().isWriteOrRW()) { if (m_inInitialStatic && !entryp->initStaticp()) entryp->initStaticp(nodep); @@ -645,6 +688,7 @@ class UndrivenVisitor final : public VNVisitorConst { void visit(AstAlways* nodep) override { VL_RESTORER(m_alwaysp); VL_RESTORER(m_alwaysCombp); + VL_RESTORER(m_alwaysFFp); AstNode::user2ClearTree(); m_alwaysp = nodep; if (nodep->keyword() == VAlwaysKwd::ALWAYS_COMB) { @@ -653,6 +697,7 @@ class UndrivenVisitor final : public VNVisitorConst { } else { m_alwaysCombp = nullptr; } + m_alwaysFFp = nodep->keyword() == VAlwaysKwd::ALWAYS_FF ? nodep : nullptr; iterateChildrenConst(nodep); if (nodep->keyword() == VAlwaysKwd::ALWAYS_COMB) UINFO(9, " Done " << nodep); } @@ -684,6 +729,7 @@ class UndrivenVisitor final : public VNVisitorConst { entryp->drivenViaCall(nodep); if (m_alwaysCombp) entryp->drivenAlwaysCombWhole(m_alwaysCombp, m_alwaysCombp->fileline()); + if (m_alwaysFFp) entryp->drivenAlwaysFFWhole(m_alwaysFFp, varp); } } } diff --git a/test_regress/t/t_case_incrdecr.v b/test_regress/t/t_case_incrdecr.v index 5fd5a4c4a..97025610f 100644 --- a/test_regress/t/t_case_incrdecr.v +++ b/test_regress/t/t_case_incrdecr.v @@ -75,8 +75,7 @@ module t // FSM with ++/-- // A more elaborate case statement, with if-else, for loops, etc // to confirm that ++/-- is handled by V3LinkInc.cpp - logic [3:0] state_d, state_q; - initial state_q = '0; + logic [3:0] state_d, state_q = '0; logic [3:0] state_counter_d, state_counter_q; always_ff @(posedge clk) begin state_q <= state_d; diff --git a/test_regress/t/t_cover_fsm_negative_extract.out b/test_regress/t/t_cover_fsm_negative_extract.out index 4c179d7e0..9794c51ea 100644 --- a/test_regress/t/t_cover_fsm_negative_extract.out +++ b/test_regress/t/t_cover_fsm_negative_extract.out @@ -368,10 +368,11 @@ S2 = 3'd2 } state_t; - int cyc; - logic start; - logic side; - logic [2:0] dyn_case; + int cyc = 0; + logic start = 1'b0; + logic side = 1'b0; + logic dyn_side = 1'b0; + logic [2:0] dyn_case = 3'd7; state_t state /*verilator fsm_reset_arc*/; fsm_if_mixed_vars_bad mixed_vars_u (.clk(clk)); @@ -392,17 +393,10 @@ fsm_direct_active_low_dynamic_reset_bad active_low_dynamic_reset_u ( .clk(clk), .rst_n(cyc != 0), .dyn_reset(dyn_case[1:0])); - initial begin - cyc = 0; - start = 1'b0; - side = 1'b0; - dyn_case = 3'd7; - end - always @(posedge clk) begin cyc <= cyc + 1; - if (cyc == 1) side <= 1'b1; - dyn_case <= {2'b11, side}; + if (cyc == 1) dyn_side <= 1'b1; + dyn_case <= {2'b11, dyn_side}; if (cyc == 5) begin $write("*-* All Finished *-*\n"); $finish; diff --git a/test_regress/t/t_cover_fsm_negative_extract.v b/test_regress/t/t_cover_fsm_negative_extract.v index a43942139..2e467b898 100644 --- a/test_regress/t/t_cover_fsm_negative_extract.v +++ b/test_regress/t/t_cover_fsm_negative_extract.v @@ -362,10 +362,11 @@ module t ( S2 = 3'd2 } state_t; - int cyc; - logic start; - logic side; - logic [2:0] dyn_case; + int cyc = 0; + logic start = 1'b0; + logic side = 1'b0; + logic dyn_side = 1'b0; + logic [2:0] dyn_case = 3'd7; state_t state /*verilator fsm_reset_arc*/; fsm_if_mixed_vars_bad mixed_vars_u (.clk(clk)); @@ -386,17 +387,10 @@ module t ( fsm_direct_active_low_dynamic_reset_bad active_low_dynamic_reset_u ( .clk(clk), .rst_n(cyc != 0), .dyn_reset(dyn_case[1:0])); - initial begin - cyc = 0; - start = 1'b0; - side = 1'b0; - dyn_case = 3'd7; - end - always @(posedge clk) begin cyc <= cyc + 1; - if (cyc == 1) side <= 1'b1; - dyn_case <= {2'b11, side}; + if (cyc == 1) dyn_side <= 1'b1; + dyn_case <= {2'b11, dyn_side}; if (cyc == 5) begin $write("*-* All Finished *-*\n"); $finish; diff --git a/test_regress/t/t_cover_fsm_noreset.out b/test_regress/t/t_cover_fsm_noreset.out index f2c28be61..f362e64df 100644 --- a/test_regress/t/t_cover_fsm_noreset.out +++ b/test_regress/t/t_cover_fsm_noreset.out @@ -17,11 +17,6 @@ int cyc; state_t state; - initial begin - cyc = 0; - state = S0; - end - always @(posedge clk) begin cyc <= cyc + 1; if (cyc == 4) begin diff --git a/test_regress/t/t_cover_fsm_noreset.v b/test_regress/t/t_cover_fsm_noreset.v index 02d8fc1b7..ed2871c26 100644 --- a/test_regress/t/t_cover_fsm_noreset.v +++ b/test_regress/t/t_cover_fsm_noreset.v @@ -16,11 +16,6 @@ module t ( int cyc; state_t state; - initial begin - cyc = 0; - state = S0; - end - always @(posedge clk) begin cyc <= cyc + 1; if (cyc == 4) begin diff --git a/test_regress/t/t_cover_fsm_reset.out b/test_regress/t/t_cover_fsm_reset.out index 8e7ec3c61..de7ea0f79 100644 --- a/test_regress/t/t_cover_fsm_reset.out +++ b/test_regress/t/t_cover_fsm_reset.out @@ -65,13 +65,10 @@ state_t state_q; state_t state_d; - initial begin - aux = 1'b1; - start = 1'b1; - end - always_ff @(posedge clk) begin if (rst) begin + aux <= 1'b1; + start <= 1'b1; state_q <= S0; aux <= 1'b0; end else begin @@ -81,14 +78,7 @@ always_comb begin state_d = state_q; -%000002 case (state_q) - // [FSM coverage] -%000001 // [fsm_arc t.reset_other_assign_ok_u.state_q::ANY->S0[reset]] [reset arc, excluded from %] -%000002 // [fsm_arc t.reset_other_assign_ok_u.state_q::S0->S1] -%000000 // [fsm_arc t.reset_other_assign_ok_u.state_q::S0->S2] -%000002 // [fsm_state t.reset_other_assign_ok_u.state_q::S0] -%000002 // [fsm_state t.reset_other_assign_ok_u.state_q::S1] -%000000 // [fsm_state t.reset_other_assign_ok_u.state_q::S2] *** UNCOVERED *** + case (state_q) S0: state_d = start ? S1 : S2; default: state_d = S0; endcase @@ -105,13 +95,8 @@ S2 = 2'd2 } state_t; - state_t state_q; - state_t other_q; - - initial begin - state_q = S0; - other_q = S2; - end + state_t state_q = S0; + state_t other_q = S2; always_ff @(posedge clk) begin if (rst) begin @@ -140,13 +125,8 @@ S2 = 2'd2 } state_t; - state_t state_q; - state_t other_q; - - initial begin - state_q = S0; - other_q = S2; - end + state_t state_q = S0; + state_t other_q = S2; // Useful because the one-block reset fallback must ignore non-constant reset // assignments and still retain the supported case(state_q) FSM below. diff --git a/test_regress/t/t_cover_fsm_reset.v b/test_regress/t/t_cover_fsm_reset.v index 5deaeb08a..d3d5862d3 100644 --- a/test_regress/t/t_cover_fsm_reset.v +++ b/test_regress/t/t_cover_fsm_reset.v @@ -54,13 +54,10 @@ module fsm_reset_other_assign_ok ( state_t state_q; state_t state_d; - initial begin - aux = 1'b1; - start = 1'b1; - end - always_ff @(posedge clk) begin if (rst) begin + aux <= 1'b1; + start <= 1'b1; state_q <= S0; aux <= 1'b0; end else begin @@ -87,13 +84,8 @@ module fsm_oneblock_reset_mismatch_ok ( S2 = 2'd2 } state_t; - state_t state_q; - state_t other_q; - - initial begin - state_q = S0; - other_q = S2; - end + state_t state_q = S0; + state_t other_q = S2; always_ff @(posedge clk) begin if (rst) begin @@ -117,13 +109,8 @@ module fsm_oneblock_reset_nonconst_ok ( S2 = 2'd2 } state_t; - state_t state_q; - state_t other_q; - - initial begin - state_q = S0; - other_q = S2; - end + state_t state_q = S0; + state_t other_q = S2; // Useful because the one-block reset fallback must ignore non-constant reset // assignments and still retain the supported case(state_q) FSM below. diff --git a/test_regress/t/t_cover_fsm_reset_incl.out b/test_regress/t/t_cover_fsm_reset_incl.out index 7f79dfb75..99b70cc9c 100644 --- a/test_regress/t/t_cover_fsm_reset_incl.out +++ b/test_regress/t/t_cover_fsm_reset_incl.out @@ -65,13 +65,10 @@ state_t state_q; state_t state_d; - initial begin - aux = 1'b1; - start = 1'b1; - end - always_ff @(posedge clk) begin if (rst) begin + aux <= 1'b1; + start <= 1'b1; state_q <= S0; aux <= 1'b0; end else begin @@ -81,14 +78,7 @@ always_comb begin state_d = state_q; -%000002 case (state_q) - // [FSM coverage] -%000001 // [fsm_arc t.reset_other_assign_ok_u.state_q::ANY->S0[reset]] -%000002 // [fsm_arc t.reset_other_assign_ok_u.state_q::S0->S1] -%000000 // [fsm_arc t.reset_other_assign_ok_u.state_q::S0->S2] -%000002 // [fsm_state t.reset_other_assign_ok_u.state_q::S0] -%000002 // [fsm_state t.reset_other_assign_ok_u.state_q::S1] -%000000 // [fsm_state t.reset_other_assign_ok_u.state_q::S2] *** UNCOVERED *** + case (state_q) S0: state_d = start ? S1 : S2; default: state_d = S0; endcase @@ -105,13 +95,8 @@ S2 = 2'd2 } state_t; - state_t state_q; - state_t other_q; - - initial begin - state_q = S0; - other_q = S2; - end + state_t state_q = S0; + state_t other_q = S2; always_ff @(posedge clk) begin if (rst) begin @@ -140,13 +125,8 @@ S2 = 2'd2 } state_t; - state_t state_q; - state_t other_q; - - initial begin - state_q = S0; - other_q = S2; - end + state_t state_q = S0; + state_t other_q = S2; // Useful because the one-block reset fallback must ignore non-constant reset // assignments and still retain the supported case(state_q) FSM below. diff --git a/test_regress/t/t_cover_fsm_two_proc_multi.out b/test_regress/t/t_cover_fsm_two_proc_multi.out index 04cddf917..56627cd5a 100644 --- a/test_regress/t/t_cover_fsm_two_proc_multi.out +++ b/test_regress/t/t_cover_fsm_two_proc_multi.out @@ -393,8 +393,6 @@ %000004 state_t state_q; %000004 state_t state_d; -%000001 initial side = 1'b0; - 000010 always_comb begin 000010 state_d = state_q; 000010 case (state_q) @@ -404,8 +402,10 @@ end %000009 always_ff @(posedge clk) begin -%000007 if (rst) state_q <= S0; -%000007 else begin +%000007 if (rst) begin +%000002 side <= 1'b0; +%000002 state_q <= S0; +%000007 end else begin %000007 side <= ~side; %000007 state_q <= state_d; end diff --git a/test_regress/t/t_cover_fsm_two_proc_multi.v b/test_regress/t/t_cover_fsm_two_proc_multi.v index 818d8103a..30cfb4edf 100644 --- a/test_regress/t/t_cover_fsm_two_proc_multi.v +++ b/test_regress/t/t_cover_fsm_two_proc_multi.v @@ -336,8 +336,6 @@ module fsm_seqmix_off ( state_t state_q; state_t state_d; - initial side = 1'b0; - always_comb begin state_d = state_q; case (state_q) @@ -347,8 +345,10 @@ module fsm_seqmix_off ( end always_ff @(posedge clk) begin - if (rst) state_q <= S0; - else begin + if (rst) begin + side <= 1'b0; + state_q <= S0; + end else begin side <= ~side; state_q <= state_d; end diff --git a/test_regress/t/t_fsm_register_wrapper_noinline.out b/test_regress/t/t_fsm_register_wrapper_noinline.out index b10271f24..8fef407e1 100644 --- a/test_regress/t/t_fsm_register_wrapper_noinline.out +++ b/test_regress/t/t_fsm_register_wrapper_noinline.out @@ -760,6 +760,7 @@ input logic start ); /* verilator lint_off BLKANDNBLK */ + /* verilator lint_off MULTIDRIVEN */ typedef enum logic [1:0] { S0 = 2'd0, S1 = 2'd1, @@ -800,6 +801,7 @@ .state_i(state_d), .state_o(state_q) ); + /* verilator lint_on MULTIDRIVEN */ /* verilator lint_on BLKANDNBLK */ endmodule diff --git a/test_regress/t/t_fsm_register_wrapper_noinline.v b/test_regress/t/t_fsm_register_wrapper_noinline.v index 11fb62ba6..9838e4083 100644 --- a/test_regress/t/t_fsm_register_wrapper_noinline.v +++ b/test_regress/t/t_fsm_register_wrapper_noinline.v @@ -660,6 +660,7 @@ module fsm_competing_direct ( input logic start ); /* verilator lint_off BLKANDNBLK */ + /* verilator lint_off MULTIDRIVEN */ typedef enum logic [1:0] { S0 = 2'd0, S1 = 2'd1, @@ -692,6 +693,7 @@ module fsm_competing_direct ( .state_i(state_d), .state_o(state_q) ); + /* verilator lint_on MULTIDRIVEN */ /* verilator lint_on BLKANDNBLK */ endmodule diff --git a/test_regress/t/t_gate_ormux.v b/test_regress/t/t_gate_ormux.v index 2f4047af3..ffab1d81f 100644 --- a/test_regress/t/t_gate_ormux.v +++ b/test_regress/t/t_gate_ormux.v @@ -86,22 +86,22 @@ module Test(/*AUTOARG*/ wdata_d1r <= wdata; end - reg [31:0] csr0000; - reg [31:0] csr0001; - reg [31:0] csr0002; - reg [31:0] csr0003; - reg [31:0] csr0004; - reg [31:0] csr0005; - reg [31:0] csr0006; - reg [31:0] csr0007; - reg [31:0] csr0008; - reg [31:0] csr0009; - reg [31:0] csr000a; - reg [31:0] csr000b; - reg [31:0] csr000c; - reg [31:0] csr000d; - reg [31:0] csr000e; - reg [31:0] csr000f; + reg [31:0] csr0000 = 32'he172d365; + reg [31:0] csr0001 = 32'h35cc25e2; + reg [31:0] csr0002 = 32'haf48436e; + reg [31:0] csr0003 = 32'h135e55e4; + reg [31:0] csr0004 = 32'h5fd6e48a; + reg [31:0] csr0005 = 32'hb07d34ad; + reg [31:0] csr0006 = 32'h2aa05deb; + reg [31:0] csr0007 = 32'hfe97b680; + reg [31:0] csr0008 = 32'h960f20bb; + reg [31:0] csr0009 = 32'h251129f0; + reg [31:0] csr000a = 32'hef3d2f93; + reg [31:0] csr000b = 32'hef4bc127; + reg [31:0] csr000c = 32'h3dfecb10; + reg [31:0] csr000d = 32'h1b4690f5; + reg [31:0] csr000e = 32'ha07822ab; + reg [31:0] csr000f = 32'hf817cbf6; wire [31:0] csr0010 = 32'h33675230; wire [31:0] csr0011 = 32'h00fa2144; wire [31:0] csr0012 = 32'h6a5e8e10; @@ -118,25 +118,6 @@ module Test(/*AUTOARG*/ wire [31:0] csr001d = 32'h02e7b33c; wire [31:0] csr001e = 32'h12101533; wire [31:0] csr001f = 32'h2cc1cce5; - initial begin - csr0000 = 32'he172d365; - csr0001 = 32'h35cc25e2; - csr0002 = 32'haf48436e; - csr0003 = 32'h135e55e4; - csr0004 = 32'h5fd6e48a; - csr0005 = 32'hb07d34ad; - csr0006 = 32'h2aa05deb; - csr0007 = 32'hfe97b680; - csr0008 = 32'h960f20bb; - csr0009 = 32'h251129f0; - csr000a = 32'hef3d2f93; - csr000b = 32'hef4bc127; - csr000c = 32'h3dfecb10; - csr000d = 32'h1b4690f5; - csr000e = 32'ha07822ab; - csr000f = 32'hf817cbf6; - end - always_ff @ (posedge clk) begin if (we_d1r && sel_d1r == 16'h0000) csr0000 <= wdata_d1r; if (we_d1r && sel_d1r == 16'h0001) csr0001 <= wdata_d1r; diff --git a/test_regress/t/t_hier_block_perf.v b/test_regress/t/t_hier_block_perf.v index 0a632c451..e42d47347 100644 --- a/test_regress/t/t_hier_block_perf.v +++ b/test_regress/t/t_hier_block_perf.v @@ -130,22 +130,22 @@ module Test ( /*AUTOARG*/ wdata_d1r <= wdata; end - reg [31:0] csr0000; - reg [31:0] csr0001; - reg [31:0] csr0002; - reg [31:0] csr0003; - reg [31:0] csr0004; - reg [31:0] csr0005; - reg [31:0] csr0006; - reg [31:0] csr0007; - reg [31:0] csr0008; - reg [31:0] csr0009; - reg [31:0] csr000a; - reg [31:0] csr000b; - reg [31:0] csr000c; - reg [31:0] csr000d; - reg [31:0] csr000e; - reg [31:0] csr000f; + reg [31:0] csr0000 = 32'he172d365; + reg [31:0] csr0001 = 32'h35cc25e2; + reg [31:0] csr0002 = 32'haf48436e; + reg [31:0] csr0003 = 32'h135e55e4; + reg [31:0] csr0004 = 32'h5fd6e48a; + reg [31:0] csr0005 = 32'hb07d34ad; + reg [31:0] csr0006 = 32'h2aa05deb; + reg [31:0] csr0007 = 32'hfe97b680; + reg [31:0] csr0008 = 32'h960f20bb; + reg [31:0] csr0009 = 32'h251129f0; + reg [31:0] csr000a = 32'hef3d2f93; + reg [31:0] csr000b = 32'hef4bc127; + reg [31:0] csr000c = 32'h3dfecb10; + reg [31:0] csr000d = 32'h1b4690f5; + reg [31:0] csr000e = 32'ha07822ab; + reg [31:0] csr000f = 32'hf817cbf6; wire [31:0] csr0010 = 32'h33675230; wire [31:0] csr0011 = 32'h00fa2144; wire [31:0] csr0012 = 32'h6a5e8e10; @@ -162,25 +162,6 @@ module Test ( /*AUTOARG*/ wire [31:0] csr001d = 32'h02e7b33c; wire [31:0] csr001e = 32'h12101533; wire [31:0] csr001f = 32'h2cc1cce5; - initial begin - csr0000 = 32'he172d365; - csr0001 = 32'h35cc25e2; - csr0002 = 32'haf48436e; - csr0003 = 32'h135e55e4; - csr0004 = 32'h5fd6e48a; - csr0005 = 32'hb07d34ad; - csr0006 = 32'h2aa05deb; - csr0007 = 32'hfe97b680; - csr0008 = 32'h960f20bb; - csr0009 = 32'h251129f0; - csr000a = 32'hef3d2f93; - csr000b = 32'hef4bc127; - csr000c = 32'h3dfecb10; - csr000d = 32'h1b4690f5; - csr000e = 32'ha07822ab; - csr000f = 32'hf817cbf6; - end - always_ff @(posedge clk) begin if (we_d1r && sel_d1r == 16'h0000) csr0000 <= wdata_d1r; if (we_d1r && sel_d1r == 16'h0001) csr0001 <= wdata_d1r; diff --git a/test_regress/t/t_lint_always_ff_multidriven_bad.out b/test_regress/t/t_lint_always_ff_multidriven_bad.out new file mode 100644 index 000000000..7486d44ec --- /dev/null +++ b/test_regress/t/t_lint_always_ff_multidriven_bad.out @@ -0,0 +1,19 @@ +%Warning-MULTIDRIVEN: t/t_lint_always_ff_multidriven_bad.v:14:5: Variable written to in always_ff also written by other process (IEEE 1800-2023 9.2.2.4): 'a' + : ... note: In instance 't' + t/t_lint_always_ff_multidriven_bad.v:14:5: + 14 | a <= 1'b1; + | ^ + t/t_lint_always_ff_multidriven_bad.v:11:11: ... Location of other write + 11 | initial a = 1'b0; + | ^ + ... For warning description see https://verilator.org/warn/MULTIDRIVEN?v=latest + ... Use "/* verilator lint_off MULTIDRIVEN */" and lint_on around source to disable this message. +%Warning-MULTIDRIVEN: t/t_lint_always_ff_multidriven_bad.v:23:11: Variable also written to in always_ff (IEEE 1800-2023 9.2.2.4): 'b' + : ... note: In instance 't' + t/t_lint_always_ff_multidriven_bad.v:23:11: + 23 | initial b = 1'b0; + | ^ + t/t_lint_always_ff_multidriven_bad.v:20:5: ... Location of always_ff write + 20 | b <= 1'b1; + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_lint_always_ff_multidriven_bad.py b/test_regress/t/t_lint_always_ff_multidriven_bad.py new file mode 100644 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_lint_always_ff_multidriven_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# 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-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('linter') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_lint_always_ff_multidriven_bad.v b/test_regress/t/t_lint_always_ff_multidriven_bad.v new file mode 100644 index 000000000..ae435f716 --- /dev/null +++ b/test_regress/t/t_lint_always_ff_multidriven_bad.v @@ -0,0 +1,25 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Zhi QU +// SPDX-License-Identifier: CC0-1.0 + +module t; + + logic a; + + initial a = 1'b0; + + always_ff @(posedge a) begin + a <= 1'b1; // <--- Warning + end + + logic b; + + always_ff @(posedge b) begin + b <= 1'b1; + end + + initial b = 1'b0; // <--- Warning + +endmodule diff --git a/test_regress/t/t_opt_const.v b/test_regress/t/t_opt_const.v index 171ccde2d..0991f93d4 100644 --- a/test_regress/t/t_opt_const.v +++ b/test_regress/t/t_opt_const.v @@ -254,15 +254,13 @@ endmodule // -> ^d[31:0] // Of course the correct result is ^d[38:0] = ^d module bug3470(input wire clk, input wire [31:0] in, output wire out); - logic [38:0] d; - initial d = 0; - initial tmp = 0; - initial expected = 0; + logic [38:0] d = 0; + logic tmp = 0; + logic expected = 0; always_ff @(posedge clk) d <= {d[6:0], in}; - logic tmp, expected; always_ff @(posedge clk) begin tmp <= ^(d >> 32) ^ (^d[31:0]); expected <= ^d; diff --git a/test_regress/t/t_trace_type_dupes.v b/test_regress/t/t_trace_type_dupes.v index f0c783e72..9232d6bff 100644 --- a/test_regress/t/t_trace_type_dupes.v +++ b/test_regress/t/t_trace_type_dupes.v @@ -19,9 +19,8 @@ module t ( /*AUTOARG*/ ); input clk; - int cyc; + int cyc = 0; logic [`NUM_SUBS - 1:0] x; - initial cyc = 0; always_ff @(posedge clk) begin cyc <= cyc + 1;