diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 12cdba74d..caea970f0 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -3998,6 +3998,23 @@ class RandomizeVisitor final : public VNVisitor { } } + // Rewrite a LogIf-of-Dist chain into nested AstConstraintIf. The outermost + // AstLogIf shell is left for the caller's AstConstraintExpr to free; inner + // shells are deleted here once their children are transplanted. + AstConstraintIf* liftLogIfChainToConstraintIf(AstLogIf* logIfp) { + FileLine* const fl = logIfp->fileline(); + AstNodeExpr* const condp = logIfp->lhsp()->unlinkFrBack(); + AstNodeExpr* const rhsp = logIfp->rhsp()->unlinkFrBack(); + AstNode* thenBodyp; + if (AstLogIf* const innerLogIfp = VN_CAST(rhsp, LogIf)) { + thenBodyp = liftLogIfChainToConstraintIf(innerLogIfp); + VL_DO_DANGLING(pushDeletep(innerLogIfp), innerLogIfp); + } else { + thenBodyp = new AstConstraintExpr{fl, rhsp}; + } + return new AstConstraintIf{fl, condp, thenBodyp, nullptr}; + } + // Replace AstDist with weighted bucket selection via AstConstraintIf chain. // Supports both constant and variable weight expressions. void lowerDistConstraints(AstTask* taskp, AstNode* constrItemsp) { @@ -4019,6 +4036,25 @@ class RandomizeVisitor final : public VNVisitor { AstConstraintExpr* const constrExprp = VN_CAST(itemp, ConstraintExpr); if (!constrExprp) continue; + + // `cond -> x dist {...}` parses as ConstraintExpr(LogIf(cond, Dist)). + // This pass only scans ConstraintExpr/If/Foreach for Dist, so lift the + // LogIf chain into nested ConstraintIf first. Chains like + // `a -> b -> dist` nest accordingly. + if (AstLogIf* const topLogIfp = VN_CAST(constrExprp->exprp(), LogIf)) { + AstNode* chainEndp = topLogIfp->rhsp(); + while (AstLogIf* const innerp = VN_CAST(chainEndp, LogIf)) { + chainEndp = innerp->rhsp(); + } + if (VN_IS(chainEndp, Dist)) { + AstConstraintIf* const liftedp = liftLogIfChainToConstraintIf(topLogIfp); + constrExprp->replaceWith(liftedp); + VL_DO_DANGLING(pushDeletep(constrExprp), constrExprp); + lowerDistConstraints(taskp, liftedp->thensp()); + continue; + } + } + AstDist* const distp = VN_CAST(constrExprp->exprp(), Dist); if (!distp) continue; diff --git a/test_regress/t/t_randomize_dist_implication.py b/test_regress/t/t_randomize_dist_implication.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_randomize_dist_implication.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_randomize_dist_implication.v b/test_regress/t/t_randomize_dist_implication.v new file mode 100644 index 000000000..39c18c1e9 --- /dev/null +++ b/test_regress/t/t_randomize_dist_implication.v @@ -0,0 +1,84 @@ +// 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 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 + +class DistScalar; + rand bit [7:0] x; + rand bit mode; + rand bit [1:0] mode2; + constraint force_cond { mode == 1'b0; mode2 == 2'b00; } + constraint c { + if (mode) { + x dist { 8'd0 := 1, 8'd255 := 3 }; + } else { + (mode2 == '0) -> x dist { 8'd0 := 3, 8'd255 := 1 }; + } + } +endclass + +class DistChain; + rand bit [7:0] x; + rand bit a; + rand bit b; + constraint force_ab { a == 1'b1; b == 1'b1; } + constraint c { + a -> b -> x dist { 8'd0 := 3, 8'd255 := 1 }; + } +endclass + +class DistForeachImpl; + rand bit [7:0] arr [4]; + rand bit enb; + constraint force_enb { enb == 1'b1; } + constraint c { + foreach (arr[i]) { + enb -> arr[i] dist { 8'd10 := 1, 8'd20 := 1, 8'd30 := 1 }; + } + } +endclass + +module t; + DistScalar obj; + DistChain ch; + DistForeachImpl fa; + + initial begin + int p; + obj = new; + ch = new; + fa = new; + + repeat (20) begin + p = obj.randomize(); + `checkd(p, 1); + // Implication is forced to fire by force_cond; x must be a bucket value. + `checkd(obj.mode, 1'b0); + `checkd(obj.mode2, 2'b00); + `checkd((obj.x == 8'd0) || (obj.x == 8'd255), 1'b1); + + p = ch.randomize(); + `checkd(p, 1); + `checkd(ch.a, 1'b1); + `checkd(ch.b, 1'b1); + `checkd((ch.x == 8'd0) || (ch.x == 8'd255), 1'b1); + + p = fa.randomize(); + `checkd(p, 1); + `checkd(fa.enb, 1'b1); + foreach (fa.arr[i]) begin + `checkd((fa.arr[i] == 8'd10) || (fa.arr[i] == 8'd20) + || (fa.arr[i] == 8'd30), 1'b1); + end + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule