From 84350859e07d7ca4efbdc9295a1f951bbdc293e0 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Wed, 11 Feb 2026 14:19:25 +0100 Subject: [PATCH] Support System Functions in Constraint Blocks (#7028) (#7036) --- src/V3Randomize.cpp | 171 +++++++++++++++++- .../t/t_constraint_countbits_unsup.out | 5 + .../t/t_constraint_countbits_unsup.py | 16 ++ test_regress/t/t_constraint_countbits_unsup.v | 22 +++ test_regress/t/t_constraint_sysfunc.py | 21 +++ test_regress/t/t_constraint_sysfunc.v | 98 ++++++++++ test_regress/t/t_constraint_unsup.out | 6 +- test_regress/t/t_constraint_unsup.v | 18 +- 8 files changed, 336 insertions(+), 21 deletions(-) create mode 100644 test_regress/t/t_constraint_countbits_unsup.out create mode 100755 test_regress/t/t_constraint_countbits_unsup.py create mode 100644 test_regress/t/t_constraint_countbits_unsup.v create mode 100755 test_regress/t/t_constraint_sysfunc.py create mode 100644 test_regress/t/t_constraint_sysfunc.v diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 75fc3ed21..b72b7c3ed 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -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()); diff --git a/test_regress/t/t_constraint_countbits_unsup.out b/test_regress/t/t_constraint_countbits_unsup.out new file mode 100644 index 000000000..63be029bc --- /dev/null +++ b/test_regress/t/t_constraint_countbits_unsup.out @@ -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 diff --git a/test_regress/t/t_constraint_countbits_unsup.py b/test_regress/t/t_constraint_countbits_unsup.py new file mode 100755 index 000000000..b7449248c --- /dev/null +++ b/test_regress/t/t_constraint_countbits_unsup.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: 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() diff --git a/test_regress/t/t_constraint_countbits_unsup.v b/test_regress/t/t_constraint_countbits_unsup.v new file mode 100644 index 000000000..6e9928862 --- /dev/null +++ b/test_regress/t/t_constraint_countbits_unsup.v @@ -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 diff --git a/test_regress/t/t_constraint_sysfunc.py b/test_regress/t/t_constraint_sysfunc.py new file mode 100755 index 000000000..9aaadb3d0 --- /dev/null +++ b/test_regress/t/t_constraint_sysfunc.py @@ -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() diff --git a/test_regress/t/t_constraint_sysfunc.v b/test_regress/t/t_constraint_sysfunc.v new file mode 100644 index 000000000..73c1249ee --- /dev/null +++ b/test_regress/t/t_constraint_sysfunc.v @@ -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 diff --git a/test_regress/t/t_constraint_unsup.out b/test_regress/t/t_constraint_unsup.out index 2708313a4..5f867038d 100644 --- a/test_regress/t/t_constraint_unsup.out +++ b/test_regress/t/t_constraint_unsup.out @@ -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 diff --git a/test_regress/t/t_constraint_unsup.v b/test_regress/t/t_constraint_unsup.v index 55dc15bc3..8e6d855af 100644 --- a/test_regress/t/t_constraint_unsup.v +++ b/test_regress/t/t_constraint_unsup.v @@ -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