SystemVerilog: add unique()/unique_index() with predicates

Enable unique and unique_index locator methods with `with (...)` for queues, dynamic arrays, and class dynamic-array properties, including VVP opcode/runtime support and regression coverage.

Made-with: Cursor
This commit is contained in:
mjoekhan 2026-04-28 21:10:11 +05:00
parent 789bff4861
commit 7cac25cbd1
12 changed files with 260 additions and 19 deletions

View File

@ -123,6 +123,10 @@ static NetExpr* elab_queue_locator_with_predicate(
sfunc_name = lex_strings.make("$ivl_queue_method$find_last_with");
else if (method_suffix == "find_last_index")
sfunc_name = lex_strings.make("$ivl_queue_method$find_last_index_with");
else if (method_suffix == "unique")
sfunc_name = lex_strings.make("$ivl_queue_method$unique_with");
else if (method_suffix == "unique_index")
sfunc_name = lex_strings.make("$ivl_queue_method$unique_index_with");
else if (method_suffix == "min")
sfunc_name = lex_strings.make("$ivl_queue_method$min_with");
else if (method_suffix == "max")
@ -3769,6 +3773,19 @@ NetExpr* PECallFunction::elaborate_expr_method_(Design*des, NetScope*scope,
des->errors += 1;
return 0;
}
if (with_expr_) {
if (!parms_.empty()) {
cerr << get_fileline() << ": error: array locator "
<< "`with` clause cannot be combined with a "
<< "method argument." << endl;
des->errors += 1;
return 0;
}
return elab_queue_locator_with_predicate(
des, scope, *this, with_expr_, prop,
element_type, static_cast<ivl_type_t>(queue),
method_name);
}
NetESFunc*sys_expr = new NetESFunc(
"$ivl_queue_method$unique",
static_cast<ivl_type_t>(queue), 1);
@ -3788,6 +3805,20 @@ NetExpr* PECallFunction::elaborate_expr_method_(Design*des, NetScope*scope,
des->errors += 1;
return 0;
}
if (with_expr_) {
if (!parms_.empty()) {
cerr << get_fileline() << ": error: array locator "
<< "`with` clause cannot be combined with a "
<< "method argument." << endl;
des->errors += 1;
return 0;
}
return elab_queue_locator_with_predicate(
des, scope, *this, with_expr_, prop,
element_type,
static_cast<ivl_type_t>(&ivl_queue_unique_index_ret),
method_name);
}
NetESFunc*sys_expr = new NetESFunc(
"$ivl_queue_method$unique_index",
static_cast<ivl_type_t>(&ivl_queue_unique_index_ret), 1);
@ -4049,10 +4080,16 @@ NetExpr* PECallFunction::elaborate_expr_method_(Design*des, NetScope*scope,
return 0;
}
if (with_expr_) {
cerr << get_fileline() << ": sorry: dynamic array unique() with a "
<< "`with` clause is not yet supported." << endl;
des->errors += 1;
return 0;
if (!parms_.empty()) {
cerr << get_fileline() << ": error: array locator "
<< "`with` clause cannot be combined with a "
<< "method argument." << endl;
des->errors += 1;
return 0;
}
return elab_queue_locator_with_predicate(
des, scope, *this, with_expr_, prop,
element_type, darray_rtype, method_name);
}
NetESFunc*sys_expr = new NetESFunc(
"$ivl_queue_method$unique", darray_rtype, 1);
@ -4073,10 +4110,18 @@ NetExpr* PECallFunction::elaborate_expr_method_(Design*des, NetScope*scope,
return 0;
}
if (with_expr_) {
cerr << get_fileline() << ": sorry: dynamic array unique_index() with a "
<< "`with` clause is not yet supported." << endl;
des->errors += 1;
return 0;
if (!parms_.empty()) {
cerr << get_fileline() << ": error: array locator "
<< "`with` clause cannot be combined with a "
<< "method argument." << endl;
des->errors += 1;
return 0;
}
return elab_queue_locator_with_predicate(
des, scope, *this, with_expr_, prop,
element_type,
static_cast<ivl_type_t>(&ivl_queue_unique_index_ret),
method_name);
}
NetESFunc*sys_expr = new NetESFunc(
"$ivl_queue_method$unique_index",
@ -4421,10 +4466,15 @@ NetExpr* PECallFunction::elaborate_expr_method_(Design*des, NetScope*scope,
return 0;
}
if (with_expr_) {
cerr << get_fileline() << ": sorry: dynamic array unique() with a "
<< "`with` clause is not yet supported." << endl;
des->errors += 1;
return 0;
if (!parms_.empty()) {
cerr << get_fileline() << ": error: array locator `with` clause "
"cannot be combined with a method argument." << endl;
des->errors += 1;
return 0;
}
return elab_queue_locator_with_predicate(
des, scope, *this, with_expr_, sub_expr, element_type,
queue_rtype, method_name);
}
NetESFunc*sys_expr = new NetESFunc(
"$ivl_queue_method$unique", queue_rtype, 1);
@ -4445,10 +4495,16 @@ NetExpr* PECallFunction::elaborate_expr_method_(Design*des, NetScope*scope,
return 0;
}
if (with_expr_) {
cerr << get_fileline() << ": sorry: dynamic array unique_index() with a "
<< "`with` clause is not yet supported." << endl;
des->errors += 1;
return 0;
if (!parms_.empty()) {
cerr << get_fileline() << ": error: array locator `with` clause "
"cannot be combined with a method argument." << endl;
des->errors += 1;
return 0;
}
return elab_queue_locator_with_predicate(
des, scope, *this, with_expr_, sub_expr, element_type,
static_cast<ivl_type_t>(&ivl_queue_unique_index_ret),
method_name);
}
NetESFunc*sys_expr = new NetESFunc(
"$ivl_queue_method$unique_index",
@ -4734,6 +4790,17 @@ NetExpr* PECallFunction::elaborate_expr_method_(Design*des, NetScope*scope,
des->errors += 1;
return 0;
}
if (with_expr_) {
if (!parms_.empty()) {
cerr << get_fileline() << ": error: array locator `with` clause "
"cannot be combined with a method argument." << endl;
des->errors += 1;
return 0;
}
return elab_queue_locator_with_predicate(
des, scope, *this, with_expr_, sub_expr, element_type,
static_cast<ivl_type_t>(queue), method_name);
}
NetESFunc*sys_expr = new NetESFunc(
"$ivl_queue_method$unique",
static_cast<ivl_type_t>(queue), 1);
@ -4754,6 +4821,18 @@ NetExpr* PECallFunction::elaborate_expr_method_(Design*des, NetScope*scope,
des->errors += 1;
return 0;
}
if (with_expr_) {
if (!parms_.empty()) {
cerr << get_fileline() << ": error: array locator `with` clause "
"cannot be combined with a method argument." << endl;
des->errors += 1;
return 0;
}
return elab_queue_locator_with_predicate(
des, scope, *this, with_expr_, sub_expr, element_type,
static_cast<ivl_type_t>(&ivl_queue_unique_index_ret),
method_name);
}
NetESFunc*sys_expr = new NetESFunc(
"$ivl_queue_method$unique_index",
static_cast<ivl_type_t>(&ivl_queue_unique_index_ret), 1);

View File

@ -46,3 +46,5 @@ Regression tests (see ivtest/vvp_tests/*.json and regress-vvp.list):
sv_queue_min_max_with.v min()/max() with predicate on queue values.
sv_darray_min_max_with.v min()/max() with predicate on dynamic arrays.
sv_class_darray_prop_locators.v locator methods on class dynamic-array properties.
sv_queue_unique_with.v unique()/unique_index() with predicate on queues.
sv_darray_unique_with.v unique()/unique_index() with predicate on dynamic arrays.

View File

@ -38,6 +38,16 @@ module test;
`check(r.size, 7);
`check(r[0], 0);
r = c.d.unique() with (item > 2);
`check(r.size, 5);
`check(r[0], 4);
`check(r[4], 3);
r = c.d.unique_index() with (item > 2);
`check(r.size, 6);
`check(r[0], 0);
`check(r[5], 7);
r = c.d.min();
`check(r.size, 2);
`check(r[0], 1);

View File

@ -0,0 +1,33 @@
// Regression: dynamic array unique()/unique_index() with predicate.
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 r[$];
initial begin
r = a.unique() with (item > 2);
`CHK(r.size == 5);
`CHK(r[0] == 4);
`CHK(r[1] == 7);
`CHK(r[2] == 5);
`CHK(r[3] == 6);
`CHK(r[4] == 3);
r = a.unique_index() with (item > 2);
`CHK(r.size == 6);
`CHK(r[0] == 0);
`CHK(r[1] == 1);
`CHK(r[2] == 3);
`CHK(r[3] == 4);
`CHK(r[4] == 6);
`CHK(r[5] == 7);
if (!failed)
$display("PASSED");
end
endmodule

View File

@ -0,0 +1,35 @@
// Regression: queue unique()/unique_index() with predicate.
module top;
bit failed = 0;
`define CHK(cond) if (!(cond)) begin $display("FAILED line %0d", `__LINE__); failed = 1; end
int q[$];
int r[$];
initial begin
q = '{4, 7, 2, 5, 7, 1, 6, 3, 1};
r = q.unique() with (item > 2);
`CHK(r.size == 5);
`CHK(r[0] == 4);
`CHK(r[1] == 7);
`CHK(r[2] == 5);
`CHK(r[3] == 6);
`CHK(r[4] == 3);
r = q.unique_index() with (item > 2);
`CHK(r.size == 6);
`CHK(r[0] == 0);
`CHK(r[1] == 1);
`CHK(r[2] == 3);
`CHK(r[3] == 4);
`CHK(r[4] == 6);
`CHK(r[5] == 7);
if (!failed)
$display("PASSED");
end
endmodule

View File

@ -257,6 +257,7 @@ sv_darray_find_locators vvp_tests/sv_darray_find_locators.json
sv_darray_min_max vvp_tests/sv_darray_min_max.json
sv_darray_min_max_with vvp_tests/sv_darray_min_max_with.json
sv_darray_unique vvp_tests/sv_darray_unique.json
sv_darray_unique_with vvp_tests/sv_darray_unique_with.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

View File

@ -0,0 +1,9 @@
{
"type" : "normal",
"source" : "sv_darray_unique_with.v",
"iverilog-args" : [ "-g2005-sv" ],
"vlog95" : {
"__comment" : "SystemVerilog darray unique with predicate",
"type" : "CE"
}
}

View File

@ -0,0 +1,9 @@
{
"type" : "normal",
"source" : "sv_queue_unique_with.v",
"iverilog-args" : [ "-g2005-sv" ],
"vlog95" : {
"__comment" : "SystemVerilog queue unique with predicate",
"type" : "CE"
}
}

View File

@ -430,6 +430,10 @@ static int eval_queue_method_find_with(ivl_expr_t expr)
mode = 6;
else if (strcmp(name, "$ivl_queue_method$max_with") == 0)
mode = 7;
else if (strcmp(name, "$ivl_queue_method$unique_with") == 0)
mode = 8;
else if (strcmp(name, "$ivl_queue_method$unique_index_with") == 0)
mode = 9;
else
return 1;
@ -449,7 +453,8 @@ static int eval_queue_method_find_with(ivl_expr_t expr)
fprintf(vvp_out, " %%load/obj v%p_0;\n", cl);
fprintf(vvp_out, " %%prop/queue/size %u;\n", pidx);
fprintf(vvp_out, " %%ix/vec4/s %u;\n", n_reg);
if (mode == 0 || mode == 1 || mode == 6 || mode == 7) {
if (mode == 0 || mode == 1 || mode == 6 || mode == 7 ||
mode == 8 || mode == 9) {
fprintf(vvp_out, " %%queue/new_empty/v;\n");
}
if (!reverse) {
@ -497,6 +502,14 @@ static int eval_queue_method_find_with(ivl_expr_t expr)
fprintf(vvp_out, " %%load/vec4 v%p_0;\n", item_sig);
fprintf(vvp_out, " %%queue/append_word/v %u;\n", elem_wid);
fprintf(vvp_out, " %%jmp T_%u.%u;\n", thread_count, lab_nom);
} else if (mode == 8) {
fprintf(vvp_out, " %%load/vec4 v%p_0;\n", item_sig);
fprintf(vvp_out, " %%queue/append_word/v %u;\n", elem_wid);
fprintf(vvp_out, " %%jmp T_%u.%u;\n", thread_count, lab_nom);
} else if (mode == 9) {
fprintf(vvp_out, " %%push/ix/vec4 %u, 32, 1;\n", i_reg);
fprintf(vvp_out, " %%queue/append_word/v 32;\n");
fprintf(vvp_out, " %%jmp T_%u.%u;\n", thread_count, lab_nom);
} else if (mode == 2) {
fprintf(vvp_out, " %%queue/new_empty/v;\n");
fprintf(vvp_out, " %%load/vec4 v%p_0;\n", item_sig);
@ -538,6 +551,10 @@ static int eval_queue_method_find_with(ivl_expr_t expr)
if (mode == 0 || mode == 1) {
/* Keep result queue object, drop class object beneath it. */
fprintf(vvp_out, " %%pop/obj 1, 1;\n");
} else if (mode == 8 || mode == 9) {
fprintf(vvp_out, " %%queue/unique/obj/v %u;\n",
mode == 9 ? 32 : elem_wid);
fprintf(vvp_out, " %%pop/obj 1, 1;\n");
} else if (mode == 6 || mode == 7) {
fprintf(vvp_out, " %%queue/%s/obj/v %u;\n",
mode == 6 ? "min" : "max", elem_wid);
@ -551,7 +568,8 @@ static int eval_queue_method_find_with(ivl_expr_t expr)
ivl_signal_t sig = ivl_expr_signal(qarg);
fprintf(vvp_out, " %%queue/size/v v%p_0;\n", sig);
fprintf(vvp_out, " %%ix/vec4/s %u;\n", n_reg);
if (mode == 0 || mode == 1 || mode == 6 || mode == 7) {
if (mode == 0 || mode == 1 || mode == 6 || mode == 7 ||
mode == 8 || mode == 9) {
fprintf(vvp_out, " %%queue/new_empty/v;\n");
}
if (!reverse) {
@ -599,6 +617,14 @@ static int eval_queue_method_find_with(ivl_expr_t expr)
fprintf(vvp_out, " %%load/vec4 v%p_0;\n", item_sig);
fprintf(vvp_out, " %%queue/append_word/v %u;\n", elem_wid);
fprintf(vvp_out, " %%jmp T_%u.%u;\n", thread_count, lab_nom);
} else if (mode == 8) {
fprintf(vvp_out, " %%load/vec4 v%p_0;\n", item_sig);
fprintf(vvp_out, " %%queue/append_word/v %u;\n", elem_wid);
fprintf(vvp_out, " %%jmp T_%u.%u;\n", thread_count, lab_nom);
} else if (mode == 9) {
fprintf(vvp_out, " %%push/ix/vec4 %u, 32, 1;\n", i_reg);
fprintf(vvp_out, " %%queue/append_word/v 32;\n");
fprintf(vvp_out, " %%jmp T_%u.%u;\n", thread_count, lab_nom);
} else if (mode == 2) {
fprintf(vvp_out, " %%queue/new_empty/v;\n");
fprintf(vvp_out, " %%load/vec4 v%p_0;\n", item_sig);
@ -632,7 +658,10 @@ static int eval_queue_method_find_with(ivl_expr_t expr)
fprintf(vvp_out, "T_%u.%u ; loop end (var)\n", thread_count,
lab_loop_end);
if (mode == 6 || mode == 7) {
if (mode == 8 || mode == 9) {
fprintf(vvp_out, " %%queue/unique/obj/v %u;\n",
mode == 9 ? 32 : elem_wid);
} else if (mode == 6 || mode == 7) {
fprintf(vvp_out, " %%queue/%s/obj/v %u;\n",
mode == 6 ? "min" : "max", elem_wid);
} else if (mode >= 2) {

View File

@ -275,6 +275,8 @@ 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_OBJ_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_OBJ_V(vthread_t thr, vvp_code_t code);
extern bool of_QUEUE_UNIQUE_INDEX_OBJ_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

@ -299,8 +299,10 @@ static const struct opcode_table_s opcode_table[] = {
{ "%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/obj/v", of_QUEUE_UNIQUE_INDEX_OBJ_V, 1, {OA_BIT1, OA_NONE, OA_NONE} },
{ "%queue/unique/index/prop/v", of_QUEUE_UNIQUE_INDEX_PROP_V, 2, {OA_NUMBER, OA_BIT1, OA_NONE} },
{ "%queue/unique/index/v", of_QUEUE_UNIQUE_INDEX_V, 2, {OA_FUNC_PTR, OA_BIT1, OA_NONE} },
{ "%queue/unique/obj/v", of_QUEUE_UNIQUE_OBJ_V, 1, {OA_BIT1, OA_NONE, OA_NONE} },
{ "%queue/unique/prop/v", of_QUEUE_UNIQUE_PROP_V, 2, {OA_NUMBER, OA_BIT1, OA_NONE} },
{ "%queue/unique/v", of_QUEUE_UNIQUE_V, 2, {OA_FUNC_PTR, OA_BIT1, OA_NONE} },
{ "%queue/word/prop/v", of_QUEUE_WORD_PROP_V, 3, {OA_NUMBER, OA_BIT1, OA_BIT2} },

View File

@ -6796,6 +6796,36 @@ bool of_QUEUE_MAX_PROP_V(vthread_t thr, vvp_code_t cp)
return true;
}
bool of_QUEUE_UNIQUE_OBJ_V(vthread_t thr, vvp_code_t cp)
{
unsigned wid = cp->bit_idx[0];
vvp_object_t src_obj;
thr->pop_object(src_obj);
vvp_queue_vec4* qsrc = 0;
vvp_darray* dsrc = 0;
get_queue_or_darray_vec4_from_object(src_obj, qsrc, dsrc);
vvp_queue_vec4* dst = qsrc ? queue_run_unique_src(qsrc, wid, false)
: queue_run_unique_src(dsrc, wid, false);
thr->push_object(vvp_object_t(dst));
return true;
}
bool of_QUEUE_UNIQUE_INDEX_OBJ_V(vthread_t thr, vvp_code_t cp)
{
unsigned wid = cp->bit_idx[0];
vvp_object_t src_obj;
thr->pop_object(src_obj);
vvp_queue_vec4* qsrc = 0;
vvp_darray* dsrc = 0;
get_queue_or_darray_vec4_from_object(src_obj, qsrc, dsrc);
vvp_queue_vec4* dst = qsrc ? queue_run_unique_src(qsrc, wid, true)
: queue_run_unique_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;