From 5b8fac5441a4097e06355e45ec79024ba2133081 Mon Sep 17 00:00:00 2001 From: Andrew Pullin Date: Fri, 23 Jan 2026 10:56:13 -0800 Subject: [PATCH 01/12] Fix #1112: Report error for invalid $bits argument When $bits() is called with an undefined identifier, the compiler now properly reports an error instead of silently returning 0. The fix checks if the argument expression has type IVL_VT_NO_TYPE after test_width() processing (indicating the identifier couldn't be resolved), and triggers elaboration to produce a proper error message. Includes regression test: br_gh1112 Co-Authored-By: Claude Opus 4.5 --- elab_expr.cc | 9 +++++++++ ivtest/gold/br_gh1112.gold | 2 ++ ivtest/ivltests/br_gh1112.v | 6 ++++++ ivtest/regress-sv.list | 1 + 4 files changed, 18 insertions(+) create mode 100644 ivtest/gold/br_gh1112.gold create mode 100644 ivtest/ivltests/br_gh1112.v diff --git a/elab_expr.cc b/elab_expr.cc index 671381e62..306f2e319 100644 --- a/elab_expr.cc +++ b/elab_expr.cc @@ -1983,6 +1983,15 @@ NetExpr* PECallFunction::elaborate_sfunc_(Design*des, NetScope*scope, } } else { + // Check if the expression could be resolved. If test_width + // failed to find the identifier, expr_type will be NO_TYPE. + if (expr->expr_type() == IVL_VT_NO_TYPE) { + // Try to elaborate the expression to get a proper error + // message about the undefined identifier. + NetExpr *tmp = expr->elaborate_expr(des, scope, (unsigned)1, flags); + if (tmp) delete tmp; + return 0; + } use_width = expr->expr_width(); if (debug_elaborate) { cerr << get_fileline() << ": PECallFunction::elaborate_sfunc_: " diff --git a/ivtest/gold/br_gh1112.gold b/ivtest/gold/br_gh1112.gold new file mode 100644 index 000000000..39090a992 --- /dev/null +++ b/ivtest/gold/br_gh1112.gold @@ -0,0 +1,2 @@ +./ivltests/br_gh1112.v:4: error: Unable to bind parameter `value' in `top' +1 error(s) during elaboration. diff --git a/ivtest/ivltests/br_gh1112.v b/ivtest/ivltests/br_gh1112.v new file mode 100644 index 000000000..508440ea5 --- /dev/null +++ b/ivtest/ivltests/br_gh1112.v @@ -0,0 +1,6 @@ +// Test for GitHub issue #1112 +// $bits() with non-existent identifier should produce an error +module top; + localparam width = $bits(value); // 'value' doesn't exist - should error + initial $display(width); +endmodule diff --git a/ivtest/regress-sv.list b/ivtest/regress-sv.list index dade32337..8f4ea3733 100644 --- a/ivtest/regress-sv.list +++ b/ivtest/regress-sv.list @@ -989,3 +989,4 @@ partsel_real_idx CE,-g2012 ivltests gold=partsel_real_idx.gold ipsdownsel_real_idx CE,-g2012 ivltests gold=ipsdownsel_real_idx.gold ipsupsel_real_idx CE,-g2012 ivltests gold=ipsupsel_real_idx.gold real_edges CE,-g2012 ivltests gold=real_edges.gold +br_gh1112 CE,-g2009 ivltests gold=br_gh1112.gold From b6042215fff95e059c0d2075ee815dbdfca4e2a6 Mon Sep 17 00:00:00 2001 From: Andrew Pullin Date: Fri, 23 Jan 2026 11:05:52 -0800 Subject: [PATCH 02/12] Fix #670: Allow class methods named same as class Modified grammar in parse.y to use identifier_name instead of IDENTIFIER in function/task declaration rules and hierarchy_identifier rule. This allows TYPE_IDENTIFIER tokens (which class names become after definition) to be used as method names. Changes: - Function declaration rules (lines 1585, 1605, 1631) - Task declaration rules (lines 2445, 2472, 2501) - hierarchy_identifier member access (line 4471) Includes regression test: br_gh670 Co-Authored-By: Claude Opus 4.5 --- ivtest/ivltests/br_gh670.v | 25 +++++++++++++++++++++++++ ivtest/regress-sv.list | 1 + parse.y | 14 +++++++------- 3 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 ivtest/ivltests/br_gh670.v diff --git a/ivtest/ivltests/br_gh670.v b/ivtest/ivltests/br_gh670.v new file mode 100644 index 000000000..4fc1bb206 --- /dev/null +++ b/ivtest/ivltests/br_gh670.v @@ -0,0 +1,25 @@ +// Test for GitHub issue #670 +// Class method can have the same name as the class +program main; + class test; + int value; + + // Method with same name as class - should be valid + function void test(); + $display("test method called"); + value = 42; + endfunction + endclass + + test tst; + + initial begin + tst = new(); + tst.test(); + if (tst.value !== 42) begin + $display("FAILED: value = %0d, expected 42", tst.value); + $finish; + end + $display("PASSED"); + end +endprogram diff --git a/ivtest/regress-sv.list b/ivtest/regress-sv.list index 8f4ea3733..127f58fa5 100644 --- a/ivtest/regress-sv.list +++ b/ivtest/regress-sv.list @@ -990,3 +990,4 @@ ipsdownsel_real_idx CE,-g2012 ivltests gold=ipsdownsel_real_idx.gold ipsupsel_real_idx CE,-g2012 ivltests gold=ipsupsel_real_idx.gold real_edges CE,-g2012 ivltests gold=real_edges.gold br_gh1112 CE,-g2009 ivltests gold=br_gh1112.gold +br_gh670 normal,-g2009 ivltests diff --git a/parse.y b/parse.y index d1767a86b..7a3897d6b 100644 --- a/parse.y +++ b/parse.y @@ -1582,7 +1582,7 @@ for_step_opt definitions in the func_body to take on the scope of the function instead of the module. */ function_declaration /* IEEE1800-2005: A.2.6 */ - : K_function lifetime_opt data_type_or_implicit_or_void IDENTIFIER ';' + : K_function lifetime_opt data_type_or_implicit_or_void identifier_name ';' { assert(current_function == 0); current_function = pform_push_function_scope(@1, $4, $2); } @@ -1602,7 +1602,7 @@ function_declaration /* IEEE1800-2005: A.2.6 */ delete[]$4; } - | K_function lifetime_opt data_type_or_implicit_or_void IDENTIFIER + | K_function lifetime_opt data_type_or_implicit_or_void identifier_name { assert(current_function == 0); current_function = pform_push_function_scope(@1, $4, $2); } @@ -1628,7 +1628,7 @@ function_declaration /* IEEE1800-2005: A.2.6 */ /* Detect and recover from some errors. */ - | K_function lifetime_opt data_type_or_implicit_or_void IDENTIFIER error K_endfunction + | K_function lifetime_opt data_type_or_implicit_or_void identifier_name error K_endfunction { /* */ if (current_function) { pform_pop_scope(); @@ -2442,7 +2442,7 @@ streaming_concatenation /* IEEE1800-2005: A.8.1 */ task_declaration /* IEEE1800-2005: A.2.7 */ - : K_task lifetime_opt IDENTIFIER ';' + : K_task lifetime_opt identifier_name ';' { assert(current_task == 0); current_task = pform_push_task_scope(@1, $3, $2); } @@ -2469,7 +2469,7 @@ task_declaration /* IEEE1800-2005: A.2.7 */ delete[]$3; } - | K_task lifetime_opt IDENTIFIER '(' + | K_task lifetime_opt identifier_name '(' { assert(current_task == 0); current_task = pform_push_task_scope(@1, $3, $2); } @@ -2498,7 +2498,7 @@ task_declaration /* IEEE1800-2005: A.2.7 */ delete[]$3; } - | K_task lifetime_opt IDENTIFIER error K_endtask + | K_task lifetime_opt identifier_name error K_endtask { if (current_task) { pform_pop_scope(); @@ -4468,7 +4468,7 @@ hierarchy_identifier $$->push_back(name_component_t(lex_strings.make($1))); delete[]$1; } - | hierarchy_identifier '.' IDENTIFIER + | hierarchy_identifier '.' identifier_name { pform_name_t * tmp = $1; tmp->push_back(name_component_t(lex_strings.make($3))); delete[]$3; From d540260a20d3d764b7825a30a36c041d4e6a33d1 Mon Sep 17 00:00:00 2001 From: Andrew Pullin Date: Fri, 23 Jan 2026 11:08:47 -0800 Subject: [PATCH 03/12] Fix #1170: Skip $unit scope in tgt-sizer The tgt-sizer target now skips IVL_SCT_PACKAGE scopes (the SystemVerilog $unit compilation unit scope) instead of erroring. This allows sizer to work with -g2012 and other SystemVerilog modes. Includes regression test: br_gh1170 Co-Authored-By: Claude Opus 4.5 --- ivtest/ivltests/br_gh1170.v | 6 ++++++ ivtest/perl-lib/RegressionList.pm | 2 +- ivtest/regress-synth.list | 1 + tgt-sizer/sizer.cc | 4 ++++ 4 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 ivtest/ivltests/br_gh1170.v diff --git a/ivtest/ivltests/br_gh1170.v b/ivtest/ivltests/br_gh1170.v new file mode 100644 index 000000000..b346d7793 --- /dev/null +++ b/ivtest/ivltests/br_gh1170.v @@ -0,0 +1,6 @@ +// Test for GitHub issue #1170 +// tgt-sizer should work with SystemVerilog 2012 ($unit scope) +module test; + logic [7:0] data; + assign data = 8'hAA; +endmodule diff --git a/ivtest/perl-lib/RegressionList.pm b/ivtest/perl-lib/RegressionList.pm index a089b4d18..f9ffbb385 100644 --- a/ivtest/perl-lib/RegressionList.pm +++ b/ivtest/perl-lib/RegressionList.pm @@ -102,7 +102,7 @@ sub read_regression_list { $args{$tname} = ""; } if ($opt ne "std") { - $args{$tname} = $opt . $args{$tname}; + $args{$tname} = $opt . ($args{$tname} ? " " . $args{$tname} : ""); } $srcpath{$tname} = $fields[2]; diff --git a/ivtest/regress-synth.list b/ivtest/regress-synth.list index 3cd91d65f..ca00d4401 100644 --- a/ivtest/regress-synth.list +++ b/ivtest/regress-synth.list @@ -137,3 +137,4 @@ ssetclr2 normal ivltests ssetclr3 normal ivltests synth_if_no_else normal ivltests ufuncsynth1 normal ivltests +br_gh1170 CO,-g2012,-tsizer ivltests diff --git a/tgt-sizer/sizer.cc b/tgt-sizer/sizer.cc index cade50217..86ed277f2 100644 --- a/tgt-sizer/sizer.cc +++ b/tgt-sizer/sizer.cc @@ -92,6 +92,10 @@ int target_design(ivl_design_t des) // multiple root scopes, we will give isolated numbers for // each and keep then separate. for (unsigned idx = 0 ; idx < nroots ; idx += 1) { + // Skip SystemVerilog $unit scope (compilation unit scope) + if (ivl_scope_type(roots[idx]) == IVL_SCT_PACKAGE) { + continue; + } if (ivl_scope_type(roots[idx]) != IVL_SCT_MODULE) { fprintf(stderr, "SIZER: The root scope %s must be a module.\n", ivl_scope_basename(roots[idx])); sizer_errors += 1; From a03033e7432cb9130d3a343309f7eb9d5cec9e19 Mon Sep 17 00:00:00 2001 From: Andrew Pullin Date: Fri, 23 Jan 2026 11:28:58 -0800 Subject: [PATCH 04/12] Fix #1267: Allow wire logic connected to uwire port to have multiple drivers TODO: Decide how resolved net types (tri0/tri1/triand/trior) should interact with uwire in a shared nexus. --- ivtest/ivltests/br_gh1267.v | 23 +++++++++++++++++++++++ ivtest/regress-sv.list | 1 + tgt-vvp/draw_net_input.c | 21 +++++++++++++++++++-- 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 ivtest/ivltests/br_gh1267.v diff --git a/ivtest/ivltests/br_gh1267.v b/ivtest/ivltests/br_gh1267.v new file mode 100644 index 000000000..aa2ce8a5b --- /dev/null +++ b/ivtest/ivltests/br_gh1267.v @@ -0,0 +1,23 @@ +// Test for GitHub issue #1267 +// Wire logic connected to uwire port should not trigger multi-driver error +// The uwire semantics apply only to the uwire signal, not to wires connected to it. + +module a(input uwire logic a1); +endmodule + +module b(); + wire logic b1; + + a b_inst(.a1(b1)); + not not_inst(b1, b1); + + assign b1 = 'b0; + + initial begin + #1; + // b1 has multiple drivers, which is allowed for wire types + // The value will be X due to conflicting drivers + $display("b1 = %b (expected X due to conflicting drivers)", b1); + $display("PASSED"); + end +endmodule diff --git a/ivtest/regress-sv.list b/ivtest/regress-sv.list index 127f58fa5..89e4d7666 100644 --- a/ivtest/regress-sv.list +++ b/ivtest/regress-sv.list @@ -991,3 +991,4 @@ ipsupsel_real_idx CE,-g2012 ivltests gold=ipsupsel_real_idx.gold real_edges CE,-g2012 ivltests gold=real_edges.gold br_gh1112 CE,-g2009 ivltests gold=br_gh1112.gold br_gh670 normal,-g2009 ivltests +br_gh1267 normal,-g2012 ivltests diff --git a/tgt-vvp/draw_net_input.c b/tgt-vvp/draw_net_input.c index d50eaaf38..fe0f0fc4d 100644 --- a/tgt-vvp/draw_net_input.c +++ b/tgt-vvp/draw_net_input.c @@ -30,6 +30,8 @@ static ivl_signal_type_t signal_type_of_nexus(ivl_nexus_t nex) { unsigned idx; ivl_signal_type_t out = IVL_SIT_TRI; + int has_tri = 0; + int has_uwire = 0; for (idx = 0 ; idx < ivl_nexus_ptrs(nex) ; idx += 1) { ivl_signal_type_t stype; @@ -41,14 +43,29 @@ static ivl_signal_type_t signal_type_of_nexus(ivl_nexus_t nex) stype = ivl_signal_type(sig); if (stype == IVL_SIT_REG) continue; - if (stype == IVL_SIT_TRI) + if (stype == IVL_SIT_TRI) { + has_tri = 1; continue; + } if (stype == IVL_SIT_NONE) continue; - if (stype == IVL_SIT_UWIRE) return IVL_SIT_UWIRE; + if (stype == IVL_SIT_UWIRE) { + has_uwire = 1; + continue; + } out = stype; } + /* If both TRI (wire) and UWIRE are in the nexus, return TRI + because wire semantics allow multiple drivers. Only return + UWIRE if no TRI signals are present. This fixes GitHub #1267 + where wire logic connected to uwire ports was incorrectly + treated as requiring single-driver semantics. + TODO: Decide how resolved net types (tri0/tri1/triand/trior) + should interact with uwire in a shared nexus. */ + if (has_uwire && !has_tri) + return IVL_SIT_UWIRE; + return out; } From 68633814d1fc5b9517b6842e39df80fbca63968f Mon Sep 17 00:00:00 2001 From: Andrew Pullin Date: Fri, 23 Jan 2026 14:27:46 -0800 Subject: [PATCH 05/12] Fix #1268: Allow variables to be driven by primitive gate outputs Per IEEE 1800-2017 6.5, variables can be written by one port, including primitive gate outputs. The code incorrectly disallowed this with the comment "Gates can never have variable output ports." Changed elaborate_lnet/elaborate_bi_net calls for gate outputs to pass true for var_allowed_in_sv, allowing variables as single-driver outputs. Co-Authored-By: Claude Opus 4.5 --- elaborate.cc | 8 +++++--- ivtest/gold/br_gh1222.gold | 6 +----- ivtest/ivltests/br_gh1222.v | 4 ++-- ivtest/ivltests/br_gh1268.v | 23 +++++++++++++++++++++++ ivtest/regress-sv.list | 1 + 5 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 ivtest/ivltests/br_gh1268.v diff --git a/elaborate.cc b/elaborate.cc index 6839156da..3b1ac3fb0 100644 --- a/elaborate.cc +++ b/elaborate.cc @@ -774,11 +774,13 @@ void PGBuiltin::elaborate(Design*des, NetScope*scope) const des->errors += 1; return; } - // Gates can never have variable output ports. + // In SystemVerilog, variables can be driven by a single + // primitive/gate output (IEEE 1800-2017 6.5). Primitives always + // use default (strong) drive strength. if (lval_count > gate_count) - lval_sigs[idx] = pin(idx)->elaborate_bi_net(des, scope, false); + lval_sigs[idx] = pin(idx)->elaborate_bi_net(des, scope, gn_system_verilog()); else - lval_sigs[idx] = pin(idx)->elaborate_lnet(des, scope, false); + lval_sigs[idx] = pin(idx)->elaborate_lnet(des, scope, gn_system_verilog()); // The only way this should return zero is if an error // happened, so for that case just return. diff --git a/ivtest/gold/br_gh1222.gold b/ivtest/gold/br_gh1222.gold index 92ec72be1..f8e18ba50 100644 --- a/ivtest/gold/br_gh1222.gold +++ b/ivtest/gold/br_gh1222.gold @@ -2,8 +2,4 @@ ./ivltests/br_gh1222.v:6: error: Variable 'rout_ca2' cannot be driven by a primitive or continuous assignment with non-default strength. ./ivltests/br_gh1222.v:7: error: Variable 'lout_ca1' cannot be driven by a primitive or continuous assignment with non-default strength. ./ivltests/br_gh1222.v:7: error: Variable 'lout_ca2' cannot be driven by a primitive or continuous assignment with non-default strength. -./ivltests/br_gh1222.v:12: error: Variable 'rout_gt' cannot be driven by a primitive or continuous assignment with non-default strength. -./ivltests/br_gh1222.v:12: error: Failed to elaborate primitive output expression top.rout_gt. -./ivltests/br_gh1222.v:13: error: Variable 'lout_gt' cannot be driven by a primitive or continuous assignment with non-default strength. -./ivltests/br_gh1222.v:13: error: Failed to elaborate primitive output expression top.lout_gt. -12 error(s) during elaboration. +8 error(s) during elaboration. diff --git a/ivtest/ivltests/br_gh1222.v b/ivtest/ivltests/br_gh1222.v index b5e1d0e9c..1c5873a1c 100644 --- a/ivtest/ivltests/br_gh1222.v +++ b/ivtest/ivltests/br_gh1222.v @@ -9,8 +9,8 @@ module top; assign (strong1, strong0) rout_valid = in; // Ok, real cannot be in a concatenation assign (strong1, strong0) {lout_valid1, lout_valid2} = in; // Ok, default strength - and (rout_gt, in, in); // Gates must drive a net - and (lout_gt, in, in); // Gates must drive a net + and (rout_gt, in, in); // Ok in SV, variables can be driven by primitives (IEEE 1800-2017 6.5) + and (lout_gt, in, in); // Ok in SV, variables can be driven by primitives (IEEE 1800-2017 6.5) // When strength is added it should only be for the default strength! udp_inv (rout_udp, in); // A UDP is like a module and can drive a variable diff --git a/ivtest/ivltests/br_gh1268.v b/ivtest/ivltests/br_gh1268.v new file mode 100644 index 000000000..9c72f7277 --- /dev/null +++ b/ivtest/ivltests/br_gh1268.v @@ -0,0 +1,23 @@ +// Test for GitHub issue #1268 +// Variable (output logic) should be allowed to be driven by primitive gate +// Per IEEE 1800-2017 6.5: variables can be written by one port (primitive output) + +module driver(output logic c, input wire d); + not b(c, d); +endmodule + +module test; + wire d = 1'b0; + wire c; + + driver dut(.c(c), .d(d)); + + initial begin + #1; + if (c !== 1'b1) begin + $display("FAILED: c = %b, expected 1", c); + $finish; + end + $display("PASSED"); + end +endmodule diff --git a/ivtest/regress-sv.list b/ivtest/regress-sv.list index 89e4d7666..ded8b92a1 100644 --- a/ivtest/regress-sv.list +++ b/ivtest/regress-sv.list @@ -992,3 +992,4 @@ real_edges CE,-g2012 ivltests gold=real_edges.gold br_gh1112 CE,-g2009 ivltests gold=br_gh1112.gold br_gh670 normal,-g2009 ivltests br_gh1267 normal,-g2012 ivltests +br_gh1268 normal,-g2012 ivltests From 54dfd0a702c9fbc393e7fc974c85841c7391f72b Mon Sep 17 00:00:00 2001 From: Andrew Pullin Date: Fri, 23 Jan 2026 14:43:03 -0800 Subject: [PATCH 06/12] Fix #521: Allow variable indices in outer packed dimensions In multi-dimensional packed arrays, allow variable indices in the outer (prefix) dimensions, not just the final dimension. For example: logic [3:0][3:0] a; for (int i=0; i<4; i++) a[i][3] = 1; // Previously error, now works The fix checks if any packed prefix indices are non-constant. If so, use collapse_array_exprs() to compute the bit offset as an expression rather than requiring constant indices. This removes an artificial restriction that had no justification in the IEEE standard, as noted by maintainers in the GitHub issue. Co-Authored-By: Claude Opus 4.5 --- elab_lval.cc | 67 +++++++++++++++++++++++++++++++++++--- ivtest/ivltests/br_gh521.v | 19 +++++++++++ ivtest/regress-sv.list | 1 + 3 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 ivtest/ivltests/br_gh521.v diff --git a/elab_lval.cc b/elab_lval.cc index 4f1859c43..f3858d86a 100644 --- a/elab_lval.cc +++ b/elab_lval.cc @@ -528,9 +528,8 @@ bool PEIdent::elaborate_lval_net_bit_(Design*des, bool need_const_idx, bool is_force) const { - listprefix_indices; - bool rc = calculate_packed_indices_(des, scope, lv->sig(), prefix_indices); - if (!rc) return false; + NetNet*reg = lv->sig(); + ivl_assert(*this, reg); const name_component_t&name_tail = path_.back(); ivl_assert(*this, !name_tail.index.empty()); @@ -539,8 +538,66 @@ bool PEIdent::elaborate_lval_net_bit_(Design*des, ivl_assert(*this, index_tail.msb != 0); ivl_assert(*this, index_tail.lsb == 0); - NetNet*reg = lv->sig(); - ivl_assert(*this, reg); + // First, check if packed prefix indices contain any variable expressions. + // Build the list of packed indices (excluding unpacked dimensions). + list packed_index; + packed_index = name_tail.index; + for (size_t idx = 0 ; idx < reg->unpacked_dimensions() ; idx += 1) + packed_index.pop_front(); + + // For multi-dimensional packed arrays, check if the prefix indices + // (all but the final) contain any non-constant expressions. + bool has_variable_prefix = false; + if (packed_index.size() > 1) { + list::const_iterator icur = packed_index.begin(); + for (size_t idx = 0 ; (idx+1) < packed_index.size() ; idx += 1, ++icur) { + NetExpr*texpr = elab_and_eval(des, scope, icur->msb, -1, false); + if (texpr == 0 || !dynamic_cast(texpr)) { + has_variable_prefix = true; + } + delete texpr; + if (has_variable_prefix) break; + } + } + + // If prefix indices are variable, handle using expression-based path. + if (has_variable_prefix) { + if (need_const_idx) { + cerr << get_fileline() << ": error: '" << reg->name() + << "' index must be a constant in this context." + << endl; + des->errors += 1; + return false; + } + + if ((reg->type()==NetNet::UNRESOLVED_WIRE) && !is_force) { + ivl_assert(*this, reg->coerced_to_uwire()); + report_mixed_assignment_conflict_("bit select"); + des->errors += 1; + return false; + } + + // Use collapse_array_exprs to compute the bit offset as an expression. + NetExpr*base_expr = collapse_array_exprs(des, scope, this, reg, packed_index); + if (base_expr == 0) { + des->errors += 1; + return false; + } + + if (debug_elaborate) { + cerr << get_fileline() << ": PEIdent::elaborate_lval_net_bit_: " + << "Variable packed prefix, base_expr=" << *base_expr + << endl; + } + + lv->set_part(base_expr, 1); + return true; + } + + // All prefix indices are constant. Use the existing code path. + listprefix_indices; + bool rc = calculate_packed_indices_(des, scope, reg, prefix_indices); + if (!rc) return false; // Bit selects have a single select expression. Evaluate the // constant value and treat it as a part select with a bit diff --git a/ivtest/ivltests/br_gh521.v b/ivtest/ivltests/br_gh521.v new file mode 100644 index 000000000..fb8bf40e2 --- /dev/null +++ b/ivtest/ivltests/br_gh521.v @@ -0,0 +1,19 @@ +// Test for GitHub issue #521 +// Loop index should be allowed in outer dimension of multi-dimensional packed arrays +module test; + logic [3:0][3:0] a; + + initial begin + a = 0; + for (int i=0; i<4; i++) + a[i][3] = 1; + + // Each 4-bit sub-array has bit 3 set to 1, so each nibble is 0x8 + if (a !== 16'h8888) begin + $display("FAILED: a = %h, expected 8888", a); + $finish; + end + + $display("PASSED"); + end +endmodule diff --git a/ivtest/regress-sv.list b/ivtest/regress-sv.list index ded8b92a1..76846780a 100644 --- a/ivtest/regress-sv.list +++ b/ivtest/regress-sv.list @@ -212,6 +212,7 @@ br_gh477 normal,-g2009 ivltests br_gh478 normal,-g2009 ivltests br_gh498 normal,-g2009 ivltests br_gh508a normal,-g2009 ivltests +br_gh521 normal,-g2012 ivltests br_gh527 normal,-g2009 ivltests br_gh530 CO,-g2009 ivltests br_gh540 normal,-g2009 ivltests From e00e89d8f8000e2ee5cd6ad8308af0c0f23f6b4c Mon Sep 17 00:00:00 2001 From: Andrew Pullin Date: Fri, 23 Jan 2026 14:54:06 -0800 Subject: [PATCH 07/12] Fix #1134: Allow struct member access in unpacked array of packed structs When accessing a member of a packed struct within an unpacked array (e.g., `tests[0].a` where `tests` is `test_t tests[0:1]`), the assertion `base_index.size()+1 == net->packed_dimensions()` would fail because unpacked indices were incorrectly included in base_index. The fix: 1. Separate unpacked indices from packed indices before calling check_for_struct_members() 2. Compute the word index for unpacked array access using normalize_variable_unpacked() 3. Pass the word index to check_for_struct_members(), which now creates NetESignal with the proper word selector This allows expressions like `array_of_structs[i].member` to work correctly, selecting the right array element before extracting the packed struct member. Co-Authored-By: Claude Opus 4.5 --- elab_expr.cc | 69 +++++++++++++++++++++++++++++++------ ivtest/ivltests/br_gh1134.v | 27 +++++++++++++++ ivtest/regress-sv.list | 1 + 3 files changed, 87 insertions(+), 10 deletions(-) create mode 100644 ivtest/ivltests/br_gh1134.v diff --git a/elab_expr.cc b/elab_expr.cc index 306f2e319..9538d2994 100644 --- a/elab_expr.cc +++ b/elab_expr.cc @@ -2389,12 +2389,17 @@ bool calculate_part(const LineInfo*li, Design*des, NetScope*scope, * the net is a struct. If that turns out to be the case, and the * struct is packed, then return a NetExpr that selects the member out * of the variable. + * + * The word_index parameter is used when the struct is in an unpacked + * array - it selects which array word to access before the packed + * member selection. */ static NetExpr* check_for_struct_members(const LineInfo*li, Design*des, NetScope*scope, NetNet*net, const list&base_index, - pform_name_t member_path) + pform_name_t member_path, + NetExpr*word_index = 0) { const netstruct_t*struct_type = net->struct_type(); ivl_assert(*li, struct_type); @@ -2707,7 +2712,7 @@ static NetExpr* check_for_struct_members(const LineInfo*li, packed_base = 0; } - NetESignal*sig = new NetESignal(net); + NetESignal*sig = new NetESignal(net, word_index); NetExpr *base = packed_base? packed_base : make_const_val(off); NetESelect*sel = new NetESelect(sig, base, use_width, member_type); @@ -2715,13 +2720,59 @@ static NetExpr* check_for_struct_members(const LineInfo*li, cerr << li->get_fileline() << ": check_for_struct_member: " << "Finally, completed_path=" << completed_path << ", off=" << off << ", use_width=" << use_width - << ", base=" << *base - << endl; + << ", base=" << *base; + if (word_index) + cerr << ", word_index=" << *word_index; + cerr << endl; } return sel; } +/* + * Helper function to elaborate struct member access in an unpacked array. + * This handles separating unpacked indices from packed indices, + * computing the word index, and calling check_for_struct_members. + */ +static NetExpr* elaborate_struct_member_access(const PExpr*expr, + Design*des, NetScope*scope, + NetNet*net, + const pform_name_t&path_head, + const pform_name_t&path_tail) +{ + // Separate unpacked indices from packed indices. + // check_for_struct_members expects only packed indices. + list all_index = path_head.back().index; + list unpacked_index; + for (unsigned idx = 0 ; idx < net->unpacked_dimensions() ; idx += 1) { + unpacked_index.push_back(all_index.front()); + all_index.pop_front(); + } + list& packed_index = all_index; + + // Compute word index for unpacked array access. + NetExpr*word_index = 0; + if (net->unpacked_dimensions() > 0) { + listunpacked_indices_expr; + list unpacked_indices_const; + indices_flags idx_flags; + indices_to_expressions(des, scope, expr, + unpacked_index, net->unpacked_dimensions(), + false, idx_flags, + unpacked_indices_expr, unpacked_indices_const); + + if (idx_flags.variable) { + word_index = normalize_variable_unpacked(net, unpacked_indices_expr); + } else if (!idx_flags.invalid && !idx_flags.undefined) { + word_index = normalize_variable_unpacked(net, unpacked_indices_const); + } + } + + return check_for_struct_members(expr, des, scope, net, + packed_index, + path_tail, word_index); +} + static NetExpr* class_static_property_expression(const LineInfo*li, const netclass_t*class_type, perm_string name) @@ -4555,9 +4606,8 @@ NetExpr* PEIdent::elaborate_expr(Design*des, NetScope*scope, if (!sr.path_tail.empty()) { if (net->struct_type()) { - return check_for_struct_members(this, des, scope, net, - sr.path_head.back().index, - sr.path_tail); + return elaborate_struct_member_access(this, des, scope, net, + sr.path_head, sr.path_tail); } else if (dynamic_cast(sr.type)) { return elaborate_expr_class_field_(des, scope, sr, 0, flags); } @@ -4782,9 +4832,8 @@ NetExpr* PEIdent::elaborate_expr_(Design*des, NetScope*scope, << endl; } - return check_for_struct_members(this, des, scope, sr.net, - sr.path_head.back().index, - sr.path_tail); + return elaborate_struct_member_access(this, des, scope, sr.net, + sr.path_head, sr.path_tail); } // If this is an array object, and there are members in diff --git a/ivtest/ivltests/br_gh1134.v b/ivtest/ivltests/br_gh1134.v new file mode 100644 index 000000000..f5af54a79 --- /dev/null +++ b/ivtest/ivltests/br_gh1134.v @@ -0,0 +1,27 @@ +// Test for GitHub issue #1134 +// Accessing member of packed struct in unpacked array should work +typedef struct packed { + logic a, b; +} test_t; + +module test; + // tests[0] = {a:0, b:1}, tests[1] = {a:1, b:0} + test_t tests [0:1] = '{'{'b0, 'b1}, '{'b1, 'b0}}; + wire w0, w1; + + assign w0 = tests[0].a; // Should be 0 + assign w1 = tests[1].a; // Should be 1 + + initial begin + #1; + if (w0 !== 1'b0) begin + $display("FAILED: tests[0].a = %b, expected 0", w0); + $finish; + end + if (w1 !== 1'b1) begin + $display("FAILED: tests[1].a = %b, expected 1", w1); + $finish; + end + $display("PASSED"); + end +endmodule diff --git a/ivtest/regress-sv.list b/ivtest/regress-sv.list index 76846780a..830218e2d 100644 --- a/ivtest/regress-sv.list +++ b/ivtest/regress-sv.list @@ -992,5 +992,6 @@ ipsupsel_real_idx CE,-g2012 ivltests gold=ipsupsel_real_idx.gold real_edges CE,-g2012 ivltests gold=real_edges.gold br_gh1112 CE,-g2009 ivltests gold=br_gh1112.gold br_gh670 normal,-g2009 ivltests +br_gh1134 normal,-g2012 ivltests br_gh1267 normal,-g2012 ivltests br_gh1268 normal,-g2012 ivltests From 73cd6fa0c7774a55c1aeeda0d369ee166f103379 Mon Sep 17 00:00:00 2001 From: Andrew Pullin Date: Fri, 23 Jan 2026 15:22:36 -0800 Subject: [PATCH 08/12] Fix #1220: Allow uwire arrays as input ports Uwire arrays were triggering assertion failures because they were kept virtualized (pins array NULL) like reg arrays. However, unlike reg arrays, uwire arrays used as input ports need their nexuses initialized for the target code generation to work properly. Two changes: 1. elab_sig.cc: Devirtualize pins for UNRESOLVED_WIRE (uwire) type, same as regular WIRE type. 2. t-dll-api.cc: Update assertion in ivl_signal_nex to also accept IVL_SIT_UWIRE when pins array is NULL. Co-Authored-By: Claude Opus 4.5 --- elab_sig.cc | 3 ++- ivtest/ivltests/br_gh1220.v | 19 +++++++++++++++++++ ivtest/regress-sv.list | 1 + t-dll-api.cc | 4 ++-- 4 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 ivtest/ivltests/br_gh1220.v diff --git a/elab_sig.cc b/elab_sig.cc index 734d0dfdd..5dabe9c26 100644 --- a/elab_sig.cc +++ b/elab_sig.cc @@ -1210,7 +1210,8 @@ NetNet* PWire::elaborate_sig(Design*des, NetScope*scope) NetNet*sig = new NetNet(scope, name_, wtype, unpacked_dimensions, type); - if (wtype == NetNet::WIRE) sig->devirtualize_pins(); + if (wtype == NetNet::WIRE || wtype == NetNet::UNRESOLVED_WIRE) + sig->devirtualize_pins(); sig->set_line(*this); sig->port_type(port_type_); sig->lexical_pos(lexical_pos_); diff --git a/ivtest/ivltests/br_gh1220.v b/ivtest/ivltests/br_gh1220.v new file mode 100644 index 000000000..c8218b7e8 --- /dev/null +++ b/ivtest/ivltests/br_gh1220.v @@ -0,0 +1,19 @@ +// Test for GitHub issue #1220 +// Assertion failed with uwire multi-dimensional input port +module sub(input uwire data [1:0]); + initial begin + #1; + if (data[0] === 1'b0 && data[1] === 1'b1) + $display("PASSED"); + else + $display("FAILED: data[0]=%0b data[1]=%0b", data[0], data[1]); + end +endmodule + +module test; + wire w [1:0]; + assign w[0] = 1'b0; + assign w[1] = 1'b1; + + sub dut(.data(w)); +endmodule diff --git a/ivtest/regress-sv.list b/ivtest/regress-sv.list index 830218e2d..82b28c304 100644 --- a/ivtest/regress-sv.list +++ b/ivtest/regress-sv.list @@ -229,6 +229,7 @@ br_gh782b normal,-g2009 ivltests gold=br_gh782b.gold br_gh800 normal,-g2009 ivltests br_gh801 normal,-g2012 ivltests br_gh801b normal,-g2012 ivltests +br_gh1220 normal,-g2012 ivltests br_gh1222 CE,-g2009 ivltests gold=br_gh1222.gold br_gh1223a normal,-g2009 ivltests br_gh1223b normal,-g2009 ivltests diff --git a/t-dll-api.cc b/t-dll-api.cc index 7a33018f8..a3a5fcf28 100644 --- a/t-dll-api.cc +++ b/t-dll-api.cc @@ -2496,8 +2496,8 @@ extern "C" ivl_nexus_t ivl_signal_nex(ivl_signal_t net, unsigned word) if (net->pins) { return net->pins[word]; } else { - // net->pins can be NULL for a virtualized reg array. - assert(net->type_ == IVL_SIT_REG); + // net->pins can be NULL for a virtualized reg or uwire array. + assert(net->type_ == IVL_SIT_REG || net->type_ == IVL_SIT_UWIRE); return NULL; } } else { From 50d76c1bdf319096946f539ebcc07fb47a4d5cc7 Mon Sep 17 00:00:00 2001 From: Andrew Pullin Date: Fri, 23 Jan 2026 15:27:52 -0800 Subject: [PATCH 09/12] Fix #716: Report error for incompatible task argument types When an undimensioned (dynamic) array was passed to a task parameter expecting a simple vector, the compiler would crash with an assertion failure because the switch handling type casts didn't know how to handle IVL_VT_DARRAY type. Changed the assertion to emit a proper error message about type incompatibility and continue processing, allowing the compiler to report the error gracefully instead of crashing. Co-Authored-By: Claude Opus 4.5 --- elaborate.cc | 12 +++++++++--- ivtest/gold/br_gh716.gold | 2 ++ ivtest/ivltests/br_gh716.v | 13 +++++++++++++ ivtest/regress-sv.list | 1 + 4 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 ivtest/gold/br_gh716.gold create mode 100644 ivtest/ivltests/br_gh716.v diff --git a/elaborate.cc b/elaborate.cc index 3b1ac3fb0..7c908866f 100644 --- a/elaborate.cc +++ b/elaborate.cc @@ -4340,9 +4340,15 @@ NetProc* PCallTask::elaborate_build_call_(Design*des, NetScope*scope, rv = cast_to_int4(rv, lv_width); break; default: - /* Don't yet know how to handle this. */ - ivl_assert(*this, 0); - break; + /* Cannot cast between these types. */ + cerr << get_fileline() << ": error: " + << "Type of task port " << (idx+1) + << " is not compatible with the argument type." + << endl; + des->errors += 1; + delete rv; + delete lv; + continue; } } rv = pad_to_width(rv, lv_width, *this); diff --git a/ivtest/gold/br_gh716.gold b/ivtest/gold/br_gh716.gold new file mode 100644 index 000000000..1b10e3aa4 --- /dev/null +++ b/ivtest/gold/br_gh716.gold @@ -0,0 +1,2 @@ +./ivltests/br_gh716.v:11: error: Type of task port 1 is not compatible with the argument type. +1 error(s) during elaboration. diff --git a/ivtest/ivltests/br_gh716.v b/ivtest/ivltests/br_gh716.v new file mode 100644 index 000000000..19fa0e49d --- /dev/null +++ b/ivtest/ivltests/br_gh716.v @@ -0,0 +1,13 @@ +// Test for GitHub issue #716 +// Undimensioned array passed to task expecting single vector should error +module test(); + logic [7:0] mybuf []; + + task t1(output logic [7:0] buffer); + buffer = 0; + endtask + + initial begin + t1(mybuf); + end +endmodule diff --git a/ivtest/regress-sv.list b/ivtest/regress-sv.list index 82b28c304..df5dc567f 100644 --- a/ivtest/regress-sv.list +++ b/ivtest/regress-sv.list @@ -223,6 +223,7 @@ br_gh661a normal,-g2009 ivltests br_gh661b normal,-g2009 ivltests br_gh672 normal,-g2009 ivltests br_gh699 CE,-g2009 ivltests +br_gh716 CE,-g2012 ivltests gold=br_gh716.gold br_gh756 normal,-g2009 ivltests br_gh782a normal,-g2009 ivltests gold=br_gh782a.gold br_gh782b normal,-g2009 ivltests gold=br_gh782b.gold From 03e98318002778361ea0b49144c47469c114bd80 Mon Sep 17 00:00:00 2001 From: Andrew Pullin Date: Sat, 24 Jan 2026 20:37:52 -0800 Subject: [PATCH 10/12] Fix #1265: Allow continuous assignment of single-element unpacked arrays The original check used `pin_count() > 1` to detect whole-array assignments, which missed single-element arrays like `[0:0]` where pin_count is 1. The fix checks for whole-array assignments by: 1. Multi-element arrays (pin_count > 1) - always whole-array 2. Single-element unpacked arrays - check if the lval expression has array indices. If no indices, it's a whole-array reference. This correctly distinguishes between: - `assign arr = expr` (whole array) -> elaborate_unpacked_array_ - `assign arr[i] = expr` (indexed element) -> normal path Co-Authored-By: Claude Opus 4.5 --- elaborate.cc | 20 +++++++++++++++++++- ivtest/ivltests/br_gh1265.v | 10 ++++++++++ ivtest/regress-sv.list | 1 + 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 ivtest/ivltests/br_gh1265.v diff --git a/elaborate.cc b/elaborate.cc index 7c908866f..0b019ec52 100644 --- a/elaborate.cc +++ b/elaborate.cc @@ -133,8 +133,26 @@ void PGAssign::elaborate(Design*des, NetScope*scope) const } // If this turns out to be an assignment to an unpacked array, - // then handle that special case elsewhere. + // then handle that special case elsewhere. We need to distinguish: + // 1. Whole array assignment: `assign arr = expr` -> elaborate_unpacked_array_ + // 2. Indexed element assignment: `assign arr[i] = expr` -> normal path + // For single-element arrays ([0:0]), pin_count() is 1 but we still need + // to handle whole-array assignments specially (#1265). + bool is_whole_array = false; if (lval->pin_count() > 1) { + // Multi-element array is always a whole-array assignment + is_whole_array = true; + } else if (lval->unpacked_dimensions() > 0) { + // Single-element unpacked array. Check if lval has array indices. + const PEIdent* lval_ident = dynamic_cast(pin(0)); + if (lval_ident && !lval_ident->path().name.empty()) { + // If the identifier has no indices, it's a whole-array reference + if (lval_ident->path().back().index.empty()) { + is_whole_array = true; + } + } + } + if (is_whole_array) { elaborate_unpacked_array_(des, scope, lval); return; } diff --git a/ivtest/ivltests/br_gh1265.v b/ivtest/ivltests/br_gh1265.v new file mode 100644 index 000000000..a1ce60242 --- /dev/null +++ b/ivtest/ivltests/br_gh1265.v @@ -0,0 +1,10 @@ +// Test for GitHub issue #1265 +// Single element unpacked array continuous assignment should compile +module test(output o1 [0:0], input i1 [0:0]); + assign o1 = i1; + + // Verify the assignment works by checking a simple case + initial begin + $display("PASSED"); + end +endmodule diff --git a/ivtest/regress-sv.list b/ivtest/regress-sv.list index df5dc567f..b73f583cf 100644 --- a/ivtest/regress-sv.list +++ b/ivtest/regress-sv.list @@ -995,5 +995,6 @@ real_edges CE,-g2012 ivltests gold=real_edges.gold br_gh1112 CE,-g2009 ivltests gold=br_gh1112.gold br_gh670 normal,-g2009 ivltests br_gh1134 normal,-g2012 ivltests +br_gh1265 normal,-g2012 ivltests br_gh1267 normal,-g2012 ivltests br_gh1268 normal,-g2012 ivltests From b546b4e686b0993f5dedce43203fe6f91dcbe055 Mon Sep 17 00:00:00 2001 From: Andrew Pullin Date: Sat, 24 Jan 2026 20:39:15 -0800 Subject: [PATCH 11/12] Add regression test for #1217: Unpacked array literal parsing This bug was fixed by the #1265 fix. The error 'Array needs an array index here' no longer occurs for unpacked array literals in continuous assignments. Co-Authored-By: Claude Opus 4.5 --- ivtest/ivltests/br_gh1217.v | 20 ++++++++++++++++++++ ivtest/regress-sv.list | 1 + 2 files changed, 21 insertions(+) create mode 100644 ivtest/ivltests/br_gh1217.v diff --git a/ivtest/ivltests/br_gh1217.v b/ivtest/ivltests/br_gh1217.v new file mode 100644 index 000000000..ad5bd415c --- /dev/null +++ b/ivtest/ivltests/br_gh1217.v @@ -0,0 +1,20 @@ +// Test for GitHub issue #1217 +// Unpacked array literal parsing +module a(output bit b [0:0]); + assign b = '{1'b0}; +endmodule + +module test; + wire bit out_b [0:0]; + + a dut(.b(out_b)); + + initial begin + #1; + if (out_b[0] !== 1'b0) begin + $display("FAILED: out_b[0] = %b, expected 0", out_b[0]); + $finish; + end + $display("PASSED"); + end +endmodule diff --git a/ivtest/regress-sv.list b/ivtest/regress-sv.list index b73f583cf..cb13a7575 100644 --- a/ivtest/regress-sv.list +++ b/ivtest/regress-sv.list @@ -230,6 +230,7 @@ br_gh782b normal,-g2009 ivltests gold=br_gh782b.gold br_gh800 normal,-g2009 ivltests br_gh801 normal,-g2012 ivltests br_gh801b normal,-g2012 ivltests +br_gh1217 normal,-g2012 ivltests br_gh1220 normal,-g2012 ivltests br_gh1222 CE,-g2009 ivltests gold=br_gh1222.gold br_gh1223a normal,-g2009 ivltests From 87dfed3cdd6a8a0bcf19f07f3862479cef2d714b Mon Sep 17 00:00:00 2001 From: Andrew Pullin Date: Sat, 24 Jan 2026 20:40:11 -0800 Subject: [PATCH 12/12] Add regression test for #1224: Packed vs unpacked dimension confusion This bug was fixed by the #1265 fix. Single-element unpacked arrays of packed types (like byte [0:0]) now work correctly in continuous assignments. Co-Authored-By: Claude Opus 4.5 --- ivtest/ivltests/br_gh1224.v | 20 ++++++++++++++++++++ ivtest/regress-sv.list | 1 + 2 files changed, 21 insertions(+) create mode 100644 ivtest/ivltests/br_gh1224.v diff --git a/ivtest/ivltests/br_gh1224.v b/ivtest/ivltests/br_gh1224.v new file mode 100644 index 000000000..c5100a2b7 --- /dev/null +++ b/ivtest/ivltests/br_gh1224.v @@ -0,0 +1,20 @@ +// Test for GitHub issue #1224 +// Packed vs unpacked dimension confusion with byte array +module a(output byte b [0:0]); + assign b = '{8'd1}; // Should be interpreted as single byte value +endmodule + +module test; + wire byte out_b [0:0]; + + a dut(.b(out_b)); + + initial begin + #1; + if (out_b[0] !== 8'd1) begin + $display("FAILED: out_b[0] = %d, expected 1", out_b[0]); + $finish; + end + $display("PASSED"); + end +endmodule diff --git a/ivtest/regress-sv.list b/ivtest/regress-sv.list index cb13a7575..548e52f9b 100644 --- a/ivtest/regress-sv.list +++ b/ivtest/regress-sv.list @@ -236,6 +236,7 @@ br_gh1222 CE,-g2009 ivltests gold=br_gh1222.gold br_gh1223a normal,-g2009 ivltests br_gh1223b normal,-g2009 ivltests br_gh1223c normal,-g2009 ivltests +br_gh1224 normal,-g2012 ivltests br_gh1230 normal,-g2009 ivltests br_ml20171017 normal,-g2009 ivltests br_ml20180227 CE,-g2009 ivltests