The lowerDistConstraints() function was not recursing into ConstraintIf
nodes, causing dist operators inside if-else blocks to remain unlowered
and trigger an internal error when ConstraintExprVisitor encountered them.
Fix by adding recursive handling of ConstraintIf nodes in lowerDistConstraints:
- Check for AstConstraintIf nodes before AstConstraintExpr
- Recursively process thensp() and elsesp() branches
- This ensures all dist operators are lowered regardless of nesting
Test case: t_randomize_dist_conditional.v demonstrates conditional dist:
constraint c {
if (randd) {
x dist { 8'd0 := 1, 8'd255 := 3 }; // 25% / 75%
} else {
x dist { 8'd0 := 3, 8'd255 := 1 }; // 75% / 25%
}
}
Fixes #7221
This commit is contained in:
parent
1b2b8afdc1
commit
2046879beb
|
|
@ -3739,6 +3739,14 @@ class RandomizeVisitor final : public VNVisitor {
|
|||
void lowerDistConstraints(AstTask* taskp, AstNode* constrItemsp) {
|
||||
for (AstNode *nextip, *itemp = constrItemsp; itemp; itemp = nextip) {
|
||||
nextip = itemp->nextp();
|
||||
|
||||
// Recursively handle ConstraintIf nodes (dist can be inside if/else)
|
||||
if (AstConstraintIf* const cifp = VN_CAST(itemp, ConstraintIf)) {
|
||||
if (cifp->thensp()) lowerDistConstraints(taskp, cifp->thensp());
|
||||
if (cifp->elsesp()) lowerDistConstraints(taskp, cifp->elsesp());
|
||||
continue;
|
||||
}
|
||||
|
||||
AstConstraintExpr* const constrExprp = VN_CAST(itemp, ConstraintExpr);
|
||||
if (!constrExprp) continue;
|
||||
AstDist* const distp = VN_CAST(constrExprp->exprp(), Dist);
|
||||
|
|
|
|||
|
|
@ -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: 2025 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()
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain
|
||||
// SPDX-FileCopyrightText: 2026
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
`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);
|
||||
// Test constraint with conditional dist operator
|
||||
|
||||
class DistScalar;
|
||||
rand bit [7:0] x;
|
||||
rand bit mode; // 0: favor zeros (weight 3:1), 1: favor max (weight 1:3)
|
||||
constraint c {
|
||||
if (mode) {
|
||||
x dist { 8'd0 := 1, 8'd255 := 3 }; // Favor 255
|
||||
} else {
|
||||
x dist { 8'd0 := 3, 8'd255 := 1 }; // Favor 0
|
||||
}
|
||||
}
|
||||
|
||||
function void display(int trial);
|
||||
$display("Trial %0d: mode=%b, x=%0d (0x%02x)", trial, mode, x, x);
|
||||
endfunction
|
||||
endclass
|
||||
|
||||
module t;
|
||||
DistScalar obj;
|
||||
int mode1_zeros = 0; // Count of zeros when mode=1 (favor 255)
|
||||
int mode1_maxes = 0; // Count of 255s when mode=1 (favor 255)
|
||||
int mode0_zeros = 0; // Count of zeros when mode=0 (favor 0)
|
||||
int mode0_maxes = 0; // Count of 255s when mode=0 (favor 0)
|
||||
int mode1_trials = 0; // Total trials with mode=1
|
||||
int mode0_trials = 0; // Total trials with mode=0
|
||||
|
||||
// Verify distribution ratio matches expected range
|
||||
function void check_distribution(int trials, int match_count, string match_name,
|
||||
int expected_pct, string mode_name);
|
||||
int actual_pct;
|
||||
int lower_bound;
|
||||
int upper_bound;
|
||||
|
||||
if (trials <= 0) return;
|
||||
|
||||
actual_pct = (match_count * 100) / trials;
|
||||
$display(" %s %s ratio: %0d/%0d = %0d%% (expected ~%0d%%)",
|
||||
mode_name, match_name, match_count, trials, actual_pct, expected_pct);
|
||||
|
||||
// Allow +/-15% deviation from expected ratio
|
||||
lower_bound = expected_pct - 15;
|
||||
upper_bound = expected_pct + 15;
|
||||
|
||||
if (actual_pct >= lower_bound && actual_pct <= upper_bound) begin
|
||||
$display("Distribution OK");
|
||||
end else begin
|
||||
$display("WARNING: Distribution appears off (expected %0d+/-15%%)", expected_pct);
|
||||
$stop;
|
||||
end
|
||||
endfunction
|
||||
|
||||
initial begin
|
||||
int p;
|
||||
obj = new;
|
||||
|
||||
// Run multiple trials to verify distribution
|
||||
for (int i = 0; i < 1000; i++) begin
|
||||
p = obj.randomize();
|
||||
`checkd(p, 1);
|
||||
|
||||
if (i < 10) obj.display(i);
|
||||
|
||||
// Track distribution based on mode value
|
||||
if (obj.mode) begin
|
||||
mode1_trials++;
|
||||
if (obj.x == 8'd0) mode1_zeros++;
|
||||
else if (obj.x == 8'd255) mode1_maxes++;
|
||||
end else begin
|
||||
mode0_trials++;
|
||||
if (obj.x == 8'd0) mode0_zeros++;
|
||||
else if (obj.x == 8'd255) mode0_maxes++;
|
||||
end
|
||||
end
|
||||
|
||||
$display("");
|
||||
$display("Statistics after 1000 trials:");
|
||||
$display(" Mode 1 (favor 255): trials=%0d, zeros=%0d, maxes=%0d",
|
||||
mode1_trials, mode1_zeros, mode1_maxes);
|
||||
$display(" Mode 0 (favor 0): trials=%0d, zeros=%0d, maxes=%0d",
|
||||
mode0_trials, mode0_zeros, mode0_maxes);
|
||||
|
||||
$display("");
|
||||
$display("Distribution Verification:");
|
||||
// Mode 1: x dist { 0 := 1, 255 := 3 } means 25% for 0, 75% for 255
|
||||
check_distribution(mode1_trials, mode1_maxes, "maxes", 75, "Mode 1");
|
||||
|
||||
// Mode 0: x dist { 0 := 3, 255 := 1 } means 75% for 0, 25% for 255
|
||||
check_distribution(mode0_trials, mode0_zeros, "zeros", 75, "Mode 0");
|
||||
|
||||
$display("");
|
||||
$display("PASSED: Conditional dist constraint test");
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
Loading…
Reference in New Issue