Support System Functions in Constraint Blocks (#7028) (#7036)

This commit is contained in:
Yilou Wang 2026-02-11 14:19:25 +01:00 committed by GitHub
parent 5d12ae3a2f
commit 84350859e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 336 additions and 21 deletions

View File

@ -1143,15 +1143,15 @@ class ConstraintExprVisitor final : public VNVisitor {
if (isGlobalConstrained && !nodep->backp()) VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
}
void visit(AstCountOnes* nodep) override {
// Convert it to (x & 1) + ((x & 2) >> 1) + ((x & 4) >> 2) + ...
FileLine* const fl = nodep->fileline();
AstNodeExpr* const argp = nodep->lhsp()->unlinkFrBack();
V3Number numOne{nodep, argp->width(), 1};
// Build popcount expansion: (x & 1) + ((x & 2) >> 1) + ...
// argp is consumed; caller must clone if reusing.
AstNodeExpr* buildCountOnesExpansion(FileLine* fl, AstNodeExpr* argp,
AstNodeExpr* dtypeNodep) {
V3Number numOne{fl, argp->width(), 1};
AstNodeExpr* sump = new AstAnd{fl, argp, new AstConst{fl, numOne}};
sump->user1(true);
for (int i = 1; i < argp->width(); i++) {
V3Number numBitMask{nodep, argp->width(), 0};
V3Number numBitMask{fl, argp->width(), 0};
numBitMask.setBit(i, 1);
AstAnd* const andp
= new AstAnd{fl, argp->cloneTreePure(false), new AstConst{fl, numBitMask}};
@ -1159,11 +1159,17 @@ class ConstraintExprVisitor final : public VNVisitor {
AstShiftR* const shiftp = new AstShiftR{
fl, andp, new AstConst{fl, AstConst::WidthedValue{}, argp->width(), (uint32_t)i}};
shiftp->user1(true);
shiftp->dtypeFrom(nodep);
sump = new AstAdd{nodep->fileline(), sump, shiftp};
shiftp->dtypeFrom(dtypeNodep);
sump = new AstAdd{fl, sump, shiftp};
sump->user1(true);
}
// Restore the original width
return sump;
}
void visit(AstCountOnes* nodep) override {
FileLine* const fl = nodep->fileline();
AstNodeExpr* const argp = nodep->lhsp()->unlinkFrBack();
AstNodeExpr* sump = buildCountOnesExpansion(fl, argp, nodep);
if (nodep->width() > sump->width()) {
sump = new AstExtend{fl, sump, nodep->width()};
sump->user1(true);
@ -1187,6 +1193,153 @@ class ConstraintExprVisitor final : public VNVisitor {
VL_DO_DANGLING(nodep->deleteTree(), nodep);
iterate(neqp);
}
void visit(AstOneHot* nodep) override {
if (editFormat(nodep)) return;
// $onehot(x) = (x != 0) && ((x & (x-1)) == 0)
FileLine* const fl = nodep->fileline();
AstNodeExpr* const argp = nodep->lhsp()->unlinkFrBack();
const int w = argp->width();
V3Number numZero{fl, w, 0};
AstNeq* const neZerop
= new AstNeq{fl, argp->cloneTreePure(false), new AstConst{fl, numZero}};
neZerop->user1(true);
V3Number numOne{fl, w, 1};
AstSub* const subp = new AstSub{fl, argp->cloneTreePure(false), new AstConst{fl, numOne}};
subp->dtypeFrom(argp);
subp->user1(true);
AstAnd* const andp = new AstAnd{fl, argp, subp};
andp->dtypeFrom(argp);
andp->user1(true);
V3Number numZero2{fl, w, 0};
AstEq* const eqZerop = new AstEq{fl, andp, new AstConst{fl, numZero2}};
eqZerop->user1(true);
AstLogAnd* const resultp = new AstLogAnd{fl, neZerop, eqZerop};
resultp->user1(true);
nodep->replaceWith(resultp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
iterate(resultp);
}
void visit(AstOneHot0* nodep) override {
if (editFormat(nodep)) return;
// $onehot0(x) = (x & (x-1)) == 0
FileLine* const fl = nodep->fileline();
AstNodeExpr* const argp = nodep->lhsp()->unlinkFrBack();
const int w = argp->width();
V3Number numOne{fl, w, 1};
AstSub* const subp = new AstSub{fl, argp->cloneTreePure(false), new AstConst{fl, numOne}};
subp->dtypeFrom(argp);
subp->user1(true);
AstAnd* const andp = new AstAnd{fl, argp, subp};
andp->dtypeFrom(argp);
andp->user1(true);
V3Number numZero{fl, w, 0};
AstEq* const eqp = new AstEq{fl, andp, new AstConst{fl, numZero}};
eqp->user1(true);
nodep->replaceWith(eqp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
iterate(eqp);
}
void visit(AstCountBits* nodep) override {
if (editFormat(nodep)) return;
FileLine* const fl = nodep->fileline();
bool countOnes = false;
bool countZeros = false;
for (AstNodeExpr* ctrlp : {nodep->rhsp(), nodep->thsp(), nodep->fhsp()}) {
const AstConst* const cp = VN_CAST(ctrlp, Const);
if (!cp) {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: non-constant control in $countbits inside constraint");
AstConst* const zerop
= new AstConst{fl, AstConst::WidthedValue{}, nodep->width(), 0u};
nodep->replaceWith(zerop);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
return;
}
if (cp->num().bitIs1(0) && !countOnes)
countOnes = true;
else if (cp->num().bitIs0(0) && !countZeros)
countZeros = true;
}
AstNodeExpr* const argp = nodep->lhsp()->unlinkFrBack();
const int argWidth = argp->width();
AstNodeExpr* sump = nullptr;
if (countOnes && countZeros) {
// ones + zeros = width for 2-state types
sump = new AstConst{fl, AstConst::WidthedValue{}, nodep->width(), (uint32_t)argWidth};
VL_DO_DANGLING(argp->deleteTree(), argp);
} else if (countOnes) {
sump = buildCountOnesExpansion(fl, argp, nodep);
} else if (countZeros) {
// width - countones(x)
AstNodeExpr* const onesCountp = buildCountOnesExpansion(fl, argp, nodep);
V3Number widthVal{nodep, onesCountp->width(), (uint32_t)argWidth};
sump = new AstSub{fl, new AstConst{fl, widthVal}, onesCountp};
sump->dtypeFrom(onesCountp);
sump->user1(true);
} else {
sump = new AstConst{fl, AstConst::WidthedValue{}, nodep->width(), 0u};
VL_DO_DANGLING(argp->deleteTree(), argp);
}
if (nodep->width() > sump->width()) {
sump = new AstExtend{fl, sump, nodep->width()};
sump->user1(true);
} else if (nodep->width() < sump->width()) {
sump = new AstSel{fl, sump, 0, nodep->width()};
sump->user1(true);
}
nodep->replaceWith(sump);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
iterate(sump);
}
void visit(AstCLog2* nodep) override {
if (editFormat(nodep)) return;
// $clog2(x): ITE chain (x<=1)?0 : (x<=2)?1 : ... : argWidth
FileLine* const fl = nodep->fileline();
AstNodeExpr* const argp = nodep->lhsp()->unlinkFrBack();
const int argWidth = argp->width();
const int resultWidth = nodep->width();
AstNodeExpr* resultp
= new AstConst{fl, AstConst::WidthedValue{}, resultWidth, (uint32_t)argWidth};
for (int k = argWidth - 1; k >= 0; k--) {
V3Number threshold{fl, argWidth, 0};
if (k < 32)
threshold.setLong(1ULL << k);
else
threshold.setBit(k, 1);
AstLte* const ltep
= new AstLte{fl, argp->cloneTreePure(false), new AstConst{fl, threshold}};
ltep->user1(true);
AstConst* const valuep
= new AstConst{fl, AstConst::WidthedValue{}, resultWidth, (uint32_t)k};
resultp = new AstCond{fl, ltep, valuep, resultp};
resultp->dtypeChgWidthSigned(resultWidth, resultWidth, VSigning::SIGNED);
resultp->user1(true);
}
VL_DO_DANGLING(argp->deleteTree(), argp);
nodep->replaceWith(resultp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
iterate(resultp);
}
void visit(AstNodeBiop* nodep) override {
if (editFormat(nodep)) return;
editSMT(nodep, nodep->lhsp(), nodep->rhsp());

View File

@ -0,0 +1,5 @@
%Error-UNSUPPORTED: t/t_constraint_countbits_unsup.v:12:21: Unsupported: non-constant control in $countbits inside constraint
12 | constraint cons { $countbits(value, ctrl) == 3; }
| ^~~~~~~~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Exiting due to

View File

@ -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: 2024 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.lint(fails=test.vlt_all, expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,22 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2026 by PlanV GmbH.
// SPDX-License-Identifier: CC0-1.0
// Test: non-constant control in $countbits inside constraint (unsupported)
class Packet;
rand bit [7:0] value;
bit ctrl;
constraint cons { $countbits(value, ctrl) == 3; }
endclass
module t;
Packet p;
initial begin
p = new;
void'(p.randomize());
end
endmodule

View File

@ -0,0 +1,21 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2026 by Wilson Snyder. 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-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()

View File

@ -0,0 +1,98 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2026 by PlanV GmbH.
// SPDX-License-Identifier: CC0-1.0
// Test: System functions ($onehot, $onehot0, $countbits, $clog2) inside
// constraint blocks (IEEE 1800-2017 Section 18.5.12)
class test_onehot;
rand bit [7:0] value;
constraint c_hot { $onehot(value); }
endclass
class test_onehot0;
rand bit [7:0] value;
constraint c_hot0 { $onehot0(value); }
endclass
class test_countbits_ones;
rand bit [7:0] value;
constraint c_bits { $countbits(value, '1) == 3; }
endclass
class test_countbits_zeros;
rand bit [7:0] value;
constraint c_bits { $countbits(value, '0) == 6; } // 6 zeros = 2 ones
endclass
class test_clog2;
rand bit [7:0] data_width;
rand bit [7:0] addr_bits;
constraint c_log { addr_bits == 8'($clog2(data_width)); }
constraint c_nonzero { data_width > 0; }
endclass
module t;
initial begin
automatic test_onehot oh = new;
automatic test_onehot0 oh0 = new;
automatic test_countbits_ones cb1 = new;
automatic test_countbits_zeros cb0 = new;
automatic test_clog2 cl = new;
automatic bit ok = 1'b1;
// Test $onehot: exactly one bit set
repeat(20) begin
if (oh.randomize() == 0) $fatal(1, "$onehot randomize failed");
if ($onehot(oh.value) !== 1'b1) begin
$display("FAIL: $onehot value=%08b, $onehot=%0b", oh.value, $onehot(oh.value));
ok = 1'b0;
end
end
// Test $onehot0: zero or one bit set
repeat(20) begin
if (oh0.randomize() == 0) $fatal(1, "$onehot0 randomize failed");
if ($onehot0(oh0.value) !== 1'b1) begin
$display("FAIL: $onehot0 value=%08b, $onehot0=%0b", oh0.value, $onehot0(oh0.value));
ok = 1'b0;
end
end
// Test $countbits counting ones: exactly 3 ones
repeat(20) begin
if (cb1.randomize() == 0) $fatal(1, "$countbits('1) randomize failed");
if ($countbits(cb1.value, '1) != 3) begin
$display("FAIL: $countbits('1) value=%08b, count=%0d",
cb1.value, $countbits(cb1.value, '1));
ok = 1'b0;
end
end
// Test $countbits counting zeros: exactly 6 zeros (= 2 ones)
repeat(20) begin
if (cb0.randomize() == 0) $fatal(1, "$countbits('0) randomize failed");
if ($countbits(cb0.value, '0) != 6) begin
$display("FAIL: $countbits('0) value=%08b, zeros=%0d ones=%0d",
cb0.value, $countbits(cb0.value, '0), $countbits(cb0.value, '1));
ok = 1'b0;
end
end
// Test $clog2: addr_bits == $clog2(data_width)
repeat(20) begin
if (cl.randomize() == 0) $fatal(1, "$clog2 randomize failed");
if (cl.addr_bits != 8'($clog2(cl.data_width))) begin
$display("FAIL: $clog2 data_width=%0d, addr_bits=%0d, expected=%0d",
cl.data_width, cl.addr_bits, $clog2(cl.data_width));
ok = 1'b0;
end
end
if (ok) $display("All tests passed");
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -1,5 +1,5 @@
%Error-UNSUPPORTED: t/t_constraint_unsup.v:9:22: Unsupported expression inside constraint
9 | constraint cons { $onehot(m_one) == 1; }
| ^~~~~~~
%Error-UNSUPPORTED: t/t_constraint_unsup.v:9:27: Unsupported expression inside constraint
9 | constraint cons { m_one ** 2 > 0; }
| ^~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Exiting due to

View File

@ -1,19 +1,19 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain
// SPDX-FileCopyrightText: 2025 Antmicro
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2026 by PlanV GmbH.
// SPDX-License-Identifier: CC0-1.0
class Packet;
rand int m_one;
constraint cons { $onehot(m_one) == 1; }
rand int m_one;
constraint cons { m_one ** 2 > 0; }
endclass
module t;
Packet p;
Packet p;
initial begin
p = new;
void'(p.randomize());
end
initial begin
p = new;
void'(p.randomize());
end
endmodule