From cfebe805c90fcfbb1de6b0606412cf86859573c5 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Thu, 23 Apr 2026 10:50:23 +0200 Subject: [PATCH] Support implication operator with constraint_set (#7300) (#7448) * Support implication operator with constraint_set * improve coverage, achieve 100 line cov * Address review: simplify addNext; tighten disable soft grammar to constraint_primary * Apply 'make format' * re-run --------- Co-authored-by: github action --- src/V3Randomize.cpp | 126 +++++++-- src/verilog.y | 45 +++- .../t/t_constraint_disable_soft_bad.out | 5 + .../t/t_constraint_disable_soft_bad.py | 16 ++ .../t/t_constraint_disable_soft_bad.v | 23 ++ .../t/t_constraint_implication_else_bad.out | 5 + .../t/t_constraint_implication_else_bad.py | 16 ++ .../t/t_constraint_implication_else_bad.v | 23 ++ .../t/t_constraint_implication_set.py | 21 ++ test_regress/t/t_constraint_implication_set.v | 254 ++++++++++++++++++ 10 files changed, 505 insertions(+), 29 deletions(-) create mode 100644 test_regress/t/t_constraint_disable_soft_bad.out create mode 100755 test_regress/t/t_constraint_disable_soft_bad.py create mode 100644 test_regress/t/t_constraint_disable_soft_bad.v create mode 100644 test_regress/t/t_constraint_implication_else_bad.out create mode 100755 test_regress/t/t_constraint_implication_else_bad.py create mode 100644 test_regress/t/t_constraint_implication_else_bad.v create mode 100755 test_regress/t/t_constraint_implication_set.py create mode 100644 test_regress/t/t_constraint_implication_set.v diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 993ba2207..c14490faa 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -2007,6 +2007,10 @@ class ConstraintExprVisitor final : public VNVisitor { void visit(AstSFormatF* nodep) override {} void visit(AstStmtExpr* nodep) override {} void visit(AstCStmt* nodep) override {} + void visit(AstIf* nodep) override { + // Hoisted runtime-conditional disable_soft statements parked in the + // constraint-items list by the pre-pass; already fully lowered. + } void visit(AstConstraintIf* nodep) override { AstNodeExpr* newp = nullptr; FileLine* const fl = nodep->fileline(); @@ -2192,28 +2196,13 @@ class ConstraintExprVisitor final : public VNVisitor { } void visit(AstConstraintExpr* nodep) override { - // IEEE 1800-2023 18.5.13: "disable soft" removes all soft constraints - // referencing the specified variable. Pass the variable name directly - // instead of going through SMT lowering. + // IEEE 1800-2023 18.5.13: 'disable soft' is a meta-level directive on + // the constraint graph, lowered to a void RANDOMIZER_DISABLE_SOFT + // call. Conditional occurrences (if / foreach / implication body) + // are rewritten by extractConditionalDisableSofts() before this + // visitor runs, so anything we see here is unconditional. if (nodep->isDisableSoft()) { - // Extract variable name from expression (VarRef or MemberSel) - std::string varName; - if (const AstNodeVarRef* const vrefp = VN_CAST(nodep->exprp(), NodeVarRef)) { - varName = vrefp->name(); - } else if (const AstMemberSel* const mselp = VN_CAST(nodep->exprp(), MemberSel)) { - varName = mselp->name(); - } else { - nodep->v3fatalSrc("Unexpected expression type in disable soft"); - return; - } - AstCMethodHard* const callp = new AstCMethodHard{ - nodep->fileline(), - new AstVarRef{nodep->fileline(), VN_AS(m_genp->user2p(), NodeModule), m_genp, - VAccess::READWRITE}, - VCMethod::RANDOMIZER_DISABLE_SOFT, - new AstConst{nodep->fileline(), AstConst::String{}, varName}}; - callp->dtypeSetVoid(); - nodep->replaceWith(callp->makeStmt()); + nodep->replaceWith(buildDisableSoftCallStmt(nodep)); VL_DO_DANGLING(nodep->deleteTree(), nodep); return; } @@ -2603,6 +2592,93 @@ class ConstraintExprVisitor final : public VNVisitor { "Visit function missing? Constraint function missing for node: " << nodep); } + // Build the runtime statement that calls RANDOMIZER_DISABLE_SOFT on the + // variable referenced by the given AstConstraintExpr. Parser enforces + // constraint_primary operand (verilog.y, IEEE 1800-2023 18.5.13). + AstNode* buildDisableSoftCallStmt(AstConstraintExpr* nodep) { + std::string varName; + if (const AstNodeVarRef* const vrefp = VN_CAST(nodep->exprp(), NodeVarRef)) { + varName = vrefp->name(); + } else if (const AstMemberSel* const mselp = VN_CAST(nodep->exprp(), MemberSel)) { + varName = mselp->name(); + } else { + nodep->v3fatalSrc("Unexpected expression type in disable soft"); + } + AstCMethodHard* const callp = new AstCMethodHard{ + nodep->fileline(), + new AstVarRef{nodep->fileline(), VN_AS(m_genp->user2p(), NodeModule), m_genp, + VAccess::READWRITE}, + VCMethod::RANDOMIZER_DISABLE_SOFT, + new AstConst{nodep->fileline(), AstConst::String{}, varName}}; + callp->dtypeSetVoid(); + return callp->makeStmt(); + } + + // Pre-pass: for every AstConstraintExpr(isDisableSoft) nested inside an + // AstConstraintIf subtree (any depth), return a list of runtime AstIf + // statements `if (accumulated_cond) randomizer.disable_soft(var);` where + // accumulated_cond AND-folds the enclosing then/else branch conditions. + // The original ConstraintExpr is replaced with `1'b1` so editSingle() + // folds the surrounding boolean cleanly. Caller appends the returned + // list to the constraint-items chain; that chain becomes the setup task + // body, which runs before the solver on every randomize() call -- giving + // disable-soft real conditional semantics per IEEE 1800-2023 18.5.13. + AstNode* extractConditionalDisableSofts(AstNode* itemsp, AstNodeExpr* outerCondp) { + AstNode* hoistListp = nullptr; + for (AstNode *stmtp = itemsp, *nextp; stmtp; stmtp = nextp) { + nextp = stmtp->nextp(); + if (AstConstraintIf* const ifp = VN_CAST(stmtp, ConstraintIf)) { + FileLine* const fl = ifp->fileline(); + // then branch: accumulated = outer && this.cond + AstNodeExpr* const thenCondp + = outerCondp ? static_cast( + new AstLogAnd{fl, outerCondp->cloneTreePure(false), + ifp->condp()->cloneTreePure(false)}) + : ifp->condp()->cloneTreePure(false); + if (AstNode* const thenHoistp + = extractConditionalDisableSofts(ifp->thensp(), thenCondp)) { + hoistListp + = hoistListp ? AstNode::addNext(hoistListp, thenHoistp) : thenHoistp; + } + VL_DO_DANGLING(thenCondp->deleteTree(), thenCondp); + if (ifp->elsesp()) { + // else branch: accumulated = outer && !this.cond + AstNodeExpr* const notInnerp + = new AstLogNot{fl, ifp->condp()->cloneTreePure(false)}; + AstNodeExpr* const elseCondp + = outerCondp ? static_cast(new AstLogAnd{ + fl, outerCondp->cloneTreePure(false), notInnerp}) + : notInnerp; + if (AstNode* const elseHoistp + = extractConditionalDisableSofts(ifp->elsesp(), elseCondp)) { + hoistListp + = hoistListp ? AstNode::addNext(hoistListp, elseHoistp) : elseHoistp; + } + VL_DO_DANGLING(elseCondp->deleteTree(), elseCondp); + } + } else if (AstConstraintExpr* const exprp = VN_CAST(stmtp, ConstraintExpr)) { + if (exprp->isDisableSoft() && outerCondp) { + FileLine* const fl = exprp->fileline(); + AstIf* const hoistIfp = new AstIf{fl, outerCondp->cloneTreePure(false), + buildDisableSoftCallStmt(exprp), nullptr}; + hoistListp = hoistListp ? AstNode::addNext(hoistListp, + static_cast(hoistIfp)) + : static_cast(hoistIfp); + // Replace in-tree disable-soft with a no-op boolean + // constraint so editSingle() folds cleanly. + AstConstraintExpr* const truep + = new AstConstraintExpr{fl, new AstConst{fl, AstConst::BitTrue{}}}; + exprp->replaceWith(truep); + VL_DO_DANGLING(exprp->deleteTree(), exprp); + } + } + // foreach/unique bodies are left untouched: disable-soft inside + // a foreach would need per-iteration condition support, and + // unique cannot legally contain disable-soft. + } + return hoistListp; + } + public: // CONSTRUCTORS explicit ConstraintExprVisitor(AstClass* classp, VMemberMap& memberMap, AstNode* nodep, @@ -2618,6 +2694,14 @@ public: , m_memberMap{memberMap} , m_writtenVars{writtenVars} , m_sizeConstrainedArraysp{sizeConstrainedArraysp} { + // Pre-pass before SMT lowering: extract conditional disable-soft + // directives as runtime AstIf statements and append them to the + // constraint-items chain so they reach the setup task body. The SMT + // visitor skips these via visit(AstIf) above. + if (AstNode* const hoistListp + = nodep ? extractConditionalDisableSofts(nodep, nullptr) : nullptr) { + nodep->addNext(hoistListp); + } iterateAndNextNull(nodep); } }; diff --git a/src/verilog.y b/src/verilog.y index f809924ee..93ccefa49 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -5085,11 +5085,11 @@ expr: // IEEE: part of expression/constant_expression/ | type_referenceEq yP_EQUAL type_referenceBoth { $$ = new AstEqT{$2, $1, $3}; } | type_referenceEq yP_NOTEQUAL type_referenceBoth { $$ = new AstNeqT{$2, $1, $3}; } // // IEEE: expr yP_MINUSGT expr (1800-2009) - // // Conflicts with constraint_expression:"expr yP_MINUSGT constraint_set" - // // To duplicating expr for constraints, just allow the more general form - // // Later Ast processing must ignore constraint terms where inappropriate - //UNSUP ~l~expr yP_MINUSGT constraint_set { $$ = $1; $$ = $1 + $2 + $3; } - //UNSUP remove line below + // // Full IEEE 1800-2023 18.7.2 "expr -> + // // constraint_set" is split: this rule handles + // // the bare-expression RHS as a boolean + // // (AstLogIf), constraint_expression has one + // // production per non-expression RHS shape. | ~l~expr yP_MINUSGT ~r~expr { $$ = new AstLogIf{$2, $1, $3}; } // // // <= is special, as we need to disambiguate it with <= assignment @@ -7910,8 +7910,37 @@ constraint_expression: // ==IEEE: constraint_expression | yUNIQUE '{' range_list '}' ';' { $$ = new AstConstraintUnique{$1, $3}; } // // IEEE: expr yP_MINUSGT constraint_set - // // Conflicts with expr:"expr yP_MINUSGT expr"; rule moved there - // + // // The bare-expression case "expr -> expr ;" + // // arrives via the general expr rule above + // // (AstLogIf) and matches "expr ';'". Each + // // production below covers one constraint_set + // // shape that is not a plain expression and + // // builds an outer AstConstraintIf wrapping the + // // statement V3Randomize already knows how to + // // lower (with disable-soft hoisted by the + // // ConstraintExprVisitor pre-pass). + | expr yP_MINUSGT '{' constraint_expressionList '}' + { $$ = new AstConstraintIf{$2, $1, $4, nullptr}; } + | expr yP_MINUSGT yIF '(' expr ')' constraint_set %prec prLOWER_THAN_ELSE + { $$ = new AstConstraintIf{$2, $1, + new AstConstraintIf{$3, $5, $7, nullptr}, nullptr}; } + | expr yP_MINUSGT yIF '(' expr ')' constraint_set yELSE constraint_set + { $$ = new AstConstraintIf{$2, $1, + new AstConstraintIf{$3, $5, $7, $9}, nullptr}; } + | expr yP_MINUSGT yFOREACH '(' idClassSelForeach ')' constraint_set + { $$ = new AstConstraintIf{$2, $1, + new AstConstraintForeach{$3, $5, $7}, nullptr}; } + | expr yP_MINUSGT yUNIQUE '{' range_list '}' ';' + { $$ = new AstConstraintIf{$2, $1, + new AstConstraintUnique{$3, $5}, nullptr}; } + | expr yP_MINUSGT ySOFT expr ';' + { AstConstraintExpr* const innerp = new AstConstraintExpr{$3, $4}; + innerp->isSoft(true); + $$ = new AstConstraintIf{$2, $1, innerp, nullptr}; } + | expr yP_MINUSGT yDISABLE ySOFT constraint_primary ';' + { AstConstraintExpr* const innerp = new AstConstraintExpr{$3, $5}; + innerp->isDisableSoft(true); + $$ = new AstConstraintIf{$2, $1, innerp, nullptr}; } | yIF '(' expr ')' constraint_set %prec prLOWER_THAN_ELSE { $$ = new AstConstraintIf{$1, $3, $5, nullptr}; } | yIF '(' expr ')' constraint_set yELSE constraint_set @@ -7920,7 +7949,7 @@ constraint_expression: // ==IEEE: constraint_expression | yFOREACH '(' idClassSelForeach ')' constraint_set { $$ = new AstConstraintForeach{$1, $3, $5}; } // // soft is 1800-2012 - | yDISABLE ySOFT expr/*constraint_primary*/ ';' + | yDISABLE ySOFT constraint_primary ';' { AstConstraintExpr* const newp = new AstConstraintExpr{$1, $3}; newp->isDisableSoft(true); $$ = newp; } diff --git a/test_regress/t/t_constraint_disable_soft_bad.out b/test_regress/t/t_constraint_disable_soft_bad.out new file mode 100644 index 000000000..397492600 --- /dev/null +++ b/test_regress/t/t_constraint_disable_soft_bad.out @@ -0,0 +1,5 @@ +%Error: t/t_constraint_disable_soft_bad.v:15:25: syntax error, unexpected ';' + 15 | disable soft (a + 1); + | ^ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: Exiting due to diff --git a/test_regress/t/t_constraint_disable_soft_bad.py b/test_regress/t/t_constraint_disable_soft_bad.py new file mode 100755 index 000000000..344a4e20a --- /dev/null +++ b/test_regress/t/t_constraint_disable_soft_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('vlt') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_constraint_disable_soft_bad.v b/test_regress/t/t_constraint_disable_soft_bad.v new file mode 100644 index 000000000..08f59978d --- /dev/null +++ b/test_regress/t/t_constraint_disable_soft_bad.v @@ -0,0 +1,23 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +class C; + rand bit [3:0] a; + rand bit [3:0] b; + + // IEEE 1800-2023 18.5.13: the operand of 'disable soft' must be a + // constraint_primary (hierarchical identifier with optional select), + // not a bare expression such as (a + 1). + constraint c_bad { + disable soft (a + 1); + } +endclass + +module t; + initial begin + $stop; + end +endmodule diff --git a/test_regress/t/t_constraint_implication_else_bad.out b/test_regress/t/t_constraint_implication_else_bad.out new file mode 100644 index 000000000..80b092eb8 --- /dev/null +++ b/test_regress/t/t_constraint_implication_else_bad.out @@ -0,0 +1,5 @@ +%Error: t/t_constraint_implication_else_bad.v:15:32: syntax error, unexpected else + 15 | (a == 0) -> { b == 4'h1; } else { b == 4'h2; } + | ^~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: Exiting due to diff --git a/test_regress/t/t_constraint_implication_else_bad.py b/test_regress/t/t_constraint_implication_else_bad.py new file mode 100755 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_constraint_implication_else_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_constraint_implication_else_bad.v b/test_regress/t/t_constraint_implication_else_bad.v new file mode 100644 index 000000000..6f9e16db8 --- /dev/null +++ b/test_regress/t/t_constraint_implication_else_bad.v @@ -0,0 +1,23 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +class C; + rand bit [3:0] a; + rand bit [3:0] b; + + // IEEE 1800-2023 18.7.2: implication operator -> takes one constraint_set + // on the RHS; it is single-armed (no else clause). Only the if (...) form + // accepts an optional else branch. + constraint c_bad { + (a == 0) -> { b == 4'h1; } else { b == 4'h2; } + } +endclass + +module t; + initial begin + $stop; + end +endmodule diff --git a/test_regress/t/t_constraint_implication_set.py b/test_regress/t/t_constraint_implication_set.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_implication_set.py @@ -0,0 +1,21 @@ +#!/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('simulator') + +if not test.have_solver: + test.skip("No constraint solver installed") + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_constraint_implication_set.v b/test_regress/t/t_constraint_implication_set.v new file mode 100644 index 000000000..64a9b1296 --- /dev/null +++ b/test_regress/t/t_constraint_implication_set.v @@ -0,0 +1,254 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0) +`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 + +typedef enum bit [1:0] { TXN_READ, TXN_WRITE } txn_e; + +// Each constraint exercises one RHS shape allowed by IEEE 1800-2023 18.7.2 +// (constraint_set on the right of the implication operator -> ). `mode` +// selects which constraint contributes; the others are vacuous. +class Forms; + rand bit [3:0] mode; + rand bit [3:0] a; + rand bit [3:0] b; + rand bit [3:0] c; + rand bit [31:0] address; + rand txn_e txn_type; + rand bit [7:0] arr [4]; + rand bit [3:0] uarr [3]; + + // Bare expression (legacy form, supported pre-PR via expr->expr). + constraint c_expr { + (mode == 4'd0) -> b == 4'h9; + } + + // Brace-block: the exact shape from issue #7300. + constraint c_brace_repro { + (mode == 4'd1) -> { + if (txn_type inside {TXN_READ}) address % (1 << 4) == 0; + } + } + + // Brace-block with multiple statements. + constraint c_brace_multi { + (mode == 4'd2) -> { + address[0] == 1'b0; + address[31] == 1'b0; + } + } + + // Bare if (without surrounding braces). + constraint c_if { + (mode == 4'd3) -> if (a == 4'h1) b == 4'h7; + } + + // Bare if/else. + constraint c_if_else { + (mode == 4'd4) -> if (a == 4'h1) b == 4'ha; + else b == 4'hb; + } + + // Bare foreach. + constraint c_foreach { + (mode == 4'd5) -> foreach (arr[i]) arr[i] < 8'h40; + } + + // Bare unique (uses the static-array form V3Randomize supports today). + constraint c_unique { + (mode == 4'd6) -> unique { uarr }; + } + + // Bare soft. + constraint c_soft { + (mode == 4'd7) -> soft b == 4'hd; + } + + // Nested implication inside a brace block. + constraint c_nested { + (mode == 4'd8) -> { + (a == 4'h0) -> { b == 4'h5; } + } + } +endclass + +// Conditional `disable soft` is meta-level: V3Randomize hoists it into a +// runtime AstIf attached to the setup task so the directive only fires +// when its outer condition is true. When override_flag==0, the soft +// `x == 4'h5` wins; when override_flag==1, the implication fires the +// disable AND the override `x == 4'hc`, so x must be 4'hc. +class DisSoft; + bit override_flag; + rand bit [3:0] x; + constraint c_soft_x { soft x == 4'h5; } + constraint c_override { + (override_flag == 1'b1) -> disable soft x ; + (override_flag == 1'b1) -> x == 4'hc ; + } +endclass + +// MemberSel target for `disable soft`. Cross-object reference (inner.y) +// parses as AstMemberSel; disable-soft must extract the variable name from +// that node shape too. +class Inner; + rand bit [3:0] y; +endclass +class DisSoftMember; + bit override_flag; + rand Inner inner; + constraint c_soft_y { soft inner.y == 4'h5; } + constraint c_override { + (override_flag == 1'b1) -> disable soft inner.y ; + (override_flag == 1'b1) -> inner.y == 4'hc ; + } + function new(); inner = new(); endfunction +endclass + +// `disable soft` in BOTH then and else arms of a single if. Exercises the +// hoist-list concat path in the else branch (a then-arm hoist already +// exists when the else-arm hoist is appended). We only check that the +// hard constraint in each arm fires correctly under its `pick` condition; +// soft enforcement priority is not asserted (Verilator's soft solver is +// best-effort). The condition variable `pick` is non-rand so the setup +// task sees its current value before the solver runs. +class DisSoftBothArms; + bit [1:0] pick; + rand bit [3:0] a; + rand bit [3:0] b; + constraint c_soft_a { soft a == 4'h7; } + constraint c_soft_b { soft b == 4'h8; } + constraint c_split { + if (pick == 2'd0) { disable soft a; a == 4'h1; } + else { disable soft b; b == 4'h2; } + } +endclass + +module t; + Forms obj; + DisSoft ds; + int ok; + + initial begin + obj = new(); + + repeat (10) begin + ok = obj.randomize() with { mode == 4'd0; }; + `checkd(ok, 1); + `checkh(obj.b, 4'h9); + end + + repeat (10) begin + ok = obj.randomize() with { mode == 4'd1; txn_type == TXN_READ; }; + `checkd(ok, 1); + `checkh(obj.address[3:0], 4'h0); + end + + repeat (10) begin + ok = obj.randomize() with { mode == 4'd2; }; + `checkd(ok, 1); + `checkh(obj.address[0], 1'b0); + `checkh(obj.address[31], 1'b0); + end + + repeat (10) begin + ok = obj.randomize() with { mode == 4'd3; a == 4'h1; }; + `checkd(ok, 1); + `checkh(obj.b, 4'h7); + end + + repeat (10) begin + ok = obj.randomize() with { mode == 4'd4; a == 4'h1; }; + `checkd(ok, 1); + `checkh(obj.b, 4'ha); + end + repeat (10) begin + ok = obj.randomize() with { mode == 4'd4; a == 4'h2; }; + `checkd(ok, 1); + `checkh(obj.b, 4'hb); + end + + repeat (10) begin + ok = obj.randomize() with { mode == 4'd5; }; + `checkd(ok, 1); + foreach (obj.arr[i]) `checkh(obj.arr[i] < 8'h40, 1'b1); + end + + repeat (10) begin + ok = obj.randomize() with { mode == 4'd6; }; + `checkd(ok, 1); + `checkh(obj.uarr[0] == obj.uarr[1], 1'b0); + `checkh(obj.uarr[0] == obj.uarr[2], 1'b0); + `checkh(obj.uarr[1] == obj.uarr[2], 1'b0); + end + + repeat (10) begin + ok = obj.randomize() with { mode == 4'd7; }; + `checkd(ok, 1); + `checkh(obj.b, 4'hd); + end + + repeat (10) begin + ok = obj.randomize() with { mode == 4'd8; a == 4'h0; }; + `checkd(ok, 1); + `checkh(obj.b, 4'h5); + end + + ds = new(); + ds.override_flag = 1'b0; + repeat (10) begin + ok = ds.randomize(); + `checkd(ok, 1); + `checkh(ds.x, 4'h5); + end + ds.override_flag = 1'b1; + repeat (10) begin + ok = ds.randomize(); + `checkd(ok, 1); + `checkh(ds.x, 4'hc); + end + + begin + automatic DisSoftMember dsm = new(); + dsm.override_flag = 1'b0; + repeat (10) begin + ok = dsm.randomize(); + `checkd(ok, 1); + `checkh(dsm.inner.y, 4'h5); + end + dsm.override_flag = 1'b1; + repeat (10) begin + ok = dsm.randomize(); + `checkd(ok, 1); + `checkh(dsm.inner.y, 4'hc); + end + end + + begin + automatic DisSoftBothArms dba = new(); + // pick==0: hard a==1 fires (then arm); hoisted disable_soft(a) runs + dba.pick = 2'd0; + repeat (10) begin + ok = dba.randomize(); + `checkd(ok, 1); + `checkh(dba.a, 4'h1); + end + // pick==1: hard b==2 fires (else arm); hoisted disable_soft(b) runs + dba.pick = 2'd1; + repeat (10) begin + ok = dba.randomize(); + `checkd(ok, 1); + `checkh(dba.b, 4'h2); + end + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule