From 03512faee6cb56ea1c1bed576bd324a64a22487c Mon Sep 17 00:00:00 2001 From: mjoekhan Date: Tue, 28 Apr 2026 23:32:45 +0500 Subject: [PATCH] SystemVerilog: add integral product() reduction on arrays Implement queue/darray product() reduction (including class property paths) via new %queue/product/v and %queue/product/prop/v opcodes, with elaboration/codegen support and regressions for queue, dynamic-array, and class-property usage. Made-with: Cursor --- elab_expr.cc | 151 +++++++++++++++++- ivtest/ivltests/README_sv_queue_locators.txt | 8 +- .../ivltests/sv_class_darray_prop_locators.v | 3 + .../ivltests/sv_class_queue_prop_locators.v | 3 + ivtest/ivltests/sv_darray_product.v | 24 +++ ivtest/ivltests/sv_queue_product.v | 24 +++ ivtest/regress-vvp.list | 2 + ivtest/vvp_tests/sv_darray_product.json | 9 ++ ivtest/vvp_tests/sv_queue_product.json | 9 ++ tgt-vvp/eval_vec4.c | 12 +- vvp/codes.h | 2 + vvp/compile.cc | 2 + vvp/vthread.cc | 56 +++++++ 13 files changed, 295 insertions(+), 10 deletions(-) create mode 100644 ivtest/ivltests/sv_darray_product.v create mode 100644 ivtest/ivltests/sv_queue_product.v create mode 100644 ivtest/vvp_tests/sv_darray_product.json create mode 100644 ivtest/vvp_tests/sv_queue_product.json diff --git a/elab_expr.cc b/elab_expr.cc index 7db2ea69e..58e6ae8c0 100644 --- a/elab_expr.cc +++ b/elab_expr.cc @@ -1758,6 +1758,13 @@ unsigned PECallFunction::test_width_method_(Design*, NetScope*, signed_flag_ = darray->get_signed(); return expr_width_; } + if (method_name == "product") { + expr_type_ = darray->element_base_type(); + expr_width_ = darray->element_width(); + min_width_ = expr_width_; + signed_flag_ = darray->get_signed(); + return expr_width_; + } if (method_name == "find" || method_name == "find_index" || method_name == "unique" || method_name == "unique_index" || @@ -1823,6 +1830,13 @@ unsigned PECallFunction::test_width_method_(Design*, NetScope*, signed_flag_ = darray->get_signed(); return expr_width_; } + if (method_name == "product") { + expr_type_ = darray->element_base_type(); + expr_width_ = darray->element_width(); + min_width_ = expr_width_; + signed_flag_ = darray->get_signed(); + return expr_width_; + } if (method_name == "unique" || method_name == "unique_index" || method_name == "min" || method_name == "max") { @@ -3880,6 +3894,31 @@ NetExpr* PECallFunction::elaborate_expr_method_(Design*des, NetScope*scope, sys_expr->parm(0, prop); return sys_expr; } + if (method_name == "product") { + if (parms_.size() != 0) { + cerr << get_fileline() << ": error: product() method " + << "takes no arguments" << endl; + des->errors += 1; + } + if (with_expr_) { + cerr << get_fileline() << ": sorry: queue product() with a " + << "`with` clause is not yet supported." << endl; + des->errors += 1; + return 0; + } + if (!queue_method_element_is_integral_vec4(element_type)) { + cerr << get_fileline() << ": sorry: queue product() for this " + << "element type is not yet supported." << endl; + des->errors += 1; + return 0; + } + NetESFunc*sys_expr = new NetESFunc( + "$ivl_queue_method$product", + element_type, 1); + sys_expr->set_line(*this); + 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 @@ -4419,6 +4458,31 @@ NetExpr* PECallFunction::elaborate_expr_method_(Design*des, NetScope*scope, sys_expr->parm(0, prop); return sys_expr; } + if (method_name == "product") { + if (parms_.size() != 0) { + cerr << get_fileline() << ": error: product() method " + << "takes no arguments" << endl; + des->errors += 1; + } + if (with_expr_) { + cerr << get_fileline() << ": sorry: array product() with a " + << "`with` clause is not yet supported." << endl; + des->errors += 1; + return 0; + } + if (!queue_method_element_is_integral_vec4(element_type)) { + cerr << get_fileline() << ": sorry: array product() for this " + << "element type is not yet supported." << endl; + des->errors += 1; + return 0; + } + NetESFunc*sys_expr = new NetESFunc( + "$ivl_queue_method$product", + element_type, 1); + sys_expr->set_line(*this); + 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 @@ -4824,6 +4888,31 @@ NetExpr* PECallFunction::elaborate_expr_method_(Design*des, NetScope*scope, sys_expr->parm(0, sub_expr); return sys_expr; } + if (method_name == "product") { + if (parms_.size() != 0) { + cerr << get_fileline() << ": error: product() method " + << "takes no arguments" << endl; + des->errors += 1; + } + if (with_expr_) { + cerr << get_fileline() << ": sorry: dynamic array product() with a " + << "`with` clause is not yet supported." << endl; + des->errors += 1; + return 0; + } + if (!queue_method_element_is_integral_vec4(element_type)) { + cerr << get_fileline() << ": sorry: dynamic array product() for this " + << "element type is not yet supported." << endl; + des->errors += 1; + return 0; + } + NetESFunc*sys_expr = new NetESFunc( + "$ivl_queue_method$product", + element_type, 1); + sys_expr->set_line(*this); + 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 @@ -5000,6 +5089,31 @@ NetExpr* PECallFunction::elaborate_expr_method_(Design*des, NetScope*scope, sys_expr->parm(0, sub_expr); return sys_expr; } + if (method_name == "product") { + if (parms_.size() != 0) { + cerr << get_fileline() << ": error: product() method " + << "takes no arguments" << endl; + des->errors += 1; + } + if (with_expr_) { + cerr << get_fileline() << ": sorry: queue product() with a " + << "`with` clause is not yet supported." << endl; + des->errors += 1; + return 0; + } + if (!queue_method_element_is_integral_vec4(element_type)) { + cerr << get_fileline() << ": sorry: queue product() for this " + << "element type is not yet supported." << endl; + des->errors += 1; + return 0; + } + NetESFunc*sys_expr = new NetESFunc( + "$ivl_queue_method$product", + element_type, 1); + sys_expr->set_line(*this); + 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 @@ -6464,6 +6578,22 @@ NetExpr* PEIdent::elaborate_expr(Design*des, NetScope*scope, fun->parm(0, arg); return fun; } + if (member_comp.name == "product") { + if (!queue_method_element_is_integral_vec4(element_type)) { + cerr << get_fileline() << ": sorry: queue product() for this " + << "element type is not yet supported." << endl; + des->errors += 1; + return 0; + } + NetESFunc*fun = new NetESFunc( + "$ivl_queue_method$product", + element_type, 1); + fun->set_line(*this); + NetESignal*arg = new NetESignal(sr.net); + arg->set_line(*sr.net); + 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 @@ -6908,11 +7038,22 @@ NetExpr* PEIdent::elaborate_expr_(Design*des, NetScope*scope, fun->parm(0, arg); return fun; } else if (member_comp.name == "product") { - cerr << get_fileline() << ": sorry: 'product()' " - "array reduction method is not currently " - "implemented." << endl; - des->errors += 1; - return 0; + const netdarray_t* dar_sum = sr.net->darray_type(); + ivl_assert(*this, dar_sum); + ivl_type_t element_type = dar_sum->element_type(); + if (!queue_method_element_is_integral_vec4(element_type)) { + cerr << get_fileline() << ": sorry: dynamic array product() for this " + << "element type is not yet supported." << endl; + des->errors += 1; + return 0; + } + NetESFunc*fun = new NetESFunc("$ivl_queue_method$product", + element_type, 1); + fun->set_line(*this); + NetESignal*arg = new NetESignal(sr.net); + arg->set_line(*sr.net); + fun->parm(0, arg); + return fun; // FIXME: Check this is only an integral type. } else if (member_comp.name == "and") { cerr << get_fileline() << ": sorry: 'and()' " diff --git a/ivtest/ivltests/README_sv_queue_locators.txt b/ivtest/ivltests/README_sv_queue_locators.txt index 693463290..84b6b2c45 100644 --- a/ivtest/ivltests/README_sv_queue_locators.txt +++ b/ivtest/ivltests/README_sv_queue_locators.txt @@ -5,7 +5,8 @@ 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, min, max, unique, unique_index, sum (integral) + find_last_index, min, max, unique, unique_index, + sum/product (integral) Behavior notes (LRM-oriented): @@ -24,6 +25,9 @@ Behavior notes (LRM-oriented): empty arrays/queues yield 0. `sum() with (expr)` reduces the expression result for each item. + * product() returns the scalar reduction product of elements (integral + vector types). + * 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 @@ -53,6 +57,8 @@ Regression tests (see ivtest/vvp_tests/*.json and regress-vvp.list): 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. sv_class_queue_prop_locators.v locator methods on class queue properties. + sv_queue_product.v integral product() reduction on queues. + sv_darray_product.v integral product() reduction on dynamic arrays. sv_queue_sum.v integral sum() reduction on queues. sv_darray_sum.v integral sum() reduction on dynamic arrays. sv_queue_sum_with.v sum() with expression on queues. diff --git a/ivtest/ivltests/sv_class_darray_prop_locators.v b/ivtest/ivltests/sv_class_darray_prop_locators.v index 5f7da3ec7..7458e8ffe 100644 --- a/ivtest/ivltests/sv_class_darray_prop_locators.v +++ b/ivtest/ivltests/sv_class_darray_prop_locators.v @@ -57,6 +57,9 @@ module test; `check(r.size, 1); `check(r[0], 6); + c.d = '{2, 3, 4}; + `check(c.d.product(), 24); + if (!failed) $display("PASSED"); end diff --git a/ivtest/ivltests/sv_class_queue_prop_locators.v b/ivtest/ivltests/sv_class_queue_prop_locators.v index 193988d1c..c6e5adf0b 100644 --- a/ivtest/ivltests/sv_class_queue_prop_locators.v +++ b/ivtest/ivltests/sv_class_queue_prop_locators.v @@ -49,6 +49,9 @@ module test; `check(r.size, 2); `check(r[0], 7); + c.q = '{2, 3, 4}; + `check(c.q.product(), 24); + if (!failed) $display("PASSED"); end diff --git a/ivtest/ivltests/sv_darray_product.v b/ivtest/ivltests/sv_darray_product.v new file mode 100644 index 000000000..e9a35181c --- /dev/null +++ b/ivtest/ivltests/sv_darray_product.v @@ -0,0 +1,24 @@ +// Regression: dynamic array product() reduction (integral). + +module top; + + bit failed = 0; + + `define CHK(cond) if (!(cond)) begin $display("FAILED line %0d", `__LINE__); failed = 1; end + + int a[]; + int p; + + initial begin + a = '{2, 3, 4}; + p = a.product(); + `CHK(p === 24); + + a = '{-2, 3, 5}; + p = a.product(); + `CHK(p === -30); + + if (!failed) + $display("PASSED"); + end +endmodule diff --git a/ivtest/ivltests/sv_queue_product.v b/ivtest/ivltests/sv_queue_product.v new file mode 100644 index 000000000..28dbe3f27 --- /dev/null +++ b/ivtest/ivltests/sv_queue_product.v @@ -0,0 +1,24 @@ +// Regression: queue product() reduction (integral). + +module top; + + bit failed = 0; + + `define CHK(cond) if (!(cond)) begin $display("FAILED line %0d", `__LINE__); failed = 1; end + + int q[$]; + int p; + + initial begin + q = '{2, 3, 4}; + p = q.product(); + `CHK(p === 24); + + q = '{-2, 3, 5}; + p = q.product(); + `CHK(p === -30); + + if (!failed) + $display("PASSED"); + end +endmodule diff --git a/ivtest/regress-vvp.list b/ivtest/regress-vvp.list index ed1ab62ad..4f919349d 100644 --- a/ivtest/regress-vvp.list +++ b/ivtest/regress-vvp.list @@ -257,6 +257,7 @@ 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_darray_min_max_with vvp_tests/sv_darray_min_max_with.json +sv_darray_product vvp_tests/sv_darray_product.json sv_darray_sum vvp_tests/sv_darray_sum.json sv_darray_sum_with vvp_tests/sv_darray_sum_with.json sv_darray_unique vvp_tests/sv_darray_unique.json @@ -286,6 +287,7 @@ 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_queue_min_max_with vvp_tests/sv_queue_min_max_with.json +sv_queue_product vvp_tests/sv_queue_product.json sv_queue_sum vvp_tests/sv_queue_sum.json sv_queue_sum_with vvp_tests/sv_queue_sum_with.json sv_wildcard_import8 vvp_tests/sv_wildcard_import8.json diff --git a/ivtest/vvp_tests/sv_darray_product.json b/ivtest/vvp_tests/sv_darray_product.json new file mode 100644 index 000000000..75501c439 --- /dev/null +++ b/ivtest/vvp_tests/sv_darray_product.json @@ -0,0 +1,9 @@ +{ + "type" : "normal", + "source" : "sv_darray_product.v", + "iverilog-args" : [ "-g2005-sv" ], + "vlog95" : { + "__comment" : "SystemVerilog dynamic array product() reduction", + "type" : "CE" + } +} diff --git a/ivtest/vvp_tests/sv_queue_product.json b/ivtest/vvp_tests/sv_queue_product.json new file mode 100644 index 000000000..84bb5e979 --- /dev/null +++ b/ivtest/vvp_tests/sv_queue_product.json @@ -0,0 +1,9 @@ +{ + "type" : "normal", + "source" : "sv_queue_product.v", + "iverilog-args" : [ "-g2005-sv" ], + "vlog95" : { + "__comment" : "SystemVerilog queue product() reduction", + "type" : "CE" + } +} diff --git a/tgt-vvp/eval_vec4.c b/tgt-vvp/eval_vec4.c index a17be29f6..1f987a076 100644 --- a/tgt-vvp/eval_vec4.c +++ b/tgt-vvp/eval_vec4.c @@ -1119,21 +1119,25 @@ static void draw_sfunc_vec4(ivl_expr_t expr) } } - if (strcmp(ivl_expr_name(expr), "$ivl_queue_method$sum") == 0 && + if ((strcmp(ivl_expr_name(expr), "$ivl_queue_method$sum") == 0 || + strcmp(ivl_expr_name(expr), "$ivl_queue_method$product") == 0) && parm_count == 1) { ivl_expr_t arg = ivl_expr_parm(expr, 0); unsigned wid = ivl_expr_width(expr); + const char* op = strcmp(ivl_expr_name(expr), "$ivl_queue_method$product") == 0 + ? "product" + : "sum"; if (ivl_expr_type(arg) == IVL_EX_PROPERTY) { ivl_signal_t clas = ivl_expr_signal(arg); unsigned pidx = ivl_expr_property_idx(arg); fprintf(vvp_out, " %%load/obj v%p_0;\n", clas); - fprintf(vvp_out, " %%queue/sum/prop/v %u, %u;\n", pidx, wid); + fprintf(vvp_out, " %%queue/%s/prop/v %u, %u;\n", op, pidx, wid); fprintf(vvp_out, " %%pop/obj 1, 0;\n"); return; } assert(ivl_expr_type(arg) == IVL_EX_SIGNAL); - fprintf(vvp_out, " %%queue/sum/v v%p_0, %u;\n", - ivl_expr_signal(arg), wid); + fprintf(vvp_out, " %%queue/%s/v v%p_0, %u;\n", + op, ivl_expr_signal(arg), wid); return; } diff --git a/vvp/codes.h b/vvp/codes.h index f0e47ed57..5a31d88b5 100644 --- a/vvp/codes.h +++ b/vvp/codes.h @@ -251,6 +251,8 @@ extern bool of_QPOP_PROP_F_V(vthread_t thr, vvp_code_t code); extern bool of_PROP_QUEUE_SIZE(vthread_t thr, vvp_code_t code); extern bool of_QUEUE_APPEND_WORD_V(vthread_t thr, vvp_code_t code); extern bool of_QUEUE_NEW_EMPTY_V(vthread_t thr, vvp_code_t code); +extern bool of_QUEUE_PRODUCT_PROP_V(vthread_t thr, vvp_code_t code); +extern bool of_QUEUE_PRODUCT_V(vthread_t thr, vvp_code_t code); extern bool of_QUEUE_SIZE_V(vthread_t thr, vvp_code_t code); extern bool of_QUEUE_SUM_OBJ_V(vthread_t thr, vvp_code_t code); extern bool of_QUEUE_SUM_PROP_V(vthread_t thr, vvp_code_t code); diff --git a/vvp/compile.cc b/vvp/compile.cc index fbd35498d..b13d84762 100644 --- a/vvp/compile.cc +++ b/vvp/compile.cc @@ -298,6 +298,8 @@ static const struct opcode_table_s opcode_table[] = { { "%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/product/prop/v", of_QUEUE_PRODUCT_PROP_V, 2, {OA_NUMBER, OA_BIT1, OA_NONE} }, + { "%queue/product/v", of_QUEUE_PRODUCT_V, 2, {OA_FUNC_PTR, OA_BIT1, OA_NONE} }, { "%queue/size/v", of_QUEUE_SIZE_V, 1, {OA_FUNC_PTR, OA_NONE, OA_NONE} }, { "%queue/sum/obj/v", of_QUEUE_SUM_OBJ_V, 1, {OA_BIT1, OA_NONE, OA_NONE} }, { "%queue/sum/prop/v", of_QUEUE_SUM_PROP_V, 2, {OA_NUMBER, OA_BIT1, OA_NONE} }, diff --git a/vvp/vthread.cc b/vvp/vthread.cc index 05334f7b5..bdfc13f9b 100644 --- a/vvp/vthread.cc +++ b/vvp/vthread.cc @@ -6425,6 +6425,62 @@ static vvp_vector4_t queue_sum_words_src(SRC* src, unsigned wid) return acc; } +template +static vvp_vector4_t queue_product_words_src(SRC* src, unsigned wid) +{ + vvp_vector4_t acc = queue_unique_ulong_to_vec4(1UL, wid); + if (!src) + return acc; + for (size_t i = 0; i < src->get_size(); i += 1) { + vvp_vector4_t vi(wid); + src->get_word(i, vi); + acc.mul(vi); + } + return acc; +} + +bool of_QUEUE_PRODUCT_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_vector4_t prod; + if (qsrc) + prod = queue_product_words_src(qsrc, wid); + else if (dsrc) + prod = queue_product_words_src(dsrc, wid); + else { + vvp_queue* src_q = get_queue_object(thr, net); + vvp_queue_vec4* src = dynamic_cast(src_q); + prod = queue_product_words_src(src, wid); + } + thr->push_vec4(prod); + return true; +} + +bool of_QUEUE_PRODUCT_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(); + assert(cobj); + + vvp_object_t qobj; + cobj->get_object(pid, qobj, 0); + vvp_queue_vec4* qsrc = 0; + vvp_darray* dsrc = 0; + get_queue_or_darray_vec4_from_object(qobj, qsrc, dsrc); + vvp_vector4_t prod = + qsrc ? queue_product_words_src(qsrc, wid) + : queue_product_words_src(dsrc, wid); + thr->push_vec4(prod); + return true; +} + bool of_QUEUE_SUM_V(vthread_t thr, vvp_code_t cp) { vvp_net_t* net = cp->net;