From 44fe96b2da73e6ea9cd45e7ac00bb616efbcb85a Mon Sep 17 00:00:00 2001 From: Sumanth Kadiyala Date: Wed, 12 Aug 2026 19:13:02 -0400 Subject: [PATCH] Add `+verilator+assert+lock` to ignore RTL assert control statements (#8086) Co-authored-by: Sumanth Kadiyala --- bin/verilator | 1 + docs/guide/exe_sim.rst | 7 ++ include/verilated.cpp | 18 ++++- include/verilated.h | 7 ++ test_regress/t/t_assert_ctl_arg.cpp | 26 +++++++ test_regress/t/t_assert_ctl_lock_arg.py | 18 +++++ test_regress/t/t_assert_ctl_lock_arg.v | 70 +++++++++++++++++++ .../t/t_assert_ctl_lock_arg_noassert.py | 23 ++++++ 8 files changed, 168 insertions(+), 2 deletions(-) create mode 100755 test_regress/t/t_assert_ctl_lock_arg.py create mode 100644 test_regress/t/t_assert_ctl_lock_arg.v create mode 100755 test_regress/t/t_assert_ctl_lock_arg_noassert.py diff --git a/bin/verilator b/bin/verilator index cb866e243..f5148e41f 100755 --- a/bin/verilator +++ b/bin/verilator @@ -636,6 +636,7 @@ description of these arguments. =for VL_SPHINX_EXTRACT "_build/gen/args_verilated.rst" + +verilator+assert+lock Lock assertion status changes at startup +verilator+coverage+file+ Set coverage output filename +verilator+debug Enable debugging +verilator+debugi+ Enable debugging at a level diff --git a/docs/guide/exe_sim.rst b/docs/guide/exe_sim.rst index 966ceffd6..d85e56427 100644 --- a/docs/guide/exe_sim.rst +++ b/docs/guide/exe_sim.rst @@ -21,6 +21,13 @@ Summary: Options: +.. option:: +verilator+assert+lock + + Only allow command line options to disable / enable assertions. + Disables RTL from changing assertion handling via ``$asserton``, + ``$assertoff``, and ``$assertcontrol``. Also prevents ``VerilatedContext*`` + assertion control functions from updating assertion handling. + .. option:: +verilator+coverage+file+ When a model was Verilated using :vlopt:`--coverage`, sets the filename diff --git a/include/verilated.cpp b/include/verilated.cpp index cb4b052da..292c66e12 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -3092,6 +3092,7 @@ VerilatedContext::Serialized::Serialized() { bool VerilatedContext::assertOn() const VL_MT_SAFE { return m_s.m_assertOn; } void VerilatedContext::assertOn(bool flag) VL_MT_SAFE { + if (assertCtlsLocked()) return; // Set all assert and directive types when true, clear otherwise. m_s.m_assertOn = VL_MASK_I(ASSERT_ON_WIDTH) * flag; } @@ -3110,16 +3111,26 @@ uint32_t VerilatedContext::assertOnMask(VerilatedAssertType_t types, } void VerilatedContext::assertOnSet(VerilatedAssertType_t types, VerilatedAssertDirectiveType_t directives) VL_MT_SAFE { + if (assertCtlsLocked()) return; m_s.m_assertOn |= assertOnMask(types, directives); } void VerilatedContext::assertOnClear(VerilatedAssertType_t types, VerilatedAssertDirectiveType_t directives) VL_MT_SAFE { + if (assertCtlsLocked()) return; m_s.m_assertOn &= ~assertOnMask(types, directives); } +bool VerilatedContext::assertCtlsLocked() const VL_MT_SAFE { + return m_ns.m_assertCtlsLocked; +} +void VerilatedContext::assertCtlsLocked(bool flag) VL_MT_SAFE { + m_ns.m_assertCtlsLocked = flag; +} void VerilatedContext::assertCtl(uint32_t controlType, VerilatedAssertType_t types, VerilatedAssertDirectiveType_t directives) VL_MT_SAFE { // IEEE 1800-2023 Table 20-5 control_type. Lock freezes the On/Off state of the // selected bits until Unlock; On/Off/Kill leave locked bits unchanged. + // +verilator+assert+lock freezes everything, including Lock/Unlock itself. + if (assertCtlsLocked()) return; const uint32_t mask = assertOnMask(types, directives); const uint32_t lockedMask = mask & ~m_s.m_assertLock; switch (controlType) { @@ -3570,7 +3581,9 @@ void VerilatedContextImp::commandArgVl(const std::string& arg) { if (0 == std::strncmp(arg.c_str(), "+verilator+", std::strlen("+verilator+"))) { std::string str; uint64_t u64; - if (commandArgVlString(arg, "+verilator+coverage+file+", str)) { + if (arg == "+verilator+assert+lock") { + assertCtlsLocked(true); + } else if (commandArgVlString(arg, "+verilator+coverage+file+", str)) { coverageFilename(str); } else if (arg == "+verilator+debug") { Verilated::debug(4); @@ -3589,7 +3602,8 @@ void VerilatedContextImp::commandArgVl(const std::string& arg) { logFilename(str); logOutputToFile(false /* append */); } else if (arg == "+verilator+noassert") { - assertOn(false); + // Set directly on to avoid conflicts with +verilator+assert+lock + m_s.m_assertOn = 0; } else if (commandArgVlUint64(arg, "+verilator+prof+exec+start+", u64)) { profExecStart(u64); } else if (commandArgVlUint64(arg, "+verilator+prof+exec+window+", u64, 1)) { diff --git a/include/verilated.h b/include/verilated.h index 93e2b06bb..bc8d56cb2 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -450,6 +450,7 @@ protected: // A worker queues $finish before the main thread callback can set m_gotFinish. std::atomic m_finishPending{0}; // Number of queued $finish callbacks std::atomic m_finishPendingTime{TIME_UNSET}; // Time of the first callback + std::atomic m_assertCtlsLocked{false}; // When true, all assertion-control updates are ignored int m_stopReserved = 0; // Posted $stop requests not yet executed bool m_executingFinal = false; // Running generated final() code uint64_t m_profExecStart = 1; // +prof+exec+start time @@ -531,6 +532,12 @@ public: /// Clear enabled status for given assertion types void assertOnClear(VerilatedAssertType_t types, VerilatedAssertDirectiveType_t directives) VL_MT_SAFE; + /// Return if assertion-control updates are locked. When locked, RTL assert + // control statements ($asserton/$assertoff/$assertcontrol) are ignored, as + // are updates from the C++ API. + bool assertCtlsLocked() const VL_MT_SAFE; + /// Lock/unlock assertion-control updates. + void assertCtlsLocked(bool flag) VL_MT_SAFE; /// Apply assertion control for given control, assertion, and directive types void assertCtl(uint32_t controlType, VerilatedAssertType_t types, VerilatedAssertDirectiveType_t directives) VL_MT_SAFE; diff --git a/test_regress/t/t_assert_ctl_arg.cpp b/test_regress/t/t_assert_ctl_arg.cpp index 684db6436..128d7be25 100644 --- a/test_regress/t/t_assert_ctl_arg.cpp +++ b/test_regress/t/t_assert_ctl_arg.cpp @@ -138,8 +138,34 @@ void verilatedTest() { contextp->assertCtl(FAIL_ON, TYPE, DIRECTIVE); TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_FAIL_ON, TYPE, DIRECTIVE)); } + +void verilatedLockedTest() { + // With +verilator+assert+lock, every assertion-control update is a no-op + const std::unique_ptr contextp{new VerilatedContext}; + + // Clear some bits to test assertOnSet + contextp->assertOnClear(2, 3); + const char* argsp[] = {"+verilator+assert+lock"}; + contextp->commandArgsAdd(1, argsp); + TEST_CHECK_EQ(contextp->assertCtlsLocked(), true); + + // Validate each assert control API call is a no-op + contextp->assertOn(false); + TEST_CHECK_NZ(contextp->assertOn()); + + contextp->assertOnSet(2, 3); + TEST_CHECK_Z(contextp->assertOnGet(2, 3)); + + contextp->assertOnClear(1, 1); + TEST_CHECK_NZ(contextp->assertOnGet(1, 1)); + + contextp->assertCtl(4, 4, 1); + TEST_CHECK_NZ(contextp->assertOnGet(4, 1)); +} + int main(int argc, char** argv) { verilatedTest(); + verilatedLockedTest(); if (errors) return 10; const std::unique_ptr contextp{new VerilatedContext}; diff --git a/test_regress/t/t_assert_ctl_lock_arg.py b/test_regress/t/t_assert_ctl_lock_arg.py new file mode 100755 index 000000000..a3eab4dd5 --- /dev/null +++ b/test_regress/t/t_assert_ctl_lock_arg.py @@ -0,0 +1,18 @@ +#!/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('vlt') + +test.compile(verilator_flags2=["--binary --assert"]) + +test.execute(all_run_flags=["+verilator+assert+lock"]) + +test.passes() diff --git a/test_regress/t/t_assert_ctl_lock_arg.v b/test_regress/t/t_assert_ctl_lock_arg.v new file mode 100644 index 000000000..b74fa3516 --- /dev/null +++ b/test_regress/t/t_assert_ctl_lock_arg.v @@ -0,0 +1,70 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Run with +verilator+assert+lock, which freezes the enabled assertions, so +// every assertion control below is ignored and all directives keep firing. + +// verilog_format: off +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0); +// verilog_format: on + +`ifdef T_ASSERT_CTL_LOCK_ARG_NOASSERT +`define ctl_on_off 3 // On, ignored as the assertions stay disabled +`define expected 0 +`else +`define ctl_on_off 4 // Off, ignored as the assertions stay enabled +`define expected 10 +`endif + +module t; + logic clk = 0; + logic fals = 1'b0; + + int imm_fails = 0; + int conc_fails = 0; + int assume_fails = 0; + int ctl; + + always #5 clk = ~clk; + + // posedge clk at t = 5, 15, 25, 35, 45, 55, 65, 75, 85, 95 + always @(posedge clk) imm_assert : assert (fals) else imm_fails = imm_fails + 1; + + conc_assert : assert property (@(posedge clk) fals) else conc_fails = conc_fails + 1; + + conc_assume : assume property (@(posedge clk) fals) else assume_fails = assume_fails + 1; + + initial begin + #6; // t=6 + $assertcontrol(`ctl_on_off); // On or Off, whichever would flip all types + #10; // t=16 + $assertcontrol(`ctl_on_off, 2); // Same, but only for SIMPLE_IMMEDIATE + #10; // t=26 + $assertcontrol(1); // Lock + $assertcontrol(2); // Unlock + #10; // t=36 + $assertcontrol(5); // Kill + #10; // t=46 + $assertpassoff; + $assertfailoff; + #10; // t=56 + ctl = `ctl_on_off; + $assertcontrol(ctl); // Same again, via a non-constant control_type + #10; // t=66 + $assertkill; + #34; // t=100 + $finish; + end + + // Every directive kept the enabled state it started with, as no control + // above took effect + final begin + `checkd(imm_fails, `expected); + `checkd(conc_fails, `expected); + `checkd(assume_fails, `expected); + $write("*-* All Finished *-*\n"); + end +endmodule diff --git a/test_regress/t/t_assert_ctl_lock_arg_noassert.py b/test_regress/t/t_assert_ctl_lock_arg_noassert.py new file mode 100755 index 000000000..a1bd36752 --- /dev/null +++ b/test_regress/t/t_assert_ctl_lock_arg_noassert.py @@ -0,0 +1,23 @@ +#!/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('vlt') +test.top_filename = "t_assert_ctl_lock_arg.v" + +test.compile( + verilator_flags2=["--binary --assert +define+T_ASSERT_CTL_LOCK_ARG_NOASSERT"]) + +test.execute(all_run_flags=["+verilator+noassert", "+verilator+assert+lock"]) + +# Same result with the arguments in the opposite order +test.execute(all_run_flags=["+verilator+assert+lock", "+verilator+noassert"]) + +test.passes()