Support randomize() with (identifier_list) {constraint_block} (#7486) (#7507)

Fixes #7486.
This commit is contained in:
Yilou Wang 2026-04-28 12:10:53 +02:00 committed by GitHub
parent 5d1b4fe8a8
commit 327fc4ffbe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 580 additions and 48 deletions

View File

@ -537,6 +537,12 @@ class AstWith final : public AstNode {
// @astgen op2 := valueArgRefp : AstLambdaArgRef
// @astgen op3 := exprp : List[AstNode] // Pins, expression and constraints
// TODO: Separate expression and constraints
private:
// 'with (identifier_list) {...}' restricted form (IEEE 1800-2023 18.7).
bool m_restricted = false;
bool m_validated = false; // identifier_list typo / unused checks already run
std::set<std::string> m_restrictedNames;
public:
AstWith(FileLine* fl, AstLambdaArgRef* indexArgRefp, AstLambdaArgRef* valueArgRefp,
AstNode* exprp)
@ -553,6 +559,18 @@ public:
BROKEN_RTN(!valueArgRefp()); // varp needed to know lambda's arg dtype
return nullptr;
}
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
void restricted(bool flag) { m_restricted = flag; }
bool restricted() const { return m_restricted; }
bool validated() const { return m_validated; }
void validated(bool flag) { m_validated = flag; }
void addRestrictedName(const std::string& name) { m_restrictedNames.insert(name); }
const std::set<std::string>& restrictedNames() const { return m_restrictedNames; }
// True if 'name' binds into the randomize() target class.
bool nameResolvesToTarget(const std::string& name) const {
return !m_restricted || m_restrictedNames.count(name);
}
};
// === AstNodeExpr ===
@ -2821,6 +2839,10 @@ class AstWithParse final : public AstNodeExpr {
// @astgen op1 := funcrefp : AstNodeExpr
// @astgen op3 := exprsp : List[AstNodeExpr] // With's parenthesis part
// @astgen op4 := constraintsp : List[AstNode] // With's braces part
private:
// True for the 'with (identifier_list) {...}' form, including 'with () {...}'.
bool m_restricted = false;
public:
AstWithParse(FileLine* fl, AstNodeExpr* funcrefp, AstNodeExpr* exprsp,
AstNode* constraintsp = nullptr)
@ -2831,6 +2853,8 @@ public:
}
ASTGEN_MEMBERS_AstWithParse;
bool sameNode(const AstNode* /*samep*/) const override { return true; }
bool restricted() const { return m_restricted; }
void restricted(bool flag) { m_restricted = flag; }
string emitVerilog() override { V3ERROR_NA_RETURN(""); }
string emitC() override { V3ERROR_NA_RETURN(""); }

View File

@ -3558,3 +3558,30 @@ const char* AstNot::widthMismatch() const VL_MT_STABLE {
BROKEN_RTN(lhsp()->widthMin() != widthMin());
return nullptr;
}
void AstWith::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (m_restricted) {
str << " [RESTRICTED={";
bool first = true;
for (const std::string& n : m_restrictedNames) {
if (!first) str << ",";
str << n;
first = false;
}
str << "}]";
}
}
void AstWith::dumpJson(std::ostream& str) const {
dumpJsonBoolIf(str, "restricted", m_restricted);
if (m_restricted) {
std::string joined;
bool first = true;
for (const std::string& n : m_restrictedNames) {
if (!first) joined += ",";
joined += n;
first = false;
}
dumpJsonStr(str, "restrictedNames", joined);
}
dumpJsonGen(str);
}

View File

@ -2194,24 +2194,39 @@ class LinkDotFindVisitor final : public VNVisitor {
}
}
// Type depends on the method used, let V3Width figure it out later
if (nodep->exprsp()
|| nodep->constraintsp()) { // Else empty expression and pretend no "with"
AstNode* exprOrConstraintsp = nullptr;
if (nodep->exprsp() && nodep->constraintsp()) {
// When support this probably should change AstWith to separate out
// the expr from the constraint equation using separate op2/op3 similar
// to AstWithParse
nodep->v3warn(E_UNSUPPORTED, "Unsupported: 'randomize with (...) {...}'");
} else if (nodep->exprsp())
exprOrConstraintsp = nodep->exprsp()->unlinkFrBackWithNext();
if (nodep->constraintsp())
exprOrConstraintsp = AstNode::addNext(
exprOrConstraintsp, nodep->constraintsp()->unlinkFrBackWithNext());
// 'with (...) { ... }' constraint_block form is randomize-only
// (IEEE 1800-2023 7.12 vs. 18.7); array methods take 'with (expr)'.
if (nodep->restricted() && funcrefp->name() != "randomize") {
nodep->v3error("'with (...) { ... }' constraint block is only valid for"
" randomize() (IEEE 1800-2023 18.7)");
funcrefp->addArgsp(argsp);
nodep->replaceWith(nodep->funcrefp()->unlinkFrBack());
VL_DO_DANGLING(nodep->deleteTree(), nodep);
return;
}
const bool restrictedRandomize = nodep->restricted();
if (nodep->exprsp() || nodep->constraintsp()
|| restrictedRandomize) { // Else empty expression and pretend no "with"
AstLambdaArgRef* const indexArgRefp
= new AstLambdaArgRef{argFl, name + "__DOT__index", true};
AstLambdaArgRef* const valueArgRefp = new AstLambdaArgRef{argFl, name, false};
AstWith* const newp
= new AstWith{nodep->fileline(), indexArgRefp, valueArgRefp, exprOrConstraintsp};
= new AstWith{nodep->fileline(), indexArgRefp, valueArgRefp, nullptr};
// Harvest the identifier_list into AstWith; grammar guarantees AstParseRef.
if (restrictedRandomize) {
newp->restricted(true);
while (AstNode* const itemp = nodep->exprsp()) {
newp->addRestrictedName(VN_AS(itemp, ParseRef)->name());
itemp->unlinkFrBack();
VL_DO_DANGLING(pushDeletep(itemp), itemp);
}
}
AstNode* exprOrConstraintsp = nullptr;
if (nodep->exprsp()) exprOrConstraintsp = nodep->exprsp()->unlinkFrBackWithNext();
if (nodep->constraintsp())
exprOrConstraintsp = AstNode::addNext(
exprOrConstraintsp, nodep->constraintsp()->unlinkFrBackWithNext());
newp->addExprp(exprOrConstraintsp); // addExprp() tolerates nullptr
funcrefp->withp(newp);
}
funcrefp->addArgsp(argsp);
@ -3082,7 +3097,9 @@ class LinkDotResolveVisitor final : public VNVisitor {
int m_modportNum = 0; // Uniqueify modport numbers
int m_indent = 0; // Indentation (tree depth) for debug
bool m_inSens = false; // True if in senitem
bool m_inWith = false; // True if in with
const AstWith* m_currentWithp = nullptr; // Enclosing 'with' (nullptr if none)
std::set<std::string>
m_restrictedNamesUsed; // Names from current 'with (id_list)' that resolved into target
bool m_genericIfaceModule = false; // True if in module containing generic interface
std::map<std::string, AstNode*> m_ifClassImpNames; // Names imported from interface class
std::set<AstClass*> m_extendsParam; // Classes that have a parameterized super class
@ -3911,7 +3928,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
VSymEnt* classSymp = getThisClassSymp();
// In 'randomize() with { this.member }', 'this' refers to randomized
// object, not the calling class (IEEE 1800-2023 18.7)
if (m_randSymp && m_inWith) classSymp = m_randSymp;
if (m_randSymp && m_currentWithp) classSymp = m_randSymp;
if (!classSymp) {
nodep->v3error("'this' used outside class (IEEE 1800-2023 8.11)");
m_ds.m_dotErr = true;
@ -4224,11 +4241,16 @@ class LinkDotResolveVisitor final : public VNVisitor {
VSymEnt* foundp;
string baddot;
VSymEnt* okSymp = nullptr;
if (m_randSymp) {
// Restricted 'with (id_list)': only id_list names bind into target class.
if (m_randSymp
&& (!m_currentWithp || m_currentWithp->nameResolvesToTarget(nodep->name()))) {
foundp = m_randSymp->findIdFlat(nodep->name());
if (foundp) {
if (m_currentWithp && m_currentWithp->restricted()) {
m_restrictedNamesUsed.insert(nodep->name());
}
if (!start) m_ds.m_dotPos = DP_MEMBER;
if (!m_inWith) {
if (!m_currentWithp) {
UASSERT_OBJ(m_randMethodCallp, nodep, "Expected to be under randomize()");
// This will start failing once complex expressions are allowed on the LHS
// of randomize() with args
@ -5174,9 +5196,14 @@ class LinkDotResolveVisitor final : public VNVisitor {
dotSymp = m_statep->findDotted(nodep->fileline(), dotSymp, nodep->dotted(), baddot,
okSymp, true); // Maybe nullptr
}
if (m_randSymp) {
// Restricted 'with (id_list)': only id_list names bind into target class.
if (m_randSymp
&& (!m_currentWithp || m_currentWithp->nameResolvesToTarget(nodep->name()))) {
VSymEnt* const foundp = m_randSymp->findIdFlat(nodep->name());
if (foundp && m_inWith) {
if (foundp && m_currentWithp) {
if (m_currentWithp->restricted()) {
m_restrictedNamesUsed.insert(nodep->name());
}
UINFO(9, indent() << "randomize-with fromSym " << foundp->nodep());
AstArg* argsp = nullptr;
if (nodep->argsp()) {
@ -5569,11 +5596,45 @@ class LinkDotResolveVisitor final : public VNVisitor {
UINFO(5, indent() << "visit " << nodep);
checkNoDot(nodep);
VL_RESTORER(m_curSymp);
VL_RESTORER(m_inWith);
VL_RESTORER(m_currentWithp);
VL_RESTORER(m_restrictedNamesUsed);
{
m_ds.m_dotSymp = m_curSymp = m_statep->getNodeSym(nodep);
m_inWith = true;
m_currentWithp = nodep;
// Validate the identifier_list once per AstWith (m_validated travels
// with cloneTree, so cloned witnesses skip the redundant check).
const bool firstVisit = !nodep->validated();
if (firstVisit) {
nodep->validated(true);
m_restrictedNamesUsed.clear();
// IEEE 1800-2023 18.7: each name in the identifier_list must designate
// a member of the randomize() target class.
if (nodep->restricted() && m_randSymp) {
for (const std::string& n : nodep->restrictedNames()) {
if (!m_randSymp->findIdFlat(n)) {
nodep->v3error("Identifier '"
<< n
<< "' in 'with (...)' identifier list is not a"
" member of the randomize() target class"
" (IEEE 1800-2023 18.7)");
m_restrictedNamesUsed.insert(n);
}
}
}
}
iterateChildren(nodep);
// Flag names that the user listed but never referenced in the constraint.
if (firstVisit && nodep->restricted()) {
for (const std::string& n : nodep->restrictedNames()) {
if (!m_restrictedNamesUsed.count(n)) {
nodep->v3warn(UNUSED, "Identifier '"
<< n
<< "' in 'with (...)' identifier list is"
" not referenced in the constraint"
" block");
}
}
}
}
m_ds.m_dotSymp = VL_RESTORER_PREV(m_curSymp);
}

View File

@ -4194,7 +4194,11 @@ task_subroutine_callNoMethod<nodeExprp>: // function_subroutine_callNoMethod
// // We implement randomize as a normal funcRef, since randomize isn't a keyword
// // Note yNULL is already part of expressions, so they come for free
| funcRef yWITH__CUR constraint_block { $$ = new AstWithParse{$2, $1, nullptr, $3}; }
| funcRef yWITH__PAREN_CUR '(' expr ')' constraint_block { $$ = new AstWithParse{$2, $1, $4, $6}; }
| funcRef yWITH__PAREN_CUR '(' inlineConstraintIdListE ')' constraint_block
{ AstWithParse* const withParsep
= new AstWithParse{$2, $1, $4, $6};
withParsep->restricted(true);
$$ = withParsep; }
;
function_subroutine_callNoMethod<nodeExprp>: // IEEE: function_subroutine_call (as function)
@ -4211,7 +4215,11 @@ function_subroutine_callNoMethod<nodeExprp>: // IEEE: function_subroutine
// // We implement randomize as a normal funcRef, since randomize isn't a keyword
// // Note yNULL is already part of expressions, so they come for free
| funcRef yWITH__CUR constraint_block { $$ = new AstWithParse{$2, $1, nullptr, $3}; }
| funcRef yWITH__PAREN_CUR '(' expr ')' constraint_block { $$ = new AstWithParse{$2, $1, $4, $6}; }
| funcRef yWITH__PAREN_CUR '(' inlineConstraintIdListE ')' constraint_block
{ AstWithParse* const withParsep
= new AstWithParse{$2, $1, $4, $6};
withParsep->restricted(true);
$$ = withParsep; }
;
system_t_stmt_call<nodeStmtp>: // IEEE: part of system_tf_call (as task returning statement)
@ -5407,6 +5415,21 @@ exprList<nodeExprp>:
| exprList ',' expr { $$ = $1->addNext($3); }
;
// identifier_list for 'with' in inline randomize constraints (IEEE 1800-2023 18.7).
// Only simple identifiers; non-identifier expressions are parse errors.
inlineConstraintIdList<nodeExprp>:
id { $$ = new AstParseRef{$<fl>1, *$1, nullptr, nullptr}; }
| inlineConstraintIdList ',' id { $$ = $1->addNext(
new AstParseRef{$<fl>3, *$3, nullptr, nullptr}); }
;
// Optional identifier_list. Empty 'with () {...}' differs from bare 'with {...}'
// via AstWithParse::restricted().
inlineConstraintIdListE<nodeExprp>:
/* empty */ { $$ = nullptr; }
| inlineConstraintIdList { $$ = $1; }
;
exprEListE<nodep>: // expression list with empty commas allowed
exprE { $$ = $1; }
| exprEListE ',' exprE { $$ = addNextNull($1, $3); }

View File

@ -682,10 +682,224 @@ module Vt_debug_emitv_t;
restrict (@(posedge clk) ##1 a[0]
);
endmodule
package Vt_debug_emitv_std;
class Vt_debug_emitv_semaphore;
int signed m_keyCount;
function new;
input int signed keyCount;
m_keyCount = keyCount;
endfunction
task put;
input int signed keyCount;
m_keyCount = (m_keyCount + keyCount);
endtask
task get;
input int signed keyCount;
while ((m_keyCount < keyCount)) begin
begin
???? // WAIT
(m_keyCount >= keyCount)end
end
m_keyCount = (m_keyCount - keyCount);
endtask
function try_get;
input int signed keyCount;
begin : label3
try_get = /*CRESET*/;
if ((m_keyCount >= keyCount)) begin
begin
m_keyCount = (m_keyCount -
keyCount);
try_get = 'sh1;
disable label3;
end
end
try_get = 'sh0;
disable label3;
end
endfunction
endclass
class Vt_debug_emitv_process;
typedef enum int signed{
FINISHED = 32'h0,
RUNNING = 32'h1,
WAITING = 32'h2,
SUSPENDED = 32'h3,
KILLED = 32'h4
} state;
VlProcessRef m_process;
function self;
???? // CLASSREFDTYPE 'process'
p
???? // CLASSREFDTYPE 'process'
;
begin : label4
self = /*CRESET*/;
p = new();
$c(p.m_process = vlProcess;);
self = p;
disable label4;
end
endfunction
task set_status;
input int signed s;
$c(m_process->state(s););
endtask
function status;
begin : label5
status = /*CRESET*/;
status = ($c(m_process->state()));
disable label5;
end
endfunction
task kill;
set_status(process::KILLED);
endtask
task suspend;
$error("std::process::suspend() not supported");
$stop;
endtask
task resume;
set_status(process::RUNNING);
endtask
task await;
???? // WAIT
((status() == process::FINISHED) || (status()
==
process::
KILLED))endtask
task killQueue;
ref
???? // CLASSREFDTYPE 'process'
processQueue[$]
???? // CLASSREFDTYPE 'process'
;
begin : unnamedblk1_1
integer signed __Vrepeat0;
__Vrepeat0 = processQueue.size();
while ((__Vrepeat0 > 32'h0)) begin
begin
kill();
end
__Vrepeat0 = (__Vrepeat0 - 32'h1);
end
end
endtask
???? // SYSTEMCSECTION
???? // SYSTEMCSECTION
???? // SYSTEMCSECTION
???? // SYSTEMCSECTION
???? // SYSTEMCSECTION
???? // SYSTEMCSECTION
???? // SYSTEMCSECTION
???? // SYSTEMCSECTION
???? // SYSTEMCSECTION
???? // SYSTEMCSECTION
???? // SYSTEMCSECTION
???? // SYSTEMCSECTION
???? // SYSTEMCSECTION
???? // SYSTEMCSECTION
???? // SYSTEMCSECTION
???? // SYSTEMCSECTION
???? // SYSTEMCSECTION
???? // SYSTEMCSECTION
function get_randstate;
string s;
begin : label6
get_randstate = /*CRESET*/;
s = string'($c(0));
$c(s = m_process->randstate(););
get_randstate = s;
disable label6;
end
endfunction
task set_randstate;
input string s;
$c(m_process->randstate(s););
endtask
function new;
endfunction
process::FINISHEDprocess::KILLEDprocess::RUNNINGprocess::SUSPENDEDprocess::WAITINGendclass
function randomize;
randomize = 'sh0;
endfunction
typedef struct {
string name;
int signed weight;
int signed goal;
string comment;
int signed at_least;
int signed auto_bin_max;
int signed cross_num_print_missing;
bit cross_retain_auto_bins;
bit detect_overlap;
bit per_instance;
bit get_inst_coverage;
} vl_covergroup_options_t;
typedef struct {
int signed weight;
int signed goal;
string comment;
int signed at_least;
int signed auto_bin_max;
bit detect_overlap;
} vl_coverpoint_options_t;
typedef struct {
int signed weight;
int signed goal;
string comment;
int signed at_least;
int signed cross_num_print_missing;
bit cross_retain_auto_bins;
} vl_cross_options_t;
typedef struct {
int signed weight;
int signed goal;
string comment;
bit strobe;
bit merge_instances;
bit distribute_first;
real real_interval;
} vl_covergroup_type_options_t;
typedef struct {
int signed weight;
int signed goal;
string comment;
real real_interval;
} vl_coverpoint_type_options_t;
typedef struct {
int signed weight;
int signed goal;
string comment;
} vl_cross_type_options_t;
endpackage
package Vt_debug_emitv___024unit;
class Vt_debug_emitv_Cls;
int signed member;
member = 'sh1;
int signed rmember1;
int signed rmember2;
task method;
if ((this != this)) begin
$stop;
@ -693,7 +907,23 @@ package Vt_debug_emitv___024unit;
endtask
function new;
endfunction
function randomize;
endfunction
endclass
function rand_restricted;
input
???? // CLASSREFDTYPE 'Cls'
obj
???? // CLASSREFDTYPE 'Cls'
;
input int signed member;
begin : label7
rand_restricted = randomize() with (
???? // CONSTRAINTEXPR
(item.rmember1 < member)) ;
disable label7;
end
endfunction
endpackage
interface Vt_debug_emitv_Iface;
input logic clk;
@ -712,13 +942,13 @@ module Vt_debug_emitv_sub;
endtask
function f;
input int signed v;
begin : label3
begin : label8
if ((v == 'sh0)) begin
f = 'sh21;
disable label3;
disable label8;
end
f = ({32'h1{{31'h0, v[2]}}} + 32'h1);
disable label3;
disable label8;
end
endfunction
real r;

View File

@ -21,11 +21,17 @@ endpackage
class Cls;
int member = 1;
rand int rmember1;
rand int rmember2;
function void method;
if (this != this) $stop;
endfunction
endclass
function int rand_restricted(Cls obj, int member);
return obj.randomize() with (rmember1, rmember2) { rmember1 < member; rmember2 < member; };
endfunction
interface Iface (
input clk
);

View File

@ -1,8 +0,0 @@
%Error-UNSUPPORTED: t/t_randomize_with_constraint.v:13:26: Unsupported: 'randomize with (...) {...}'
13 | return obj.randomize() with (
| ^~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error-UNSUPPORTED: t/t_randomize_with_constraint.v:21:26: Unsupported: 'randomize with (...) {...}'
21 | return obj.randomize() with (
| ^~~~
%Error: Exiting due to

View File

@ -9,8 +9,13 @@
import vltest_bootstrap
test.scenarios('vlt')
test.scenarios('simulator')
test.lint(fails=True, expect_filename=test.golden_filename)
if not test.have_solver:
test.skip("No constraint solver installed")
test.compile()
test.execute()
test.passes()

View File

@ -4,33 +4,105 @@
// SPDX-FileCopyrightText: 2025 Wilson Snyder
// 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);
`define check_range(gotv,minv,maxv) do if ((gotv) < (minv) || (gotv) > (maxv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d-%0d\n", `__FILE__,`__LINE__, (gotv), (minv), (maxv)); `stop; end while(0);
// verilog_format: on
class Cls;
rand int m_x;
int m_y = -1;
rand int m_z;
int y = -1; // class member named 'y' intentionally shadows the caller arg
int lo = -100; // class member named 'lo' intentionally shadows the caller arg
endclass
function int func1(Cls obj, int y);
return obj.randomize() with (
m_x) {
// 'y' is not in the list -> resolves to the caller arg (10), not class member (-1).
function int func_restricted(Cls obj, int y);
return obj.randomize() with (m_x) {
m_x > 0;
m_x < y;
};
endfunction
function int func2(Cls obj, int y);
return obj.randomize() with (
m_x) {
m_x > 0;
m_x < m_y;
// Multi-id list: 'lo' is also a class member but resolves to caller arg.
function int func_multi(Cls obj, int lo, int hi);
return obj.randomize() with (m_x, m_z) {
m_x > lo;
m_x < hi;
m_z > lo;
m_z < hi;
m_x < m_z;
};
endfunction
// Unrestricted baseline: no id list.
function int func_unrestricted(Cls obj);
return obj.randomize() with {
m_x > 0;
m_x < 10;
};
endfunction
// Empty id list: no name binds into the class; constraint references obj.* explicitly.
function automatic int func_empty(Cls obj, int lo, int hi);
// verilog_format: off
return obj.randomize() with () {
obj.m_x > lo;
obj.m_x < hi;
};
// verilog_format: on
endfunction
// 'local::y' bypasses class scope -> caller arg, not obj.y.
function int func_local_qual(Cls obj, int y);
obj.y = -42;
// verilog_format: off
return obj.randomize() with {
m_x > 0;
m_x < local::y;
};
// verilog_format: on
endfunction
// Consecutive restricted blocks must not leak each other's id list.
function automatic int func_sequential(Cls obj, int hi);
int rc = 1;
rc &= obj.randomize() with (m_x) { m_x > 0; m_x < hi; };
rc &= obj.randomize() with (m_z) { m_z > 0; m_z < hi; };
return rc;
endfunction
module t;
initial begin
Cls c;
int i;
c = new;
i = func1(c, 2);
i = func2(c, 2);
repeat (20) begin
i = func_restricted(c, 10);
`checkd(i, 1);
`check_range(c.m_x, 1, 9);
i = func_multi(c, 0, 50);
`checkd(i, 1);
`check_range(c.m_x, 1, 49);
`check_range(c.m_z, 1, 49);
`checkd(c.m_x < c.m_z, 1);
i = func_unrestricted(c);
`checkd(i, 1);
`check_range(c.m_x, 1, 9);
i = func_empty(c, 0, 9);
`checkd(i, 1);
`check_range(c.m_x, 1, 8);
i = func_local_qual(c, 8);
`checkd(i, 1);
`check_range(c.m_x, 1, 7);
i = func_sequential(c, 6);
`checkd(i, 1);
// Statement form: discards return value via void'.
void'(c.randomize() with (m_x) { m_x > 0; m_x < 5; });
`check_range(c.m_x, 1, 4);
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,10 @@
%Error: t/t_randomize_with_idlist_bad.v:14:26: Identifier 'm_z' in 'with (...)' identifier list is not a member of the randomize() target class (IEEE 1800-2023 18.7)
14 | return obj.randomize() with (m_x, m_z) { m_x > 0; };
| ^~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Warning-UNUSED: t/t_randomize_with_idlist_bad.v:19:26: Identifier 'm_y' in 'with (...)' identifier list is not referenced in the constraint block
19 | return obj.randomize() with (m_x, m_y) { m_x > 0; };
| ^~~~
... For warning description see https://verilator.org/warn/UNUSED?v=latest
... Use "/* verilator lint_off UNUSED */" and lint_on around source to disable this message.
%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: 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()

View File

@ -0,0 +1,30 @@
// 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 Cls;
rand int m_x;
rand int m_y;
endclass
// 'm_z' is not a member of Cls -- typo (rejected as IEEE 1800-2023 18.7 violation).
function int F_unknown(Cls obj);
return obj.randomize() with (m_x, m_z) { m_x > 0; };
endfunction
// 'm_y' is a class member but never referenced in the constraint -- flagged unused.
function int F_unused(Cls obj);
return obj.randomize() with (m_x, m_y) { m_x > 0; };
endfunction
module t;
Cls c;
initial begin
c = new;
if (F_unknown(c) != 1) $stop;
if (F_unused(c) != 1) $stop;
$finish;
end
endmodule

View File

@ -0,0 +1,5 @@
%Error: t/t_randomize_with_idlist_method_bad.v:13:21: 'with (...) { ... }' constraint block is only valid for randomize() (IEEE 1800-2023 18.7)
13 | total = q.sum() with (item) { item > 0; };
| ^~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%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: 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()

View File

@ -0,0 +1,15 @@
// 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
module t;
int q[$];
int total;
initial begin
q.push_back(1);
q.push_back(2);
total = q.sum() with (item) { item > 0; };
end
endmodule