SystemVerilog: add min/max locator methods for queue and darray

Implement queue and dynamic-array min()/max() for integral vec4 element types, with VVP support for both queue-backed and darray-backed storage and regressions in ivtest. Keep min/max as no-arg methods and document current behavior alongside existing locator tests.

Made-with: Cursor
This commit is contained in:
mjoekhan 2026-04-28 16:42:54 +05:00
parent 20bd360aec
commit 30b4afa7d2
11 changed files with 372 additions and 3 deletions

View File

@ -1736,7 +1736,8 @@ unsigned PECallFunction::test_width_method_(Design*, NetScope*,
return expr_width_;
}
if (method_name == "find" || method_name == "find_index") {
if (method_name == "find" || method_name == "find_index" ||
method_name == "min" || method_name == "max") {
expr_type_ = IVL_VT_QUEUE;
expr_width_ = 1;
min_width_ = 1;
@ -1791,7 +1792,8 @@ unsigned PECallFunction::test_width_method_(Design*, NetScope*,
return expr_width_;
}
if (method_name == "unique" || method_name == "unique_index") {
if (method_name == "unique" || method_name == "unique_index" ||
method_name == "min" || method_name == "max") {
expr_type_ = IVL_VT_QUEUE;
expr_width_ = 1;
min_width_ = 1;
@ -3788,6 +3790,26 @@ NetExpr* PECallFunction::elaborate_expr_method_(Design*des, NetScope*scope,
sys_expr->parm(0, prop);
return sys_expr;
}
if (method_name == "min" || method_name == "max") {
if (parms_.size() != 0) {
cerr << get_fileline() << ": error: " << method_name
<< "() method takes no arguments" << endl;
des->errors += 1;
}
if (!queue_method_element_is_integral_vec4(element_type)) {
cerr << get_fileline() << ": sorry: queue " << method_name
<< "() for this element type is not yet supported." << endl;
des->errors += 1;
return 0;
}
NetESFunc*sys_expr = new NetESFunc(
method_name == "min" ? "$ivl_queue_method$min"
: "$ivl_queue_method$max",
static_cast<ivl_type_t>(queue), 1);
sys_expr->set_line(*this);
sys_expr->parm(0, prop);
return sys_expr;
}
if (method_name == "find") {
if (!queue_method_element_is_integral_vec4(element_type)) {
cerr << get_fileline() << ": sorry: queue find() for this "
@ -4289,6 +4311,32 @@ NetExpr* PECallFunction::elaborate_expr_method_(Design*des, NetScope*scope,
sys_expr->parm(1, cmp);
return sys_expr;
}
if (method_name == "min" || method_name == "max") {
if (parms_.size() != 0) {
cerr << get_fileline() << ": error: " << method_name
<< "() method takes no arguments" << endl;
des->errors += 1;
}
if (!queue_method_element_is_integral_vec4(element_type)) {
cerr << get_fileline() << ": sorry: dynamic array " << method_name
<< "() for this element type is not yet supported." << endl;
des->errors += 1;
return 0;
}
if (with_expr_) {
cerr << get_fileline() << ": sorry: dynamic array " << method_name
<< "() with a `with` clause is not yet supported." << endl;
des->errors += 1;
return 0;
}
NetESFunc*sys_expr = new NetESFunc(
method_name == "min" ? "$ivl_queue_method$min"
: "$ivl_queue_method$max",
queue_rtype, 1);
sys_expr->set_line(*this);
sys_expr->parm(0, sub_expr);
return sys_expr;
}
cerr << get_fileline() << ": error: Method " << method_name
<< " is not a dynamic array method." << endl;
@ -4381,6 +4429,32 @@ NetExpr* PECallFunction::elaborate_expr_method_(Design*des, NetScope*scope,
sys_expr->parm(0, sub_expr);
return sys_expr;
}
if (method_name == "min" || method_name == "max") {
if (parms_.size() != 0) {
cerr << get_fileline() << ": error: " << method_name
<< "() method takes no arguments" << endl;
des->errors += 1;
}
if (!queue_method_element_is_integral_vec4(element_type)) {
cerr << get_fileline() << ": sorry: queue " << method_name
<< "() for this element type is not yet supported." << endl;
des->errors += 1;
return 0;
}
if (with_expr_) {
cerr << get_fileline() << ": sorry: queue " << method_name
<< "() with a `with` clause is not yet supported." << endl;
des->errors += 1;
return 0;
}
NetESFunc*sys_expr = new NetESFunc(
method_name == "min" ? "$ivl_queue_method$min"
: "$ivl_queue_method$max",
static_cast<ivl_type_t>(queue), 1);
sys_expr->set_line(*this);
sys_expr->parm(0, sub_expr);
return sys_expr;
}
if (method_name == "find") {
if (!queue_method_element_is_integral_vec4(element_type)) {
@ -5798,6 +5872,23 @@ NetExpr* PEIdent::elaborate_expr(Design*des, NetScope*scope,
fun->parm(0, arg);
return fun;
}
if (member_comp.name == "min" || member_comp.name == "max") {
if (!queue_method_element_is_integral_vec4(element_type)) {
cerr << get_fileline() << ": sorry: queue " << member_comp.name
<< "() for this element type is not yet supported." << endl;
des->errors += 1;
return 0;
}
NetESFunc*fun = new NetESFunc(
member_comp.name == "min" ? "$ivl_queue_method$min"
: "$ivl_queue_method$max",
static_cast<ivl_type_t>(queue), 1);
fun->set_line(*this);
NetESignal*arg = new NetESignal(sr.net);
arg->set_line(*sr.net);
fun->parm(0, arg);
return fun;
}
cerr << get_fileline() << ": error: Unknown or unsupported queue "
<< "member `" << member_comp.name << "'." << endl;
des->errors += 1;
@ -6112,6 +6203,23 @@ NetExpr* PEIdent::elaborate_expr_(Design*des, NetScope*scope,
fun->parm(0, arg);
return fun;
}
if (member_comp.name == "min" || member_comp.name == "max") {
if (!queue_method_element_is_integral_vec4(element_type)) {
cerr << get_fileline() << ": sorry: queue " << member_comp.name
<< "() for this element type is not yet supported." << endl;
des->errors += 1;
return 0;
}
NetESFunc*fun = new NetESFunc(
member_comp.name == "min" ? "$ivl_queue_method$min"
: "$ivl_queue_method$max",
static_cast<ivl_type_t>(queue), 1);
fun->set_line(*this);
NetESignal*arg = new NetESignal(sr.net);
arg->set_line(*sr.net);
fun->parm(0, arg);
return fun;
}
}
// Dynamic array (not queue) — array location / reduction methods.

View File

@ -5,7 +5,7 @@ This directory includes regression tests for IEEE 1800 locator methods
on unpacked queues (int q[$]) and dynamic arrays (int da[]):
find, find_index, find_first, find_first_index, find_last,
find_last_index, unique, unique_index
find_last_index, min, max, unique, unique_index
Behavior notes (LRM-oriented):
@ -17,6 +17,9 @@ Behavior notes (LRM-oriented):
queue with zero or one element; no match yields an empty queue (not a
scalar sentinel).
* min() and max() return queues containing all elements equal to the
selected extrema.
* For dynamic arrays, runtime support treats storage as vvp_darray (including
atom-backed integral arrays), not only vvp_queue_vec4. See vvp/vthread.cc:
get_queue_or_darray_vec4_from_net() and the %queue/* opcodes used for
@ -37,3 +40,5 @@ Regression tests (see ivtest/vvp_tests/*.json and regress-vvp.list):
sv_queue_find_locators_ext.v Longer queue, compound predicates.
sv_queue_unique.v unique / unique_index.
sv_darray_find_locators.v Same locator patterns on int[] dynamic array.
sv_queue_min_max.v min() and max() on queue values.
sv_darray_min_max.v min() and max() on dynamic array values.

View File

@ -0,0 +1,36 @@
// Regression: dynamic array min() and max() locator methods return queues.
module top;
bit failed = 0;
`define CHK(cond) \
if (!(cond)) begin \
$display("FAILED line %0d", `__LINE__); \
failed = 1; \
end
int a[] = '{4, 7, 2, 5, 7, 1, 6, 3, 1};
int empty[];
int r[$];
initial begin
r = a.min();
`CHK(r.size == 2);
`CHK(r[0] == 1);
`CHK(r[1] == 1);
r = a.max();
`CHK(r.size == 2);
`CHK(r[0] == 7);
`CHK(r[1] == 7);
r = empty.min();
`CHK(r.size == 0);
r = empty.max();
`CHK(r.size == 0);
if (!failed)
$display("PASSED");
end
endmodule

View File

@ -0,0 +1,47 @@
// Regression: queue min() and max() locator methods return queues.
module top;
bit failed = 0;
`define CHK(cond) \
if (!(cond)) begin \
$display("FAILED line %0d", `__LINE__); \
failed = 1; \
end
int q[$];
int e[$];
int r[$];
initial begin
q.delete();
q.push_back(4);
q.push_back(7);
q.push_back(2);
q.push_back(5);
q.push_back(7);
q.push_back(1);
q.push_back(6);
q.push_back(3);
q.push_back(1);
r = q.min();
`CHK(r.size == 2);
`CHK(r[0] == 1);
`CHK(r[1] == 1);
r = q.max();
`CHK(r.size == 2);
`CHK(r[0] == 7);
`CHK(r[1] == 7);
r = e.min();
`CHK(r.size == 0);
r = e.max();
`CHK(r.size == 0);
if (!failed)
$display("PASSED");
end
endmodule

View File

@ -253,6 +253,7 @@ sv_const_fail8 vvp_tests/sv_const_fail8.json
sv_const_fail9 vvp_tests/sv_const_fail9.json
sv_darray_assign_op vvp_tests/sv_darray_assign_op.json
sv_darray_find_locators vvp_tests/sv_darray_find_locators.json
sv_darray_min_max vvp_tests/sv_darray_min_max.json
sv_default_port_value1 vvp_tests/sv_default_port_value1.json
sv_default_port_value2 vvp_tests/sv_default_port_value2.json
sv_default_port_value3 vvp_tests/sv_default_port_value3.json
@ -276,6 +277,7 @@ sv_queue_unique vvp_tests/sv_queue_unique.json
sv_queue_find vvp_tests/sv_queue_find.json
sv_queue_find_locators_ext vvp_tests/sv_queue_find_locators_ext.json
sv_queue_find_with vvp_tests/sv_queue_find_with.json
sv_queue_min_max vvp_tests/sv_queue_min_max.json
sv_wildcard_import8 vvp_tests/sv_wildcard_import8.json
sdf_header vvp_tests/sdf_header.json
task_return1 vvp_tests/task_return1.json

View File

@ -0,0 +1,9 @@
{
"type" : "normal",
"source" : "sv_darray_min_max.v",
"iverilog-args" : [ "-g2005-sv" ],
"vlog95" : {
"__comment" : "SystemVerilog dynamic array min/max locator methods",
"type" : "CE"
}
}

View File

@ -0,0 +1,9 @@
{
"type" : "normal",
"source" : "sv_queue_min_max.v",
"iverilog-args" : [ "-g2005-sv" ],
"vlog95" : {
"__comment" : "SystemVerilog queue min/max locator methods",
"type" : "CE"
}
}

View File

@ -315,6 +315,26 @@ static int eval_queue_method_unique(ivl_expr_t expr)
return 0;
}
if (strcmp(name, "$ivl_queue_method$min") == 0 ||
strcmp(name, "$ivl_queue_method$max") == 0) {
const char* opname = strcmp(name, "$ivl_queue_method$min") == 0
? "min"
: "max";
if (ivl_expr_type(arg) == IVL_EX_PROPERTY) {
ivl_signal_t cl = ivl_expr_signal(arg);
unsigned pidx = ivl_expr_property_idx(arg);
fprintf(vvp_out, " %%load/obj v%p_0;\n", cl);
fprintf(vvp_out, " %%queue/%s/prop/v %u, %u;\n", opname, pidx,
elem_wid);
fprintf(vvp_out, " %%pop/obj 1, 0;\n");
} else {
ivl_signal_t sig = ivl_expr_signal(arg);
fprintf(vvp_out, " %%queue/%s/v v%p_0, %u;\n", opname, sig,
elem_wid);
}
return 0;
}
return 1;
}

View File

@ -269,6 +269,10 @@ extern bool of_QUEUE_FIND_LAST_INDEX_PROP_V(vthread_t thr, vvp_code_t code);
extern bool of_QUEUE_FIND_LAST_INDEX_V(vthread_t thr, vvp_code_t code);
extern bool of_QUEUE_FIND_LAST_PROP_V(vthread_t thr, vvp_code_t code);
extern bool of_QUEUE_FIND_LAST_V(vthread_t thr, vvp_code_t code);
extern bool of_QUEUE_MIN_PROP_V(vthread_t thr, vvp_code_t code);
extern bool of_QUEUE_MIN_V(vthread_t thr, vvp_code_t code);
extern bool of_QUEUE_MAX_PROP_V(vthread_t thr, vvp_code_t code);
extern bool of_QUEUE_MAX_V(vthread_t thr, vvp_code_t code);
extern bool of_QUEUE_UNIQUE_INDEX_PROP_V(vthread_t thr, vvp_code_t code);
extern bool of_QUEUE_UNIQUE_INDEX_V(vthread_t thr, vvp_code_t code);
extern bool of_QUEUE_UNIQUE_PROP_V(vthread_t thr, vvp_code_t code);

View File

@ -291,6 +291,10 @@ static const struct opcode_table_s opcode_table[] = {
{ "%queue/find_last/index/v", of_QUEUE_FIND_LAST_INDEX_V, 2, {OA_FUNC_PTR, OA_BIT1, OA_NONE} },
{ "%queue/find_last/prop/v", of_QUEUE_FIND_LAST_PROP_V, 2, {OA_NUMBER, OA_BIT1, OA_NONE} },
{ "%queue/find_last/v", of_QUEUE_FIND_LAST_V, 2, {OA_FUNC_PTR, OA_BIT1, OA_NONE} },
{ "%queue/max/prop/v", of_QUEUE_MAX_PROP_V, 2, {OA_NUMBER, OA_BIT1, OA_NONE} },
{ "%queue/max/v", of_QUEUE_MAX_V, 2, {OA_FUNC_PTR, OA_BIT1, OA_NONE} },
{ "%queue/min/prop/v", of_QUEUE_MIN_PROP_V, 2, {OA_NUMBER, OA_BIT1, OA_NONE} },
{ "%queue/min/v", of_QUEUE_MIN_V, 2, {OA_FUNC_PTR, OA_BIT1, OA_NONE} },
{ "%queue/new_empty/v", of_QUEUE_NEW_EMPTY_V, 0, {OA_NONE, OA_NONE, OA_NONE} },
{ "%queue/size/v", of_QUEUE_SIZE_V, 1, {OA_FUNC_PTR, OA_NONE, OA_NONE} },
{ "%queue/unique/index/prop/v", of_QUEUE_UNIQUE_INDEX_PROP_V, 2, {OA_NUMBER, OA_BIT1, OA_NONE} },

View File

@ -6357,6 +6357,41 @@ static vvp_queue_vec4* queue_find_first_last_to_queue_src(SRC* src,
return dst;
}
/* min()/max() return all elements equal to the selected extrema. */
template <class SRC>
static vvp_queue_vec4* queue_run_min_max_src(SRC* src, unsigned wid, bool want_max)
{
vvp_queue_vec4* dst = new vvp_queue_vec4();
if (!src || src->get_size() == 0)
return dst;
vvp_vector4_t best(wid);
src->get_word(0, best);
dst->push_back(best, 0);
size_t n = src->get_size();
for (size_t i = 1; i < n; i += 1) {
vvp_vector4_t vi(wid);
src->get_word(i, vi);
if (vi.eeq(best)) {
dst->push_back(vi, 0);
continue;
}
vvp_bit4_t ge = compare_gtge(vi, best, BIT4_1);
bool replace = want_max ? (ge == BIT4_1) : (ge == BIT4_0);
if (!replace)
continue;
best = vi;
delete dst;
dst = new vvp_queue_vec4();
dst->push_back(best, 0);
}
return dst;
}
bool of_QUEUE_FIND_V(vthread_t thr, vvp_code_t cp)
{
vvp_vector4_t cmp = thr->pop_vec4();
@ -6621,6 +6656,96 @@ bool of_QUEUE_FIND_LAST_INDEX_PROP_V(vthread_t thr, vvp_code_t cp)
return true;
}
bool of_QUEUE_MIN_V(vthread_t thr, vvp_code_t cp)
{
vvp_net_t* net = cp->net;
unsigned wid = cp->bit_idx[0];
vvp_queue_vec4* qsrc = 0;
vvp_darray* dsrc = 0;
get_queue_or_darray_vec4_from_net(thr, net, qsrc, dsrc);
vvp_queue_vec4* dst;
if (qsrc)
dst = queue_run_min_max_src(qsrc, wid, false);
else if (dsrc)
dst = queue_run_min_max_src(dsrc, wid, false);
else {
vvp_queue* src_q = get_queue_object<vvp_queue_vec4>(thr, net);
vvp_queue_vec4* src = dynamic_cast<vvp_queue_vec4*>(src_q);
dst = queue_run_min_max_src(src, wid, false);
}
thr->push_object(vvp_object_t(dst));
return true;
}
bool of_QUEUE_MIN_PROP_V(vthread_t thr, vvp_code_t cp)
{
size_t pid = cp->number;
unsigned wid = cp->bit_idx[0];
vvp_object_t& top = thr->peek_object();
vvp_cobject*cobj = top.peek<vvp_cobject>();
assert(cobj);
vvp_object_t qobj;
cobj->get_object(pid, qobj, 0);
vvp_queue_vec4* qsrc = qobj.peek<vvp_queue_vec4>();
vvp_darray* dsrc = 0;
if (!qsrc) {
vvp_darray* dany = qobj.peek<vvp_darray>();
if (dany && dynamic_cast<vvp_queue*>(dany) == 0)
dsrc = dany;
}
vvp_queue_vec4* dst = qsrc ? queue_run_min_max_src(qsrc, wid, false)
: queue_run_min_max_src(dsrc, wid, false);
thr->push_object(vvp_object_t(dst));
return true;
}
bool of_QUEUE_MAX_V(vthread_t thr, vvp_code_t cp)
{
vvp_net_t* net = cp->net;
unsigned wid = cp->bit_idx[0];
vvp_queue_vec4* qsrc = 0;
vvp_darray* dsrc = 0;
get_queue_or_darray_vec4_from_net(thr, net, qsrc, dsrc);
vvp_queue_vec4* dst;
if (qsrc)
dst = queue_run_min_max_src(qsrc, wid, true);
else if (dsrc)
dst = queue_run_min_max_src(dsrc, wid, true);
else {
vvp_queue* src_q = get_queue_object<vvp_queue_vec4>(thr, net);
vvp_queue_vec4* src = dynamic_cast<vvp_queue_vec4*>(src_q);
dst = queue_run_min_max_src(src, wid, true);
}
thr->push_object(vvp_object_t(dst));
return true;
}
bool of_QUEUE_MAX_PROP_V(vthread_t thr, vvp_code_t cp)
{
size_t pid = cp->number;
unsigned wid = cp->bit_idx[0];
vvp_object_t& top = thr->peek_object();
vvp_cobject*cobj = top.peek<vvp_cobject>();
assert(cobj);
vvp_object_t qobj;
cobj->get_object(pid, qobj, 0);
vvp_queue_vec4* qsrc = qobj.peek<vvp_queue_vec4>();
vvp_darray* dsrc = 0;
if (!qsrc) {
vvp_darray* dany = qobj.peek<vvp_darray>();
if (dany && dynamic_cast<vvp_queue*>(dany) == 0)
dsrc = dany;
}
vvp_queue_vec4* dst = qsrc ? queue_run_min_max_src(qsrc, wid, true)
: queue_run_min_max_src(dsrc, wid, true);
thr->push_object(vvp_object_t(dst));
return true;
}
bool of_QUEUE_UNIQUE_V(vthread_t thr, vvp_code_t cp)
{
vvp_net_t*net = cp->net;