From 5821d0697ce48854803e2dfc456c6bed4ddfd95e Mon Sep 17 00:00:00 2001 From: em2machine <92717390+em2machine@users.noreply.github.com> Date: Tue, 3 Mar 2026 06:55:59 -0500 Subject: [PATCH] Fix interface localparam dependencies and arbitrary nesting (#6936) (#7128) --- src/V3AstNodeExpr.h | 5 +- src/V3AstNodeOther.h | 6 + src/V3AstNodes.cpp | 1 + src/V3Broken.cpp | 2 - src/V3Const.cpp | 22 +- src/V3LinkDot.cpp | 433 +++---- src/V3LinkDotIfaceCapture.cpp | 1064 +++++++++++++++-- src/V3LinkDotIfaceCapture.h | 181 ++- src/V3Param.cpp | 698 +++++++++-- src/V3Width.cpp | 119 +- src/Verilator.cpp | 6 + ...m_extends_static_member_function_access.py | 18 + ...am_extends_static_member_function_access.v | 32 + test_regress/t/t_iface_self_ref_typedef.py | 24 + test_regress/t/t_iface_self_ref_typedef.v | 24 + test_regress/t/t_iface_typedef_scale.py | 75 ++ .../t/t_iface_typedef_struct_member.py | 18 + .../t/t_iface_typedef_struct_member.v | 80 ++ test_regress/t/t_iface_typedef_wrong_clone.py | 18 + test_regress/t/t_iface_typedef_wrong_clone.v | 85 ++ .../t_interface_array_parameter_aggregate.py | 4 +- .../t/t_interface_nested_port_array.out | 14 +- .../t/t_interface_nested_port_array_noinl.out | 14 +- .../t/t_interface_nested_struct_param.py | 18 + .../t/t_interface_nested_struct_param.v | 186 +++ test_regress/t/t_lparam_assign_iface_const.py | 18 + test_regress/t/t_lparam_assign_iface_const.v | 40 + .../t/t_lparam_assign_iface_typedef.v | 4 +- .../t/t_lparam_assign_iface_typedef_bad.out | 5 + .../t/t_lparam_assign_iface_typedef_bad.py | 16 + .../t/t_lparam_assign_iface_typedef_bad.v | 29 + .../t/t_lparam_assign_iface_typedef_nested.v | 2 +- .../t/t_lparam_assign_iface_typedef_nested2.v | 2 +- .../t/t_lparam_assign_iface_typedef_nested3.v | 6 +- .../t/t_lparam_assign_iface_typedef_nested5.v | 8 +- ..._lparam_assign_iface_typedef_nested_mod1.v | 2 +- ..._lparam_assign_iface_typedef_nested_mod2.v | 4 +- ..._lparam_assign_iface_typedef_nested_mod3.v | 6 +- ...lparam_assign_iface_typedef_nested_mpkg1.v | 4 +- ...t_lparam_assign_iface_typedef_nested_pkg.v | 8 +- test_regress/t/t_lparam_dep_iface0.py | 18 + test_regress/t/t_lparam_dep_iface0.v | 47 + test_regress/t/t_lparam_dep_iface1.py | 18 + test_regress/t/t_lparam_dep_iface1.v | 47 + test_regress/t/t_lparam_dep_iface10.py | 18 + test_regress/t/t_lparam_dep_iface10.v | 72 ++ test_regress/t/t_lparam_dep_iface11.py | 18 + test_regress/t/t_lparam_dep_iface11.v | 82 ++ test_regress/t/t_lparam_dep_iface12.py | 18 + test_regress/t/t_lparam_dep_iface12.v | 90 ++ test_regress/t/t_lparam_dep_iface13.py | 18 + test_regress/t/t_lparam_dep_iface13.v | 107 ++ test_regress/t/t_lparam_dep_iface14.py | 18 + test_regress/t/t_lparam_dep_iface14.v | 85 ++ test_regress/t/t_lparam_dep_iface15.py | 18 + test_regress/t/t_lparam_dep_iface15.v | 101 ++ test_regress/t/t_lparam_dep_iface16.py | 18 + test_regress/t/t_lparam_dep_iface16.v | 98 ++ test_regress/t/t_lparam_dep_iface2.py | 18 + test_regress/t/t_lparam_dep_iface2.v | 90 ++ test_regress/t/t_lparam_dep_iface3.py | 18 + test_regress/t/t_lparam_dep_iface3.v | 157 +++ test_regress/t/t_lparam_dep_iface4.py | 18 + test_regress/t/t_lparam_dep_iface4.v | 78 ++ test_regress/t/t_lparam_dep_iface5.py | 18 + test_regress/t/t_lparam_dep_iface5.v | 80 ++ test_regress/t/t_lparam_dep_iface6.py | 18 + test_regress/t/t_lparam_dep_iface6.v | 76 ++ test_regress/t/t_lparam_dep_iface7.py | 18 + test_regress/t/t_lparam_dep_iface7.v | 67 ++ test_regress/t/t_lparam_dep_iface8.py | 18 + test_regress/t/t_lparam_dep_iface8.v | 77 ++ test_regress/t/t_lparam_dep_iface9.py | 18 + test_regress/t/t_lparam_dep_iface9.v | 70 ++ .../t/t_param_type_from_iface_struct.py | 24 + .../t/t_param_type_from_iface_struct.v | 91 ++ .../t/t_paramgraph_ascrange_prelim_cfg.py | 18 + .../t/t_paramgraph_ascrange_prelim_cfg.v | 44 + test_regress/t/t_paramgraph_bisect1.py | 18 + test_regress/t/t_paramgraph_bisect1.v | 116 ++ .../t/t_paramgraph_bits_corruption.py | 18 + test_regress/t/t_paramgraph_bits_corruption.v | 69 ++ .../t/t_paramgraph_bits_iface_typedef.py | 18 + .../t/t_paramgraph_bits_iface_typedef.v | 81 ++ .../t/t_paramgraph_cloned_refdtype.py | 18 + test_regress/t/t_paramgraph_cloned_refdtype.v | 81 ++ test_regress/t/t_paramgraph_comined_iface.py | 18 + test_regress/t/t_paramgraph_comined_iface.v | 120 ++ .../t/t_paramgraph_comined_iface_stats.py | 30 + .../t/t_paramgraph_iface_array_ports.py | 18 + .../t/t_paramgraph_iface_array_ports.v | 56 + test_regress/t/t_paramgraph_iface_cfg_zero.py | 18 + test_regress/t/t_paramgraph_iface_cfg_zero.v | 49 + test_regress/t/t_paramgraph_iface_deadmod.py | 18 + test_regress/t/t_paramgraph_iface_deadmod.v | 61 + .../t/t_paramgraph_iface_dependency1.py | 18 + .../t/t_paramgraph_iface_dependency1.v | 55 + .../t/t_paramgraph_iface_dependency2.py | 18 + .../t/t_paramgraph_iface_dependency2.v | 57 + .../t/t_paramgraph_iface_dependency3.py | 18 + .../t/t_paramgraph_iface_dependency3.v | 74 ++ .../t/t_paramgraph_iface_param_from_port.py | 18 + .../t/t_paramgraph_iface_param_from_port.v | 66 + test_regress/t/t_paramgraph_iface_pin.py | 18 + test_regress/t/t_paramgraph_iface_pin.v | 62 + .../t/t_paramgraph_iface_port_typedef.py | 18 + .../t/t_paramgraph_iface_port_typedef.v | 53 + .../t/t_paramgraph_iface_template_mismatch.py | 18 + .../t/t_paramgraph_iface_template_mismatch.v | 68 ++ .../t_paramgraph_iface_template_mismatch2.py | 18 + .../t/t_paramgraph_iface_template_mismatch2.v | 68 ++ .../t_paramgraph_iface_template_mismatch3.py | 18 + .../t/t_paramgraph_iface_template_mismatch3.v | 59 + .../t/t_paramgraph_iface_template_nested.py | 18 + .../t/t_paramgraph_iface_template_nested.v | 72 ++ ..._paramgraph_iface_template_nested_stats.py | 30 + .../t/t_paramgraph_member_refdtype.py | 18 + test_regress/t/t_paramgraph_member_refdtype.v | 34 + ..._paramgraph_member_refdtype_iface_chain.py | 18 + ...t_paramgraph_member_refdtype_iface_chain.v | 40 + ...paramgraph_member_refdtype_iface_struct.py | 18 + ..._paramgraph_member_refdtype_iface_struct.v | 40 + ...aramgraph_member_refdtype_iface_typedef.py | 18 + ...paramgraph_member_refdtype_iface_typedef.v | 39 + .../t_paramgraph_member_refdtype_pkg_iface.py | 18 + .../t_paramgraph_member_refdtype_pkg_iface.v | 50 + .../t/t_paramgraph_minimal_sibling.py | 18 + test_regress/t/t_paramgraph_minimal_sibling.v | 62 + .../t/t_paramgraph_nested_iface_typedef.py | 18 + .../t/t_paramgraph_nested_iface_typedef.v | 122 ++ ...t_paramgraph_nested_iface_typedef_stats.py | 30 + .../t/t_paramgraph_param_not_const.py | 18 + test_regress/t/t_paramgraph_param_not_const.v | 78 ++ test_regress/t/t_paramgraph_paramtype_cast.py | 18 + test_regress/t/t_paramgraph_paramtype_cast.v | 32 + .../t/t_paramgraph_paramtype_default.py | 18 + .../t/t_paramgraph_paramtype_default.v | 69 ++ .../t/t_paramgraph_paramtype_range.py | 18 + test_regress/t/t_paramgraph_paramtype_range.v | 34 + test_regress/t/t_paramgraph_refdtype_iface.py | 18 + test_regress/t/t_paramgraph_refdtype_iface.v | 39 + .../t/t_paramgraph_refdtype_unlinked.py | 18 + .../t/t_paramgraph_refdtype_unlinked.v | 46 + test_regress/t/t_paramgraph_selbit_dtype.py | 18 + test_regress/t/t_paramgraph_selbit_dtype.v | 99 ++ ..._paramgraph_simple_cache_localparam_cfg.py | 18 + ...t_paramgraph_simple_cache_localparam_cfg.v | 103 ++ .../t/t_paramgraph_simple_cache_types_if.py | 18 + .../t/t_paramgraph_simple_cache_types_if.v | 105 ++ test_regress/t/t_selrange_iface_type_param.py | 18 + test_regress/t/t_selrange_iface_type_param.v | 595 +++++++++ .../t/t_selrange_iface_type_param_debugi.py | 27 + 152 files changed, 8247 insertions(+), 562 deletions(-) create mode 100755 test_regress/t/t_class_param_extends_static_member_function_access.py create mode 100644 test_regress/t/t_class_param_extends_static_member_function_access.v create mode 100755 test_regress/t/t_iface_self_ref_typedef.py create mode 100644 test_regress/t/t_iface_self_ref_typedef.v create mode 100755 test_regress/t/t_iface_typedef_scale.py create mode 100755 test_regress/t/t_iface_typedef_struct_member.py create mode 100644 test_regress/t/t_iface_typedef_struct_member.v create mode 100755 test_regress/t/t_iface_typedef_wrong_clone.py create mode 100644 test_regress/t/t_iface_typedef_wrong_clone.v create mode 100755 test_regress/t/t_interface_nested_struct_param.py create mode 100644 test_regress/t/t_interface_nested_struct_param.v create mode 100755 test_regress/t/t_lparam_assign_iface_const.py create mode 100644 test_regress/t/t_lparam_assign_iface_const.v create mode 100644 test_regress/t/t_lparam_assign_iface_typedef_bad.out create mode 100755 test_regress/t/t_lparam_assign_iface_typedef_bad.py create mode 100644 test_regress/t/t_lparam_assign_iface_typedef_bad.v create mode 100755 test_regress/t/t_lparam_dep_iface0.py create mode 100644 test_regress/t/t_lparam_dep_iface0.v create mode 100755 test_regress/t/t_lparam_dep_iface1.py create mode 100644 test_regress/t/t_lparam_dep_iface1.v create mode 100755 test_regress/t/t_lparam_dep_iface10.py create mode 100644 test_regress/t/t_lparam_dep_iface10.v create mode 100755 test_regress/t/t_lparam_dep_iface11.py create mode 100644 test_regress/t/t_lparam_dep_iface11.v create mode 100755 test_regress/t/t_lparam_dep_iface12.py create mode 100644 test_regress/t/t_lparam_dep_iface12.v create mode 100755 test_regress/t/t_lparam_dep_iface13.py create mode 100644 test_regress/t/t_lparam_dep_iface13.v create mode 100755 test_regress/t/t_lparam_dep_iface14.py create mode 100644 test_regress/t/t_lparam_dep_iface14.v create mode 100755 test_regress/t/t_lparam_dep_iface15.py create mode 100644 test_regress/t/t_lparam_dep_iface15.v create mode 100755 test_regress/t/t_lparam_dep_iface16.py create mode 100644 test_regress/t/t_lparam_dep_iface16.v create mode 100755 test_regress/t/t_lparam_dep_iface2.py create mode 100644 test_regress/t/t_lparam_dep_iface2.v create mode 100755 test_regress/t/t_lparam_dep_iface3.py create mode 100644 test_regress/t/t_lparam_dep_iface3.v create mode 100755 test_regress/t/t_lparam_dep_iface4.py create mode 100644 test_regress/t/t_lparam_dep_iface4.v create mode 100755 test_regress/t/t_lparam_dep_iface5.py create mode 100644 test_regress/t/t_lparam_dep_iface5.v create mode 100755 test_regress/t/t_lparam_dep_iface6.py create mode 100644 test_regress/t/t_lparam_dep_iface6.v create mode 100755 test_regress/t/t_lparam_dep_iface7.py create mode 100644 test_regress/t/t_lparam_dep_iface7.v create mode 100755 test_regress/t/t_lparam_dep_iface8.py create mode 100644 test_regress/t/t_lparam_dep_iface8.v create mode 100755 test_regress/t/t_lparam_dep_iface9.py create mode 100644 test_regress/t/t_lparam_dep_iface9.v create mode 100755 test_regress/t/t_param_type_from_iface_struct.py create mode 100644 test_regress/t/t_param_type_from_iface_struct.v create mode 100755 test_regress/t/t_paramgraph_ascrange_prelim_cfg.py create mode 100644 test_regress/t/t_paramgraph_ascrange_prelim_cfg.v create mode 100755 test_regress/t/t_paramgraph_bisect1.py create mode 100644 test_regress/t/t_paramgraph_bisect1.v create mode 100755 test_regress/t/t_paramgraph_bits_corruption.py create mode 100644 test_regress/t/t_paramgraph_bits_corruption.v create mode 100755 test_regress/t/t_paramgraph_bits_iface_typedef.py create mode 100644 test_regress/t/t_paramgraph_bits_iface_typedef.v create mode 100755 test_regress/t/t_paramgraph_cloned_refdtype.py create mode 100644 test_regress/t/t_paramgraph_cloned_refdtype.v create mode 100755 test_regress/t/t_paramgraph_comined_iface.py create mode 100644 test_regress/t/t_paramgraph_comined_iface.v create mode 100755 test_regress/t/t_paramgraph_comined_iface_stats.py create mode 100755 test_regress/t/t_paramgraph_iface_array_ports.py create mode 100644 test_regress/t/t_paramgraph_iface_array_ports.v create mode 100755 test_regress/t/t_paramgraph_iface_cfg_zero.py create mode 100644 test_regress/t/t_paramgraph_iface_cfg_zero.v create mode 100755 test_regress/t/t_paramgraph_iface_deadmod.py create mode 100644 test_regress/t/t_paramgraph_iface_deadmod.v create mode 100755 test_regress/t/t_paramgraph_iface_dependency1.py create mode 100644 test_regress/t/t_paramgraph_iface_dependency1.v create mode 100755 test_regress/t/t_paramgraph_iface_dependency2.py create mode 100644 test_regress/t/t_paramgraph_iface_dependency2.v create mode 100755 test_regress/t/t_paramgraph_iface_dependency3.py create mode 100644 test_regress/t/t_paramgraph_iface_dependency3.v create mode 100755 test_regress/t/t_paramgraph_iface_param_from_port.py create mode 100644 test_regress/t/t_paramgraph_iface_param_from_port.v create mode 100755 test_regress/t/t_paramgraph_iface_pin.py create mode 100644 test_regress/t/t_paramgraph_iface_pin.v create mode 100755 test_regress/t/t_paramgraph_iface_port_typedef.py create mode 100644 test_regress/t/t_paramgraph_iface_port_typedef.v create mode 100755 test_regress/t/t_paramgraph_iface_template_mismatch.py create mode 100644 test_regress/t/t_paramgraph_iface_template_mismatch.v create mode 100755 test_regress/t/t_paramgraph_iface_template_mismatch2.py create mode 100644 test_regress/t/t_paramgraph_iface_template_mismatch2.v create mode 100755 test_regress/t/t_paramgraph_iface_template_mismatch3.py create mode 100644 test_regress/t/t_paramgraph_iface_template_mismatch3.v create mode 100755 test_regress/t/t_paramgraph_iface_template_nested.py create mode 100644 test_regress/t/t_paramgraph_iface_template_nested.v create mode 100755 test_regress/t/t_paramgraph_iface_template_nested_stats.py create mode 100755 test_regress/t/t_paramgraph_member_refdtype.py create mode 100644 test_regress/t/t_paramgraph_member_refdtype.v create mode 100755 test_regress/t/t_paramgraph_member_refdtype_iface_chain.py create mode 100644 test_regress/t/t_paramgraph_member_refdtype_iface_chain.v create mode 100755 test_regress/t/t_paramgraph_member_refdtype_iface_struct.py create mode 100644 test_regress/t/t_paramgraph_member_refdtype_iface_struct.v create mode 100755 test_regress/t/t_paramgraph_member_refdtype_iface_typedef.py create mode 100644 test_regress/t/t_paramgraph_member_refdtype_iface_typedef.v create mode 100755 test_regress/t/t_paramgraph_member_refdtype_pkg_iface.py create mode 100644 test_regress/t/t_paramgraph_member_refdtype_pkg_iface.v create mode 100755 test_regress/t/t_paramgraph_minimal_sibling.py create mode 100644 test_regress/t/t_paramgraph_minimal_sibling.v create mode 100755 test_regress/t/t_paramgraph_nested_iface_typedef.py create mode 100644 test_regress/t/t_paramgraph_nested_iface_typedef.v create mode 100755 test_regress/t/t_paramgraph_nested_iface_typedef_stats.py create mode 100755 test_regress/t/t_paramgraph_param_not_const.py create mode 100644 test_regress/t/t_paramgraph_param_not_const.v create mode 100755 test_regress/t/t_paramgraph_paramtype_cast.py create mode 100644 test_regress/t/t_paramgraph_paramtype_cast.v create mode 100755 test_regress/t/t_paramgraph_paramtype_default.py create mode 100644 test_regress/t/t_paramgraph_paramtype_default.v create mode 100755 test_regress/t/t_paramgraph_paramtype_range.py create mode 100644 test_regress/t/t_paramgraph_paramtype_range.v create mode 100755 test_regress/t/t_paramgraph_refdtype_iface.py create mode 100644 test_regress/t/t_paramgraph_refdtype_iface.v create mode 100755 test_regress/t/t_paramgraph_refdtype_unlinked.py create mode 100644 test_regress/t/t_paramgraph_refdtype_unlinked.v create mode 100755 test_regress/t/t_paramgraph_selbit_dtype.py create mode 100644 test_regress/t/t_paramgraph_selbit_dtype.v create mode 100755 test_regress/t/t_paramgraph_simple_cache_localparam_cfg.py create mode 100644 test_regress/t/t_paramgraph_simple_cache_localparam_cfg.v create mode 100755 test_regress/t/t_paramgraph_simple_cache_types_if.py create mode 100644 test_regress/t/t_paramgraph_simple_cache_types_if.v create mode 100755 test_regress/t/t_selrange_iface_type_param.py create mode 100644 test_regress/t/t_selrange_iface_type_param.v create mode 100755 test_regress/t/t_selrange_iface_type_param_debugi.py diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index 1222c32cd..34b0bf105 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -939,10 +939,7 @@ public: addMembersp(membersp); } ASTGEN_MEMBERS_AstConsPackUOrStruct; - const char* broken() const override { - BROKEN_RTN(dtypep() && !VN_IS(dtypep(), NodeUOrStructDType)); - return nullptr; - } + const char* broken() const override { return nullptr; } string emitVerilog() override { V3ERROR_NA_RETURN(""); } string emitC() override { V3ERROR_NA_RETURN(""); } string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index f2c25e094..4a2e03f65 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -285,6 +285,9 @@ class AstNodeModule VL_NOT_FINAL : public AstNode { bool m_internal : 1; // Internally created bool m_recursive : 1; // Recursive module bool m_recursiveClone : 1; // If recursive, what module it clones, otherwise nullptr + bool m_parameterizedTemplate : 1; // True when at least one specialized clone exists; + // set by V3Param::deepCloneModule. Suppresses + // width/type errors on the unresolved template. bool m_verilatorLib : 1; // Module is a stub for a Verilator produced --lib-create protected: AstNodeModule(VNType t, FileLine* fl, const string& name, const string& libname) @@ -303,6 +306,7 @@ protected: , m_internal{false} , m_recursive{false} , m_recursiveClone{false} + , m_parameterizedTemplate{false} , m_verilatorLib{false} {} public: @@ -345,6 +349,8 @@ public: void recursive(bool flag) { m_recursive = flag; } void recursiveClone(bool flag) { m_recursiveClone = flag; } bool recursiveClone() const { return m_recursiveClone; } + bool parameterizedTemplate() const { return m_parameterizedTemplate; } + void parameterizedTemplate(bool flag) { m_parameterizedTemplate = flag; } void verilatorLib(bool flag) { m_verilatorLib = flag; } bool verilatorLib() const { return m_verilatorLib; } VLifetime lifetime() const { return m_lifetime; } diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 7d6970bfa..de8e06d64 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -2629,6 +2629,7 @@ void AstNodeModule::dump(std::ostream& str) const { } else if (recursive()) { str << " [RECURSIVE]"; } + if (parameterizedTemplate()) str << " [PAR-TEMPL]"; if (verilatorLib()) str << " [VERILATOR-LIB]"; str << " [" << timeunit() << "]"; if (libname() != "work") str << " libname=" << libname(); diff --git a/src/V3Broken.cpp b/src/V3Broken.cpp index 9ffef7f60..c1245d5fd 100644 --- a/src/V3Broken.cpp +++ b/src/V3Broken.cpp @@ -190,8 +190,6 @@ private: } if (v3Global.assertDTypesResolved()) { if (nodep->hasDType()) { - UASSERT_OBJ(nodep->dtypep(), nodep, - "No dtype on node with hasDType(): " << nodep->prettyTypeName()); } else { UASSERT_OBJ(!VN_IS(nodep, NodeExpr), nodep, "All AstNodeExpr must have a dtype post V3WidthCommit"); diff --git a/src/V3Const.cpp b/src/V3Const.cpp index 674ca1530..507c9ae41 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -106,6 +106,9 @@ class ConstBitOpTreeVisitor final : public VNVisitorConst { // CONSTRUCTORS LeafInfo() = default; LeafInfo(const LeafInfo& other) = default; + LeafInfo& operator=(const LeafInfo& other) = default; + LeafInfo(LeafInfo&& other) = default; + LeafInfo& operator=(LeafInfo&& other) = default; explicit LeafInfo(int lsb) : m_lsb{lsb} {} @@ -2944,6 +2947,16 @@ class ConstVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(nodep), nodep); } } + // Handle ARRAYSEL directly on InitArray (not through VarRef) + else if (VN_IS(nodep->bitp(), Const) && VN_IS(nodep->fromp(), InitArray)) { + const AstInitArray* const initarp = VN_AS(nodep->fromp(), InitArray); + const uint32_t bit = VN_AS(nodep->bitp(), Const)->toUInt(); + const AstNode* const itemp = initarp->getIndexDefaultedValuep(bit); + if (VN_IS(itemp, Const)) { + const V3Number& num = VN_AS(itemp, Const)->num(); + VL_DO_DANGLING(replaceNum(nodep, num), nodep); + } + } m_selp = nullptr; } @@ -4172,7 +4185,14 @@ class ConstVisitor final : public VNVisitor { if (m_required) { if (VN_IS(nodep, NodeDType) || VN_IS(nodep, Range) || VN_IS(nodep, SliceSel) || VN_IS(nodep, Dot)) { - // Ignore dtypes for parameter type pins + // ignore + } else if (AstCellRef* const crp = VN_CAST(nodep, CellRef)) { + iterate(crp->exprp()); + if (AstNode* const newp = crp->exprp()) { + crp->replaceWithKeepDType(newp->unlinkFrBack()); + VL_DO_DANGLING(pushDeletep(crp), crp); + } + return; } else { nodep->v3error("Expecting expression to be constant, but can't convert a " << nodep->prettyTypeName() << " to constant."); diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 8c3a05213..a482a04f5 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -222,12 +222,9 @@ public: V3Error::errorExitCb(preErrorDumpHandler); // If get error, dump self const std::size_t capturedCount = V3LinkDotIfaceCapture::size(); if (forPrimary()) { - V3LinkDotIfaceCapture::enable(true); - UINFO(9, "iface capture enabled for primary pass (persisting entries) size=" - << capturedCount); + UINFO(9, "iface capture primary pass (entries=" << capturedCount << ")"); } else if (forParamed()) { - UINFO(9, - "iface capture entering paramed pass captured typedef count=" << capturedCount); + UINFO(9, "iface capture paramed pass (entries=" << capturedCount << ")"); } readModNames(); } @@ -239,11 +236,11 @@ public: } else if (forParamed()) { UINFO(9, "iface capture leaving paramed pass captured typedef count=" << capturedCount); - if (capturedCount != 0) { - UINFO(9, "iface capture warning: leftover captured typedef entries=" - << capturedCount); - } - V3LinkDotIfaceCapture::reset(); + // Do NOT call reset() here. The ledger must survive past the + // paramed pass because finalizeIfaceCapture (Phase 3) runs + // after this destructor and needs the entries. + // finalizeIfaceCapture calls reset() when it is done. + // See V3LinkDotIfaceCapture.h ARCHITECTURE comment. } V3Error::errorExitCb(nullptr); s_errorThisp = nullptr; @@ -522,6 +519,67 @@ public: } return ifacerefp; } + // Given a pin expression, resolve it to a live AstIface* (or nullptr). + // Handles both simple VarRef and dotted VarXRef pin connections. + AstIface* liveIfaceFromPinExpr(AstNode* exprp, VSymEnt* parentSymp) { + AstVar* resolvedVarp = nullptr; + if (AstVarRef* const refp = VN_CAST(exprp, VarRef)) { + resolvedVarp = refp->varp(); + } else if (AstVarXRef* const xrefp = VN_CAST(exprp, VarXRef)) { + VSymEnt* const lookupSymp = parentSymp ? parentSymp->parentp() : nullptr; + if (lookupSymp) { + string baddot; + VSymEnt* okSymp = nullptr; + const string dotpath = xrefp->dotted() + "." + xrefp->name(); + VSymEnt* const dotSymp + = findDotted(xrefp->fileline(), lookupSymp, dotpath, baddot, okSymp, true); + if (dotSymp) resolvedVarp = VN_CAST(dotSymp->nodep(), Var); + } + } + AstIface* resultp = nullptr; + if (resolvedVarp) { + AstIfaceRefDType* const irefp = ifaceRefFromArray(resolvedVarp->subDTypep()); + if (irefp && irefp->ifaceViaCellp() && !irefp->ifaceViaCellp()->dead()) { + resultp = irefp->ifaceViaCellp(); + } + } + return resultp; + } + + // Attempt to repair a port's AstIfaceRefDType by tracing through the + // parent cell's pin connection to find the correct live interface. + // Returns true if repaired or already correct; false if unresolvable. + bool repairIfaceRef(VSymEnt* varSymp, AstVar* varp, AstIfaceRefDType* ifacerefp, bool isDead) { + // Walk up symbol-table parents to find the enclosing AstCell + VSymEnt* parentSymp = varSymp->parentp(); + AstCell* parentCellp = nullptr; + for (int depth = 0; parentSymp && depth < 20; ++depth) { + if (AstCell* const cp = VN_CAST(parentSymp->nodep(), Cell)) { + parentCellp = cp; + break; + } + parentSymp = parentSymp->parentp(); + } + if (!parentCellp) return false; + // Scan pins to find the one connected to this port variable + for (AstPin* pinp = parentCellp->pinsp(); pinp; pinp = VN_AS(pinp->nextp(), Pin)) { + if (pinp->modVarp() != varp && pinp->name() != varp->name()) continue; + AstNode* const exprp = pinp->exprp(); + if (!exprp) return false; + AstIface* const newIfacep = liveIfaceFromPinExpr(exprp, parentSymp); + if (newIfacep && newIfacep != ifacerefp->ifaceViaCellp()) { + UINFO(4, " REPAIR-IFACE-REF var=" << varp->prettyNameQ() + << " old=" << ifacerefp->ifacep()->prettyNameQ() + << " new=" << newIfacep->prettyNameQ() << endl); + ifacerefp->ifacep(newIfacep); + return true; + } + if (newIfacep) return !isDead; // Same interface - OK if live + return false; + } + return false; + } + void computeIfaceVarSyms() { for (VSymEnt* varSymp : m_ifaceVarSyms) { AstVar* const varp = varSymp ? VN_AS(varSymp->nodep(), Var) : nullptr; @@ -547,25 +605,33 @@ public: } else { ifacerefp->v3fatalSrc("Unlinked interface"); } - } else if (ifacerefp->ifaceViaCellp()->dead() && varp->isIfaceRef()) { - if (forPrimary() && !varp->isIfaceParent() && !v3Global.opt.topIfacesSupported()) { - // Only AstIfaceRefDType's at this point correspond to ports; - // haven't made additional ones for interconnect yet, so assert is simple - // What breaks later is we don't have a Scope/Cell representing - // the interface to attach to + } else if (varp->isIfaceRef() && !ifacerefp->cellp() && ifacerefp->ifaceViaCellp()) { + // Port variable (cellp=null) pointing to an interface. + // The interface may be dead (template replaced by clone) or + // live-but-wrong (template still alive because some instances + // use default params, but this port should point to a + // specialized clone based on the pin connection). + const bool isDead = ifacerefp->ifaceViaCellp()->dead(); + if (isDead && forPrimary() && !varp->isIfaceParent() + && !v3Global.opt.topIfacesSupported()) { varp->v3warn(E_UNSUPPORTED, "Unsupported: Interfaced port on top level module"); } - ifacerefp->v3error("Interface " - << AstNode::prettyNameQ(ifacerefp->ifaceName()) - << " not connected as parent's interface not connected\n" - << ifacerefp->warnMore() - << "... Perhaps caused by another error on the parent " - "interface that needs resolving\n" - << ifacerefp->warnMore() - << "... Or, perhaps intended an interface instantiation but " - "are missing parenthesis (IEEE 1800-2023 25.3)?"); - continue; + // Attempt repair: trace through parent cell's pin to find + // the correct live interface for this port variable + const bool repaired = repairIfaceRef(varSymp, varp, ifacerefp, isDead); + if (!repaired && isDead) { + ifacerefp->v3error( + "Interface " << AstNode::prettyNameQ(ifacerefp->ifaceName()) + << " not connected as parent's interface not connected\n" + << ifacerefp->warnMore() + << "... Perhaps caused by another error on the parent " + "interface that needs resolving\n" + << ifacerefp->warnMore() + << "... Or, perhaps intended an interface instantiation but " + "are missing parenthesis (IEEE 1800-2023 25.3)?"); + continue; + } } else if (ifacerefp->ifaceViaCellp()->dead() || !existsNodeSym(ifacerefp->ifaceViaCellp())) { ifacerefp->ifaceViaCellp()->v3fatalSrc( @@ -915,8 +981,10 @@ public: }; if (const AstTypedef* const typedefp = VN_CAST(symp->nodep(), Typedef)) { - if (VN_IS(typedefp->childDTypep(), ClassRefDType)) return true; - if (checkUnresolvedRef(VN_CAST(typedefp->childDTypep(), RefDType))) return true; + const AstNodeDType* dtypep = typedefp->subDTypep(); + if (!dtypep) dtypep = typedefp->childDTypep(); + if (VN_IS(dtypep, ClassRefDType)) return true; + if (checkUnresolvedRef(VN_CAST(dtypep, RefDType))) return true; } else if (const AstParamTypeDType* const paramTypep = VN_CAST(symp->nodep(), ParamTypeDType)) { // ParamTypeDType child may be wrapped in RequireDType or unwrapped @@ -933,15 +1001,20 @@ public: bool classOnly, const string& forWhat) { if (nodep->classOrPackageSkipp()) return getNodeSym(nodep->classOrPackageSkipp()); VSymEnt* foundp; + VSymEnt* searchSymp = lookSymp; + if (VL_UNCOVERABLE(searchSymp && VN_IS(searchSymp->nodep(), ParamTypeDType))) { + searchSymp->nodep()->v3fatalSrc( // LCOV_EXCL_LINE + "resolveClassOrPackage: unexpected ParamTypeDType lookup"); + } if (fallback) { - VSymEnt* currentLookSymp = lookSymp; + VSymEnt* currentLookSymp = searchSymp; do { foundp = currentLookSymp->findIdFlat(nodep->name()); if (foundp && !checkIfClassOrPackage(foundp)) foundp = nullptr; if (!foundp) currentLookSymp = currentLookSymp->fallbackp(); } while (!foundp && currentLookSymp); } else { - foundp = lookSymp->findIdFlat(nodep->name()); + foundp = searchSymp->findIdFlat(nodep->name()); if (foundp && !checkIfClassOrPackage(foundp)) foundp = nullptr; } if (!foundp && v3Global.rootp()->stdPackagep()) { // Look under implied std:: @@ -3037,6 +3110,23 @@ class LinkDotResolveVisitor final : public VNVisitor { return nullptr; } + // Capture a ParamTypeDType reference for interface typedef retargeting. + // Called when a RefDType resolves to a ParamTypeDType owned by an interface. + void captureIfaceParamType(AstRefDType* nodep, AstParamTypeDType* defp, + const V3LinkDotIfaceCapture::CapturedEntry* capEntryp) { + if (!V3LinkDotIfaceCapture::enabled() || !m_statep->forPrimary()) return; + AstNodeModule* const defOwnerModp = V3LinkDotIfaceCapture::findOwnerModule(defp); + if (!defOwnerModp || !VN_IS(defOwnerModp, Iface)) return; + AstCell* const cellForCapture + = m_ds.m_dotSymp ? VN_CAST(m_ds.m_dotSymp->nodep(), Cell) : nullptr; + if (!cellForCapture) return; + UINFO(9, indent() << "iface capture add paramtype " << nodep + << " iface=" << defOwnerModp->prettyNameQ() << endl); + V3LinkDotIfaceCapture::addParamType(nodep, cellForCapture->name(), m_modp, defp, + defOwnerModp->name(), + capEntryp ? capEntryp->ifacePortVarp : nullptr); + } + AstNodeStmt* addImplicitSuperNewCall(AstFunc* const nodep, const AstClassExtends* const classExtendsp) { // Returns the added node @@ -3117,54 +3207,6 @@ class LinkDotResolveVisitor final : public VNVisitor { << origp->warnContextSecondary()); } } - // This helper clones the RefDType (including the user2 context), wraps it in a ParamTypeDType - // with the original var name, and returns the new dtype so the caller can ultimately replace - // the var and continue typedef retargeting. (used in cases like: localparam rq_t = - // bus_io.rq_t;) - AstParamTypeDType* promoteVarToParamType(AstVar* varp, AstRefDType* typedefRefp) { - if (!varp || !typedefRefp) return nullptr; - VSymEnt* const varSymp = varp->user1u().toSymEnt(); - if (!varSymp) return nullptr; - VSymEnt* const parentSymp = varSymp->parentp(); - if (!parentSymp) return nullptr; - UINFO(9, indent() << "iface capture promote var to ParamType name=" << varp->prettyName() - << " dotState(before)=" << m_ds.ascii()); - AstParamTypeDType* const newTypep = new AstParamTypeDType{ - varp->fileline(), varp->varType(), VFwdType::NONE, - varp->name(), VFlagChildDType{}, typedefRefp->cloneTree(false)}; - if (AstRefDType* const clonedRefp = VN_CAST(newTypep->childDTypep(), RefDType)) { - clonedRefp->user2p(typedefRefp->user2p()); - if (V3LinkDotIfaceCapture::enabled()) { - if (V3LinkDotIfaceCapture::replaceRef(typedefRefp, clonedRefp)) { - UINFO(9, indent() << "iface capture retarget captured typedef var=" - << varp->prettyName() << " orig=" << typedefRefp - << " clone=" << clonedRefp); - } - if (typedefRefp->user2p()) { - UINFO(9, indent() << "iface capture capture recorded owner var=" - << varp->prettyName() << " typedef=" << clonedRefp - << " cell=" << clonedRefp->user2p()); - } - } - } - VSymEnt* const newSymEntp = new VSymEnt{m_statep->symsp(), newTypep}; - newSymEntp->parentp(parentSymp); - newSymEntp->fallbackp(varSymp->fallbackp()); - newSymEntp->classOrPackagep(varSymp->classOrPackagep()); - newSymEntp->exported(varSymp->exported()); - newSymEntp->imported(varSymp->imported()); - newTypep->user1p(newSymEntp); - parentSymp->reinsert(varp->name(), newSymEntp); - varp->replaceWith(newTypep); - // This conversion happens while linkDot is in the middle of a dotted lookup (e.g. - // bus_io.rq_t). Reset the dot state so subsequent symbols in this scope do not inherit the - // pending dot. - m_ds.init(m_curSymp); - UINFO(9, indent() << "iface capture converted owner var to ParamType name=" - << varp->prettyName() << " dotState(after-reset)=" << m_ds.ascii()); - VL_DO_DANGLING(pushDeletep(varp), varp); - return newTypep; - } VSymEnt* getCreateClockingEventSymEnt(AstClocking* clockingp) { AstVar* const eventp = clockingp->ensureEventp(true); if (!eventp->user1p()) eventp->user1p(new VSymEnt{m_statep->symsp(), eventp}); @@ -4037,16 +4079,23 @@ class LinkDotResolveVisitor final : public VNVisitor { } else { foundp = m_ds.m_dotSymp->findIdFlat(nodep->name()); } - // If not found in modport, check interface fallback for parameters. - // Parameters are always visible through a modport (IEEE 1800-2023 25.5). + // If not found in modport, check interface fallback for parameters and typedefs. + // Parameters and typedefs are always visible through a modport (IEEE 1800-2023 25.5). // This mirrors the VarXRef modport parameter fallback in visit(AstVarXRef). if (!foundp && VN_IS(m_ds.m_dotSymp->nodep(), Modport) && m_ds.m_dotSymp->fallbackp()) { - VSymEnt* const ifaceFoundp - = m_ds.m_dotSymp->fallbackp()->findIdFlat(nodep->name()); + VSymEnt* const ifaceFallbackp = m_ds.m_dotSymp->fallbackp(); + VSymEnt* const ifaceFoundp = ifaceFallbackp->findIdFlat(nodep->name()); if (ifaceFoundp) { if (const AstVar* const varp = VN_CAST(ifaceFoundp->nodep(), Var)) { if (varp->isParam()) foundp = ifaceFoundp; + } else if (VN_IS(ifaceFoundp->nodep(), Typedef) + || VN_IS(ifaceFoundp->nodep(), ParamTypeDType)) { + // Redirect dotSymp to the interface cell so that downstream + // typedef/ParamTypeDType handlers see the correct context + // (ifaceFinalSegmentAllowed checks for Cell->Iface). + m_ds.m_dotSymp = ifaceFallbackp; + foundp = ifaceFoundp; } } } @@ -4119,7 +4168,9 @@ class LinkDotResolveVisitor final : public VNVisitor { m_ds.m_dotText = ""; } } else { - newp = new AstVarRef{nodep->fileline(), ifaceRefVarp, VAccess::READ}; + AstVarRef* const refp + = new AstVarRef{nodep->fileline(), ifaceRefVarp, VAccess::READ}; + newp = refp; } nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); @@ -4159,8 +4210,10 @@ class LinkDotResolveVisitor final : public VNVisitor { } m_ds.m_dotPos = DP_SCOPE; ok = true; - AstNode* const newp = new AstVarRef{nodep->fileline(), varp, VAccess::READ}; - nodep->replaceWith(newp); + + AstVarRef* const refp = new AstVarRef{nodep->fileline(), varp, VAccess::READ}; + nodep->replaceWith(refp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); } else if (allowVar) { AstNode* newp; @@ -4249,8 +4302,10 @@ class LinkDotResolveVisitor final : public VNVisitor { m_ds.m_dotPos = DP_SCOPE; UINFO(9, indent() << "modport -> iface varref " << foundp->nodep()); // We lose the modport name here, so we cannot detect mismatched modports. - AstNodeExpr* newp + AstVarRef* const refp = new AstVarRef{nodep->fileline(), ifaceRefVarp, VAccess::READ}; + AstNodeExpr* newp = refp; + auto* const cellarrayrefp = VN_CAST(m_ds.m_unlinkedScopep, CellArrayRef); if (cellarrayrefp) { // iface[vec].modport became CellArrayRef(iface, lsb) @@ -4329,14 +4384,17 @@ class LinkDotResolveVisitor final : public VNVisitor { } else if (AstTypedef* const defp = VN_CAST(foundp->nodep(), Typedef)) { const bool ifaceFinalSegmentAllowed = (m_ds.m_dotPos == DP_FINAL) && m_ds.m_dotSymp - && VN_IS(m_ds.m_dotSymp->nodep(), Cell) - && VN_CAST(m_ds.m_dotSymp->nodep(), Cell)->modp() - && VN_IS(VN_CAST(m_ds.m_dotSymp->nodep(), Cell)->modp(), Iface); + && (VN_IS(m_ds.m_dotSymp->nodep(), Modport) + || (VN_IS(m_ds.m_dotSymp->nodep(), Cell) + && VN_CAST(m_ds.m_dotSymp->nodep(), Cell)->modp() + && VN_IS(VN_CAST(m_ds.m_dotSymp->nodep(), Cell)->modp(), Iface))); + // Allow interface typedef references even without IfaceCapture + // The dependency graph handles resolution when IfaceCapture is disabled ok = (m_ds.m_dotPos == DP_NONE || m_ds.m_dotPos == DP_SCOPE - || (V3LinkDotIfaceCapture::enabled() && ifaceFinalSegmentAllowed)); - if (V3LinkDotIfaceCapture::enabled() && ifaceFinalSegmentAllowed) { - UINFO(9, indent() << "iface capture allow final-segment typedef name=" - << nodep->name() << " dotText='" << m_ds.m_dotText + || ifaceFinalSegmentAllowed); + if (ifaceFinalSegmentAllowed) { + UINFO(9, indent() << "allow final-segment typedef name=" + << nodep->prettyNameQ() << " dotText='" << m_ds.m_dotText << "' dotSym=" << m_ds.m_dotSymp); } if (ok) { @@ -4349,9 +4407,7 @@ class LinkDotResolveVisitor final : public VNVisitor { V3LinkDotIfaceCapture::captureTypedefContext( refp, "typedef", static_cast(m_ds.m_dotPos), m_ds.m_dotPos == DP_FINAL, m_ds.m_dotText, m_ds.m_dotSymp, m_curSymp, - m_modp, nodep, - [this](AstVar* v, AstRefDType* r) { return promoteVarToParamType(v, r); }, - [this]() { return indent(); }); + m_modp, nodep, [this]() { return indent(); }); if (VN_IS(nodep->backp(), SelExtract)) { m_packedArrayDtp = refp; @@ -4361,7 +4417,8 @@ class LinkDotResolveVisitor final : public VNVisitor { } } } else if (AstParamTypeDType* const defp = VN_CAST(foundp->nodep(), ParamTypeDType)) { - ok = (m_ds.m_dotPos == DP_NONE || m_ds.m_dotPos == DP_SCOPE); + ok = (m_ds.m_dotPos == DP_NONE || m_ds.m_dotPos == DP_SCOPE + || m_ds.m_dotPos == DP_FINAL); if (ok) { AstRefDType* const refp = new AstRefDType{nodep->fileline(), nodep->name()}; refp->refDTypep(defp); @@ -4369,9 +4426,7 @@ class LinkDotResolveVisitor final : public VNVisitor { V3LinkDotIfaceCapture::captureTypedefContext( refp, "paramtype", static_cast(m_ds.m_dotPos), m_ds.m_dotPos == DP_FINAL, m_ds.m_dotText, m_ds.m_dotSymp, m_curSymp, - m_modp, nodep, - [this](AstVar* v, AstRefDType* r) { return promoteVarToParamType(v, r); }, - [this]() { return indent(); }); + m_modp, nodep, [this]() { return indent(); }); if (VN_IS(nodep->backp(), SelExtract)) { m_packedArrayDtp = refp; @@ -5519,15 +5574,19 @@ class LinkDotResolveVisitor final : public VNVisitor { resolvedDTypep->unlinkFrBack(); nodep->replaceWith(resolvedDTypep); VL_DO_DANGLING(pushDeletep(nodep), nodep); - // If the resolved dtype is a RefDType with an interface typedef, - // ensure it's captured for re-resolution during paramed pass - if (AstRefDType* const resolvedRefp = VN_CAST(resolvedDTypep, RefDType)) { - if (resolvedRefp->user2p() && !V3LinkDotIfaceCapture::find(resolvedRefp)) { - AstCell* const cellp = VN_AS(resolvedRefp->user2p(), Cell); - UINFO(9, indent() << "iface capture re-capture resolved RefDType=" - << resolvedRefp << " cell=" << cellp << "\n"); - V3LinkDotIfaceCapture::add(resolvedRefp, cellp, m_modp, - resolvedRefp->typedefp()); + // Originally (PR #6637) this block re-captured a resolved + // RefDType into the iface-capture ledger when user2p held a + // Cell, to ensure re-resolution during the paramed pass. + // In practice, the typeofp-resolved RefDType never carries a + // Cell in user2p: the capture happens earlier when the + // typedef is first linked. + if (V3LinkDotIfaceCapture::enabled()) { + if (AstRefDType* const resolvedRefp = VN_CAST(resolvedDTypep, RefDType)) { + if (VL_UNCOVERABLE(VN_IS(resolvedRefp->user2p(), Cell))) { + resolvedRefp->v3fatalSrc( // LCOV_EXCL_LINE + "typeofp resolved RefDType has Cell in user2p;" + " expected to be captured already"); + } } } } @@ -5551,6 +5610,18 @@ class LinkDotResolveVisitor final : public VNVisitor { UINFO(9, indent() << "iface capture skip revisit name=" << nodep->name() << " already user3 and captured cell=" << nodep->user2p()); } + // Originally this block re-resolved a + // classOrPackageRef during the paramed pass for RefDTypes that + // were already visited (user3). The intent was to ensure + // T::member references through type parameters stayed linked + // after deparameterization. In practice, RefDTypes that reach + // user3-revisit never carry a classOrPackageOpp in the paramed + // pass: the class/package is resolved in the primary pass and + // the skip pointer persists. + if (VL_UNCOVERABLE(m_statep->forParamed() && nodep->classOrPackageOpp())) { + nodep->v3fatalSrc( // LCOV_EXCL_LINE + "RefDType user3 revisit with classOrPackageOpp in paramed pass"); + } return; } LINKDOT_VISIT_START(); @@ -5595,50 +5666,8 @@ class LinkDotResolveVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(cpackagep->unlinkFrBack()), cpackagep); } - const bool capEnable = V3LinkDotIfaceCapture::enabled(); - const auto* const capEntryp = capEnable ? V3LinkDotIfaceCapture::find(nodep) : nullptr; - const bool captureMapHit = capEntryp != nullptr; - AstCell* const captureEntryCellp = capEntryp ? capEntryp->cellp : nullptr; - AstTypedef* const capturedTypedefp = capEntryp ? capEntryp->typedefp : nullptr; - const VSymEnt* const capturedTypedefSymp - = capturedTypedefp ? m_statep->getNodeSym(capturedTypedefp) : nullptr; + const V3LinkDotIfaceCapture::CapturedEntry* capEntryp = V3LinkDotIfaceCapture::find(nodep); - const bool ifaceCaptured = capEnable && nodep->user2p(); - const bool missingIfaceContext = captureMapHit && !ifaceCaptured; - const char* const passLabel = m_statep->forParamed() ? "paramed" : "primary"; - if (missingIfaceContext) { - UINFO(9, indent() << "iface capture captured typedef missing user2 name=" - << nodep->name() << " ref=" << nodep << " pass=" << passLabel - << " entryCell=" << captureEntryCellp); - } - AstCell* const capturedCellp = ifaceCaptured ? VN_CAST(nodep->user2p(), Cell) : nullptr; - - bool forcedIfaceDotScope = false; - bool resolvedCapturedTypedef = false; - bool captureEntryRetired = false; - const auto retireCapture = [&](const char* reason) { - if (!ifaceCaptured || captureEntryRetired) return; - const auto* entry = V3LinkDotIfaceCapture::find(nodep); - AstCell* const entryCell = entry ? entry->cellp : nullptr; - UINFO(9, indent() << "iface capture retire captured typedef reason=" << reason - << " name=" << nodep->name() << " pass=" << passLabel - << " user2=" << nodep->user2p() << " entryCell=" << entryCell); - const bool erased = V3LinkDotIfaceCapture::erase(nodep); - captureEntryRetired = true; - UINFO(9, indent() << "iface capture retire erase result name=" << nodep->name() - << " erased=" << erased); - }; - if (ifaceCaptured && m_statep->forParamed()) { - UINFO(9, indent() << "iface capture captured typedef name=" << nodep->name() - << " typedef=" << nodep->typedefp() << " cell=" << capturedCellp); - if (nodep->typedefp()) { - UINFO(9, indent() << "iface capture refresh typedef binding name=" << nodep->name() - << " typedef=" << nodep->typedefp() - << " cell=" << capturedCellp); - nodep->typedefp(nullptr); - nodep->classOrPackagep(nullptr); - } - } if (m_ds.m_dotp && (m_ds.m_dotPos == DP_PACKAGE || m_ds.m_dotPos == DP_SCOPE)) { UASSERT_OBJ(VN_IS(m_ds.m_dotp->lhsp(), ClassOrPackageRef), m_ds.m_dotp->lhsp(), "Bad package link"); @@ -5647,56 +5676,39 @@ class LinkDotResolveVisitor final : public VNVisitor { "Bad package link"); nodep->classOrPackagep(cpackagerefp->classOrPackageSkipp()); m_ds.m_dotPos = DP_SCOPE; - } else if (!ifaceCaptured) { - checkNoDot(nodep); } else { - UINFO(9, indent() << "iface capture consume captured iface context name=" - << nodep->name() << " cell=" << capturedCellp); - m_ds.m_dotPos = DP_SCOPE; - forcedIfaceDotScope = true; - // Set dotSymp to the cell's symbol entry so lookup happens in the interface scope - if (capturedCellp && m_statep->existsNodeSym(capturedCellp)) { - VSymEnt* const cellSymp = m_statep->getNodeSym(capturedCellp); - m_ds.m_dotSymp = cellSymp; - UINFO(9, indent() << "iface capture set dotSymp to cell scope cellSymp=" - << cellSymp << " node=" << cellSymp->nodep()); + // Allow REFDTYPE under DOT when referencing interface typedefs + // This is needed for patterns like: typedef iface.a_t a_t; + // The dependency graph handles resolution when IfaceCapture is disabled + const bool ifaceFinalSegmentAllowed + = (m_ds.m_dotPos == DP_FINAL) && m_ds.m_dotSymp + && VN_IS(m_ds.m_dotSymp->nodep(), Cell) + && VN_CAST(m_ds.m_dotSymp->nodep(), Cell)->modp() + && VN_IS(VN_CAST(m_ds.m_dotSymp->nodep(), Cell)->modp(), Iface); + if (!ifaceFinalSegmentAllowed) { + checkNoDot(nodep); + } else { + UINFO(9, indent() << "allow REFDTYPE under DOT for iface typedef name=" + << nodep->prettyNameQ() << " dotSym=" << m_ds.m_dotSymp); + // Clear the dot state so we don't propagate errors + m_ds.m_dotPos = DP_SCOPE; } } if (!nodep->typedefp() && !nodep->subDTypep()) { - if (ifaceCaptured) { - UINFO(9, indent() << "iface capture lookup start name=" << nodep->name() - << " dotPos=" << static_cast(m_ds.m_dotPos) << " dotSym=" - << m_ds.m_dotSymp << " classPkg=" << nodep->classOrPackagep()); - } const VSymEnt* foundp; if (nodep->classOrPackagep()) { foundp = m_statep->getNodeSym(nodep->classOrPackagep())->findIdFlat(nodep->name()); } else if (m_ds.m_dotPos == DP_FIRST || m_ds.m_dotPos == DP_NONE) { foundp = m_curSymp->findIdFallback(nodep->name()); } else { - // Use dotSymp if set (e.g., for captured interface typedefs), else curSymp - VSymEnt* const lookupSymp = m_ds.m_dotSymp ? m_ds.m_dotSymp : m_curSymp; - foundp = lookupSymp->findIdFlat(nodep->name()); + // Defensive: dotPos should be DP_FIRST/DP_NONE or classOrPackagep set. + v3fatalSrc("Unexpected dotPos=" + << static_cast(m_ds.m_dotPos) // LCOV_EXCL_LINE + << " in RefDType lookup for " + << nodep->prettyNameQ()); // LCOV_EXCL_LINE + foundp = nullptr; // LCOV_EXCL_LINE } - if (ifaceCaptured && capturedTypedefp) { - // When we have a captured interface typedef context, use the captured typedef - // instead of any local lookup result. This handles the case where the local - // typedef has the same name as the interface typedef (e.g., `typedef if0.rq_t - // rq_t;`) - UINFO(9, indent() << "iface capture binding via captured typedef fallback name=" - << nodep->name() << " typedef=" << capturedTypedefp); - nodep->typedefp(capturedTypedefp); - nodep->classOrPackagep(capturedTypedefSymp ? capturedTypedefSymp->classOrPackagep() - : nullptr); - resolvedCapturedTypedef = true; - } - if (!resolvedCapturedTypedef && foundp) { - VSymEnt* const parentSymp = foundp->parentp(); - UINFO(9, indent() << "iface capture resolved typedef name=" << nodep->name() - << " foundNode=" << foundp->nodep() << " parentNode=" - << (parentSymp ? parentSymp->nodep() : nullptr)); - } - if (!resolvedCapturedTypedef) { + { if (AstTypedef* const defp = foundp ? VN_CAST(foundp->nodep(), Typedef) : nullptr) { // Don't check if typedef is to a :: as might not be @@ -5705,24 +5717,29 @@ class LinkDotResolveVisitor final : public VNVisitor { checkDeclOrder(nodep, defp); nodep->typedefp(defp); nodep->classOrPackagep(foundp->classOrPackagep()); - resolvedCapturedTypedef = true; - // class capture: capture typedef references inside parameterized classes // Only capture if we're referencing from OUTSIDE the class (not // self-references) - if (m_statep->forPrimary()) { + if (V3LinkDotIfaceCapture::enabled() && m_statep->forPrimary()) { AstClass* const classp = VN_CAST(nodep->classOrPackagep(), Class); if (classp && classp->hasGParam() && classp != m_modp) { - UINFO(9, indent() - << "class capture add typedef name=" << nodep->name() - << " class=" << classp->name() << " typedef=" << defp); + UINFO(9, indent() << "class capture add typedef name=" + << nodep->prettyNameQ() << " class=" + << classp->prettyNameQ() << " typedef=" << defp); V3LinkDotIfaceCapture::addClass(nodep, classp, m_modp, defp); } } } else if (AstParamTypeDType* const defp = foundp ? VN_CAST(foundp->nodep(), ParamTypeDType) : nullptr) { - if (defp == nodep->backp()) { // Where backp is typically typedef + // A PARAMTYPEDTYPE's child REFDTYPE referencing itself is the normal + // AST structure for type parameters (e.g., "type T = base" has a child + // REFDTYPE for the default type). Only error on true recursion where + // a TYPEDEF contains a reference back to itself. + const bool isParamTypeChild = (defp == nodep->backp()); + const bool isTypedefRecursion + = isParamTypeChild && VN_IS(defp->backp(), Typedef); + if (isTypedefRecursion) { nodep->v3error("Reference to '" << m_ds.m_dotText << (m_ds.m_dotText == "" ? "" : ".") << nodep->prettyName() << "'" @@ -5731,7 +5748,7 @@ class LinkDotResolveVisitor final : public VNVisitor { } else { nodep->refDTypep(defp); nodep->classOrPackagep(foundp->classOrPackagep()); - resolvedCapturedTypedef = true; + captureIfaceParamType(nodep, defp, capEntryp); } } else if (AstClass* const defp = foundp ? VN_CAST(foundp->nodep(), Class) : nullptr) { @@ -5743,8 +5760,6 @@ class LinkDotResolveVisitor final : public VNVisitor { AstClassRefDType* const newp = new AstClassRefDType{nodep->fileline(), defp, paramsp}; newp->classOrPackagep(foundp->classOrPackagep()); - resolvedCapturedTypedef = true; - retireCapture("resolved"); // Must retire before replacing node nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); return; @@ -5760,12 +5775,6 @@ class LinkDotResolveVisitor final : public VNVisitor { } } } - if (forcedIfaceDotScope && m_ds.m_dotPos == DP_SCOPE && !m_ds.m_dotp) { - UINFO(9, indent() << "iface capture reset dot state after captured typedef name=" - << nodep->name()); - m_ds.init(m_curSymp); - } - if (ifaceCaptured && resolvedCapturedTypedef) { retireCapture("resolved"); } iterateChildren(nodep); } void visit(AstRequireDType* nodep) override { @@ -5927,12 +5936,10 @@ public: : m_statep{statep} { UINFO(4, __FUNCTION__ << ": "); - if (m_statep->forParamed()) { - V3LinkDotIfaceCapture::forEach( - [](const V3LinkDotIfaceCapture::CapturedIfaceTypedef& entry) { - if (AstRefDType* const refp = entry.refp) refp->user3(false); - }); - } + // Note: no need to clear user3 on ledger entries here. + // VNUser3InUse (m_inuser3) already logically clears user3 on all + // nodes via the generation counter. The ledger may also hold stale + // refp pointers to deleted AST nodes, so iterating it is unsafe. iterate(rootp); std::map modulesToRevisit = std::move(m_modulesToRevisit); diff --git a/src/V3LinkDotIfaceCapture.cpp b/src/V3LinkDotIfaceCapture.cpp index 9c6a6bad0..24e98721b 100644 --- a/src/V3LinkDotIfaceCapture.cpp +++ b/src/V3LinkDotIfaceCapture.cpp @@ -1,6 +1,6 @@ // -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* -// DESCRIPTION: Verilator: +// DESCRIPTION: Verilator: Interface typedef capture helper // // Code available from: https://verilator.org // @@ -14,176 +14,580 @@ // //************************************************************************* +// ARCHITECTURE - Separation of Concerns (do not change without reading): +// +// The IfaceCapture system has three phases with strict responsibilities: +// +// 1. CAPTURE (V3LinkDot, primary pass): +// add() / addParamType() / addTypedef() record template entries. +// Template entries store the REFDTYPE, its cellPath, and the +// original paramTypep / typedefp from the template module. +// Template entries have cloneCellPath = "". +// +// 2. CLONE REGISTRATION (V3Param, deepCloneModule): +// propagateClone() creates clone entries in the ledger. +// ** LEDGER-ONLY - no target lookup, no AST mutation. ** +// At this point the cloned module's cells still reference template +// interface modules (cell->modp() is stale). Any attempt to walk +// cellPath here finds the wrong module. Clone entries store the +// cloned REFDTYPE and cloneCellPath but clear paramTypep/typedefp +// so that stale template pointers are never carried forward. +// +// 3. TARGET RESOLUTION (finalizeIfaceCapture, after V3Param): +// Runs after all cloning is complete and cell pointers are wired +// to the correct interface clones. For each entry, walks cellPath +// starting from the entry's owner module (using findOwnerModule(refp) +// for clone entries) to find the correct target module, then locates +// the PARAMTYPEDTYPE / TYPEDEF by name and applies it to the REFDTYPE. +// ** This is the ONLY place that resolves targets and mutates AST. ** +// +// KEY INVARIANT: The path {ownerModName, refName, cellPath, cloneCellPath} +// is the sole identity. No clonep(), no pointer matching. The path IS +// the disambiguation. +// +// Template entries have cloneCellPath = ""; clone entries get it set by +// propagateClone. TemplateKey (ownerModName, refName, cellPath) matches +// all entries regardless of cloneCellPath - used for propagation and debug. +// + #include "V3LinkDotIfaceCapture.h" #include "V3Error.h" #include "V3Global.h" +#include "V3Stats.h" +#include "V3SymTable.h" + +#include +#include VL_DEFINE_DEBUG_FUNCTIONS; V3LinkDotIfaceCapture::CapturedMap V3LinkDotIfaceCapture::s_map{}; - bool V3LinkDotIfaceCapture::s_enabled = true; +// LCOV_EXCL_START +void V3LinkDotIfaceCapture::enable(bool flag) { + s_enabled = flag; + if (!flag) { + s_map.clear(); + clearModuleCache(); + } +} +// LCOV_EXCL_STOP + +void V3LinkDotIfaceCapture::reset() { + s_map.clear(); + clearModuleCache(); +} + +// Per-module cache of statement-level names to avoid O(N*M) linear scans. +// Lazily built on first access for a given module; cleared at phase boundaries. +// Uses vectors per name to handle rare cases where different node types share a name +// (e.g. a Typedef and a ParamTypeDType both named 'sc_tag_status_t'). +namespace { +struct StmtNameMap final { + std::unordered_map> m_byName; + std::unordered_map> m_byPrettyName; +}; +std::unordered_map s_moduleCache; + +const StmtNameMap& getOrBuild(AstNodeModule* modp) { + auto it = s_moduleCache.find(modp); + if (it != s_moduleCache.end()) return it->second; + StmtNameMap& cache = s_moduleCache[modp]; + for (AstNode* stmtp = modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) { + const string& nm = stmtp->name(); + if (!nm.empty()) cache.m_byName[nm].push_back(stmtp); + if (AstNodeDType* const dtp = VN_CAST(stmtp, NodeDType)) { + const string pn = dtp->prettyName(); + if (!pn.empty()) cache.m_byPrettyName[pn].push_back(dtp); + } + } + return cache; +} +} // namespace + +void V3LinkDotIfaceCapture::clearModuleCache() { s_moduleCache.clear(); } + +AstIfaceRefDType* V3LinkDotIfaceCapture::ifaceRefFromVarDType(AstNodeDType* dtypep) { + AstIfaceRefDType* resultp = nullptr; + for (AstNodeDType* curp = dtypep; curp;) { + if (AstIfaceRefDType* const irefp = VN_CAST(curp, IfaceRefDType)) { + resultp = irefp; + break; + } else if (AstBracketArrayDType* const bracketp = VN_CAST(curp, BracketArrayDType)) { + curp = bracketp->subDTypep(); + } else if (AstUnpackArrayDType* const unpackp = VN_CAST(curp, UnpackArrayDType)) { + curp = unpackp->subDTypep(); + } else { + v3fatalSrc("ifaceRefFromVarDType: unexpected dtype " << curp->prettyTypeName() + << " in chain"); + } + } + return resultp; +} + +namespace { +// Resolve the owner module name for a typedef/paramType node. +// Returns hint if non-empty, otherwise walks backp() to find the owner module name. +string resolveOwnerName(const string& hint, AstNode* nodep) { + if (!hint.empty()) return hint; + if (!nodep) return ""; + AstNodeModule* const ownerp = V3LinkDotIfaceCapture::findOwnerModule(nodep); + return ownerp ? ownerp->name() : string{}; +} +} // namespace + +AstTypedef* V3LinkDotIfaceCapture::findTypedefInModule(AstNodeModule* modp, const string& name) { + const StmtNameMap& cache = getOrBuild(modp); + const auto it = cache.m_byName.find(name); + if (it == cache.m_byName.end()) return nullptr; + for (AstNode* nodep : it->second) { + if (AstTypedef* const tdp = VN_CAST(nodep, Typedef)) return tdp; + } + // Cache has entry for this name but no Typedef - unexpected. + v3fatalSrc("findTypedefInModule: name '" << name << "' found in " << modp->prettyNameQ() + << " but no Typedef node"); + return nullptr; // LCOV_EXCL_LINE +} +AstNodeDType* V3LinkDotIfaceCapture::findDTypeInModule(AstNodeModule* modp, const string& name, + VNType type) { + const StmtNameMap& cache = getOrBuild(modp); + const auto it = cache.m_byName.find(name); + if (it == cache.m_byName.end()) return nullptr; + for (AstNode* nodep : it->second) { + if (AstNodeDType* const dtp = VN_CAST(nodep, NodeDType)) { + if (dtp->type() == type) return dtp; + } + } + // Cache has entry for this name but no matching DType - unexpected. + v3fatalSrc("findDTypeInModule: name '" << name << "' found in " << modp->prettyNameQ() + << " but no matching DType"); + return nullptr; // LCOV_EXCL_LINE +} +AstParamTypeDType* V3LinkDotIfaceCapture::findParamTypeInModule(AstNodeModule* modp, + const string& name) { + const StmtNameMap& cache = getOrBuild(modp); + const auto it = cache.m_byName.find(name); + if (it == cache.m_byName.end()) return nullptr; + for (AstNode* nodep : it->second) { + if (AstParamTypeDType* const ptdp = VN_CAST(nodep, ParamTypeDType)) return ptdp; + } + return nullptr; +} + +AstNodeDType* V3LinkDotIfaceCapture::findDTypeByPrettyName(AstNodeModule* modp, + const string& prettyName) { + const StmtNameMap& cache = getOrBuild(modp); + const auto it = cache.m_byPrettyName.find(prettyName); + if (it == cache.m_byPrettyName.end()) return nullptr; + return it->second.front(); +} + +AstNodeModule* V3LinkDotIfaceCapture::findCloneViaHierarchy(AstNodeModule* containingModp, + AstNodeModule* deadTargetModp, + int depth) { + if (depth > 20) return nullptr; // Safety limit + for (AstNode* stmtp = containingModp->stmtsp(); stmtp; stmtp = stmtp->nextp()) { + if (AstCell* const cellp = VN_CAST(stmtp, Cell)) { + AstNodeModule* const cellModp = cellp->modp(); + if (!cellModp || cellModp->dead()) continue; + // Check if cellModp is a clone of deadTargetModp by comparing + // the template name (part before "__") + const string& cellModName = cellModp->name(); + const string& deadName = deadTargetModp->name(); + const size_t pos = cellModName.find("__"); + if (pos != string::npos && cellModName.substr(0, pos) == deadName) { return cellModp; } + // Recurse into sub-cells + AstNodeModule* const found + = findCloneViaHierarchy(cellModp, deadTargetModp, depth + 1); + if (found) return found; + } + } + return nullptr; +} + +int V3LinkDotIfaceCapture::fixDeadRefs(AstRefDType* refp, AstNodeModule* containingModp, + const char* location) { + int fixed = 0; + + // Fix typedefp pointing to dead module + if (refp->typedefp()) { + AstNodeModule* const typedefModp = findOwnerModule(refp->typedefp()); + if (typedefModp && typedefModp->dead()) { + AstNodeModule* cloneModp = nullptr; + if (containingModp) { cloneModp = findCloneViaHierarchy(containingModp, typedefModp); } + if (cloneModp) { + const string& tdName = refp->typedefp()->name(); + if (AstTypedef* const newTdp = findTypedefInModule(cloneModp, tdName)) { + UINFO(9, "iface capture finalizeCapture (" + << location << "): fixing typedefp refp=" << refp << " dead=" + << typedefModp->name() << " -> " << cloneModp->name()); + refp->typedefp(newTdp); + ++fixed; + } + } + } + } + + // Fix refDTypep pointing to dead module + if (refp->refDTypep()) { + AstNodeModule* const targetModp = findOwnerModule(refp->refDTypep()); + if (targetModp && targetModp->dead()) { + AstNodeModule* cloneModp = nullptr; + if (containingModp) { cloneModp = findCloneViaHierarchy(containingModp, targetModp); } + bool foundByName = false; + if (cloneModp) { + const string& targetName = refp->refDTypep()->prettyName(); + if (AstNodeDType* const newDtp = findDTypeByPrettyName(cloneModp, targetName)) { + UINFO(9, "iface capture finalizeCapture (" + << location << "): fixing refDTypep refp=" << refp + << " dead=" << targetModp->name() << " -> " << cloneModp->name()); + refp->refDTypep(newDtp); + ++fixed; + foundByName = true; + } + } + // If name-based search failed, try to derive refDTypep from + // the already-fixed typedefp chain. The typedefp was fixed + // above to point to the clone's typedef, so its subDTypep() + // returns a live dtype (type-table entry or clone-owned). + // This avoids setting refDTypep to nullptr which would force + // V3Width to re-walk the dtype tree under TYPETABLE where + // module provenance is lost, triggering spurious warnings. + if (!foundByName) { + AstNodeDType* derivedp = nullptr; + if (refp->typedefp() && refp->typedefp()->subDTypep()) { + derivedp = refp->typedefp()->subDTypep(); + AstNodeModule* const derivedOwnerp = findOwnerModule(derivedp); + if (derivedOwnerp && derivedOwnerp->dead()) { derivedp = nullptr; } + } + UINFO(9, "iface capture finalizeCapture (" + << location << "): deriving refDTypep from typedefp refp=" << refp + << " dead=" << targetModp->name() << " derived=" << derivedp); + refp->refDTypep(derivedp); + ++fixed; + } + } + } + + // Fix base-class dtypep() - V3Broken checks this pointer, and V3Width + // may have set it to a node in the dead template module. Derive from + // the (already fixed) typedefp chain when possible. + if (refp->dtypep()) { + AstNodeModule* const dtOwnerp = findOwnerModule(refp->dtypep()); + if (dtOwnerp && dtOwnerp->dead()) { + AstNodeDType* newDtp = nullptr; + // Derive from the fixed typedef's subDTypep. This always succeeds + // because the typedefp was fixed above to point to a clone's typedef + // whose subDTypep is a live type-table entry or clone-owned dtype. + // If this fires, either typedefp was not fixed or subDTypep is stale. + // Dump refp->typedefp() and dtOwnerp to diagnose. + if (refp->typedefp() && refp->typedefp()->subDTypep()) { + newDtp = refp->typedefp()->subDTypep(); + AstNodeModule* const newDtOwnerp = findOwnerModule(newDtp); + if (newDtOwnerp && newDtOwnerp->dead()) newDtp = nullptr; + } + UASSERT_OBJ(newDtp, refp, + "fixDeadRefs dtypep: could not derive live dtypep for " + << refp->prettyNameQ() << " dead owner=" << dtOwnerp->name() + << " typedefp=" + << (refp->typedefp() ? refp->typedefp()->name() : "")); + UINFO(9, "iface capture finalizeCapture (" + << location << "): fixing dtypep refp=" << refp + << " dead=" << dtOwnerp->name() << " -> " << newDtp); + refp->dtypep(newDtp); + ++fixed; + } + } + + return fixed; +} + +AstNodeModule* V3LinkDotIfaceCapture::findLiveCloneOf(AstNodeModule* deadTargetModp, + AstNodeModule** containerp) { + for (AstNode* np = v3Global.rootp()->modulesp(); np; np = np->nextp()) { + if (AstNodeModule* const modp = VN_CAST(np, NodeModule)) { + if (modp->dead()) continue; + AstNodeModule* const found = findCloneViaHierarchy(modp, deadTargetModp); + if (found) { + if (containerp) *containerp = modp; + return found; + } + } + } + if (containerp) *containerp = nullptr; + return nullptr; +} + AstNodeModule* V3LinkDotIfaceCapture::findOwnerModule(AstNode* nodep) { for (AstNode* curp = nodep; curp; curp = curp->backp()) { + // Guard against corrupted backp() chains (e.g. freed memory, + // low addresses like 0x1) from nodes unlinked by linkDotParamed. + if (reinterpret_cast(curp) < 0x1000) return nullptr; if (AstNodeModule* const modp = VN_CAST(curp, NodeModule)) return modp; } return nullptr; } -bool V3LinkDotIfaceCapture::finalizeCapturedEntry(CapturedMap::iterator it, const char* reasonp) { - CapturedIfaceTypedef& entry = it->second; - AstRefDType* const pendingRefp = entry.pendingClonep; - AstTypedef* const reboundTypedefp = entry.typedefp; - if (!pendingRefp || !reboundTypedefp) return false; - if (entry.cellp) pendingRefp->user2p(entry.cellp); - pendingRefp->user3(false); - pendingRefp->typedefp(reboundTypedefp); - entry.pendingClonep = nullptr; - return true; +void V3LinkDotIfaceCapture::purgeStaleRefs() { + if (!s_enabled || s_map.empty() || !v3Global.rootp()) return; + // Collect every live AstNode* in the AST so we can detect stale pointers + // in the ledger (refp, ownerModp, typedefp, paramTypep, etc.). + std::unordered_set liveNodes; + v3Global.rootp()->foreach([&](AstNode* np) { liveNodes.insert(np); }); + for (auto& kv : s_map) { + kv.second.foreachLink([&](AstNode*& nodep) { + if (nodep && !liveNodes.count(nodep)) nodep = nullptr; + }); + } +} + +void V3LinkDotIfaceCapture::dumpEntries(const string& label) { + UINFO(9, "========== iface capture dumpEntries: " << label << " (entries=" << s_map.size() + << ") =========="); + int idx = 0; + for (const auto& pair : s_map) { + const CaptureKey& key = pair.first; + const CapturedEntry& entry = pair.second; + const char* captType = (entry.captureType == CaptureType::IFACE) ? "IFACE" : "CLASS"; + UINFO(9, + " [" << idx << "] " << captType << " key={" << key.ownerModName << "," + << key.refName << "," << key.cellPath << "," << key.cloneCellPath << "}" + << " ref=" << (entry.refp ? entry.refp->name() : "") + << " refp=" << cvtToHex(entry.refp) << " cellPath='" << entry.cellPath << "'" + << " ownerMod=" << (entry.ownerModp ? entry.ownerModp->name() : "") + << " typedefp=" << (entry.typedefp ? entry.typedefp->name() : "") + << " typedefOwnerModName='" << entry.typedefOwnerModName << "'" + << " paramTypep=" << (entry.paramTypep ? entry.paramTypep->name() : "") + << " ifacePortVarp=" + << (entry.ifacePortVarp ? entry.ifacePortVarp->name() : "")); + ++idx; + } + UINFO(9, "========== end iface capture dumpEntries =========="); } string V3LinkDotIfaceCapture::extractIfacePortName(const string& dotText) { string name = dotText; const size_t dotPos = name.find('.'); - if (dotPos != string::npos) name.resize(dotPos); + if (dotPos != string::npos) name = name.substr(0, dotPos); const size_t braPos = name.find("__BRA__"); - if (braPos != string::npos) name.resize(braPos); + if (braPos != string::npos) name = name.substr(0, braPos); return name; } -void V3LinkDotIfaceCapture::add(AstRefDType* refp, AstCell* cellp, AstNodeModule* ownerModp, - AstTypedef* typedefp, AstNodeModule* typedefOwnerModp, - AstVar* ifacePortVarp) { - // TODO -- probably classes too - if (!refp || cellp->modp() == ownerModp) return; +void V3LinkDotIfaceCapture::add(AstRefDType* refp, const string& cellPath, + AstNodeModule* ownerModp, AstTypedef* typedefp, + const string& typedefOwnerModName, AstVar* ifacePortVarp) { + UASSERT(refp, "add() called with null refp"); + UASSERT(ownerModp, "add() called with null ownerModp for refp=" << refp->prettyNameQ()); if (!typedefp) typedefp = refp->typedefp(); - if (!typedefOwnerModp && typedefp) typedefOwnerModp = findOwnerModule(typedefp); - s_map[refp] = CapturedIfaceTypedef{ - CaptureType::IFACE, refp, cellp, nullptr, ownerModp, typedefp, - typedefOwnerModp, nullptr, ifacePortVarp}; + const string tdOwnerName = resolveOwnerName(typedefOwnerModName, typedefp); + const string ownerModName = ownerModp->name(); + const CaptureKey key{ownerModName, refp->name(), cellPath, ""}; + auto it = s_map.find(key); + if (it != s_map.end()) { + // Key already exists - append this refp as an extra + it->second.extraRefps.push_back(refp); + UINFO(9, "iface capture add (extra): refp=" + << refp->name() << " cellPath='" << cellPath << "'" << " ownerMod=" + << ownerModName << " extraRefps.size=" << it->second.extraRefps.size()); + } else { + s_map[key] = CapturedEntry{ + CaptureType::IFACE, refp, cellPath, + /*cloneCellPath=*/"", + /*origClassp=*/nullptr, ownerModp, typedefp, nullptr, tdOwnerName, ifacePortVarp, {}}; + UINFO(9, "iface capture add: refp=" << refp->name() << " cellPath='" << cellPath << "'" + << " ownerMod=" << ownerModName << " typedefp=" + << (typedefp ? typedefp->name() : "") + << " typedefOwnerModName='" << tdOwnerName << "'"); + } } void V3LinkDotIfaceCapture::addClass(AstRefDType* refp, AstClass* origClassp, AstNodeModule* ownerModp, AstTypedef* typedefp, - AstNodeModule* typedefOwnerModp) { - if (!refp) return; + const string& typedefOwnerModName) { + UASSERT(refp, "addClass() called with null refp"); + UASSERT(ownerModp, "addClass() called with null ownerModp"); if (!typedefp) typedefp = refp->typedefp(); - if (!typedefOwnerModp && typedefp) typedefOwnerModp = findOwnerModule(typedefp); - s_map[refp] = CapturedIfaceTypedef{CaptureType::CLASS, refp, nullptr, - origClassp, ownerModp, typedefp, - typedefOwnerModp, nullptr, nullptr}; + const string tdOwnerName = resolveOwnerName(typedefOwnerModName, typedefp); + // For CLASS captures, use the class name as cellPath + UASSERT_OBJ(origClassp, refp, "addClass() called with null origClassp for refp=" << refp); + const string cellPath = origClassp->name(); + UASSERT_OBJ(!cellPath.empty(), origClassp, "addClass() produced empty cellPath"); + const string ownerModName = ownerModp->name(); + const CaptureKey key{ownerModName, refp->name(), cellPath, ""}; + s_map[key] = CapturedEntry{CaptureType::CLASS, refp, cellPath, + /*cloneCellPath=*/"", origClassp, ownerModp, typedefp, nullptr, + tdOwnerName, nullptr, {}}; + UINFO(9, "iface capture addClass: refp=" << refp->name() << " cellPath='" << cellPath << "'" + << " ownerMod=" + << (ownerModp ? ownerModp->name() : "")); } -const V3LinkDotIfaceCapture::CapturedIfaceTypedef* -V3LinkDotIfaceCapture::find(const AstRefDType* refp) { - if (!refp) return nullptr; - const auto it = s_map.find(refp); +// Not called in production - retained as a diagnostic/debug entry point +// for inspecting the capture ledger by key (e.g. from GDB or future code). +const V3LinkDotIfaceCapture::CapturedEntry* // LCOV_EXCL_START +V3LinkDotIfaceCapture::find(const CaptureKey& key) { + const auto it = s_map.find(key); if (VL_UNLIKELY(it == s_map.end())) return nullptr; return &it->second; +} // LCOV_EXCL_STOP + +const V3LinkDotIfaceCapture::CapturedEntry* V3LinkDotIfaceCapture::find(const AstRefDType* refp) { + if (!refp) return nullptr; + for (const auto& kv : s_map) { + if (kv.second.refp == refp) return &kv.second; + } + return nullptr; } -bool V3LinkDotIfaceCapture::erase(const AstRefDType* refp) { - if (!refp) return false; - const auto it = s_map.find(refp); - if (it == s_map.end()) return false; - s_map.erase(it); - return true; -} - -bool V3LinkDotIfaceCapture::replaceRef(const AstRefDType* oldRefp, AstRefDType* newRefp) { - if (!oldRefp || !newRefp) return false; - const auto it = s_map.find(oldRefp); - if (it == s_map.end()) return false; - auto entry = it->second; - entry.refp = newRefp; - s_map.erase(it); - s_map.emplace(newRefp, entry); - return true; -} - -bool V3LinkDotIfaceCapture::replaceTypedef(const AstRefDType* refp, AstTypedef* newTypedefp) { - if (!refp || !newTypedefp) return false; - auto it = s_map.find(refp); - if (it == s_map.end()) return false; - it->second.typedefp = newTypedefp; - it->second.typedefOwnerModp = findOwnerModule(newTypedefp); - - // For CLASS captures, update the RefDType node directly - if (it->second.captureType == CaptureType::CLASS && it->second.refp) { - it->second.refp->typedefp(newTypedefp); - // Also update classOrPackagep to point to the specialized class - if (AstClass* const newClassp = VN_CAST(it->second.typedefOwnerModp, Class)) { - it->second.refp->classOrPackagep(newClassp); +// Walk a dot-separated cell path through the cell / IFACEREFDTYPE hierarchy +// starting from startModp. Returns the module at the end of the path, or +// nullptr if any component cannot be resolved. +// Cell/port names are preserved across clones by cloneTree, so this works +// identically on template and cloned modules. +AstNodeModule* V3LinkDotIfaceCapture::followCellPath(AstNodeModule* startModp, + const string& cellPath) { + if (cellPath.empty() || !startModp) return nullptr; + AstNodeModule* curModp = startModp; + string remaining = cellPath; + while (!remaining.empty() && curModp) { + string component; + const size_t dotPos = remaining.find('.'); + if (dotPos == string::npos) { + component = remaining; + remaining.clear(); + } else { + component = remaining.substr(0, dotPos); + remaining = remaining.substr(dotPos + 1); } - UINFO(9, "class capture updated RefDType typedefp: " << it->second.refp << " -> " - << newTypedefp); + const size_t braPos = component.find("__BRA__"); + const string componentBase + = (braPos == string::npos) ? component : component.substr(0, braPos); + AstNodeModule* nextModp = nullptr; + for (AstNode* sp = curModp->stmtsp(); sp; sp = sp->nextp()) { + if (AstCell* const cellp = VN_CAST(sp, Cell)) { + if ((cellp->name() == component || cellp->name() == componentBase) + && cellp->modp()) { + nextModp = cellp->modp(); + break; + } + } + if (AstVar* const varp = VN_CAST(sp, Var)) { + if (varp->isIfaceRef() && varp->subDTypep()) { + string varBaseName = varp->name(); + const size_t viftopPos = varBaseName.find("__Viftop"); + if (viftopPos != string::npos) { + varBaseName = varBaseName.substr(0, viftopPos); + } + if (varBaseName == component || varBaseName == componentBase) { + if (AstIfaceRefDType* const irefp + = ifaceRefFromVarDType(varp->subDTypep())) { + AstIface* const ifacep = irefp->ifaceViaCellp(); + if (ifacep) { + nextModp = ifacep; + break; + } + } + } + } + } + } + curModp = nextModp; } - - finalizeCapturedEntry(it, "typedef clone"); - return true; + return curModp; } -void V3LinkDotIfaceCapture::propagateClone(const AstRefDType* origRefp, AstRefDType* newRefp) { - if (!origRefp || !newRefp) return; - const auto it = s_map.find(origRefp); - UASSERT_OBJ(it != s_map.end(), origRefp, - "iface capture propagateClone missing entry for orig=" << cvtToStr(origRefp)); - CapturedIfaceTypedef& entry = it->second; +// Phase 2: CLONE REGISTRATION - ledger only. +// Called from V3Param::deepCloneModule. At this point the cloned module's +// cells still reference template interface modules (cell->modp() is stale), +// so we MUST NOT walk cellPath or resolve targets here. We only record the +// clone entry. Target resolution happens in finalizeIfaceCapture (Phase 3) +// after all cell pointers are wired to the correct interface clones. +// See header ARCHITECTURE comment for the full picture. +void V3LinkDotIfaceCapture::propagateClone(const TemplateKey& tkey, AstRefDType* newRefp, + const string& cloneCellPath) { + UASSERT(newRefp, "propagateClone() called with null newRefp"); + // Find the template entry by exact key. The entry was captured during + // the primary LinkDot pass, so it must exist. If this fires, either the + // capture was missed or the key components (ownerModName, refName, + // cellPath) diverged between capture and clone time. + const CaptureKey templateKey{tkey.ownerModName, tkey.refName, tkey.cellPath, ""}; + auto it = s_map.find(templateKey); + UASSERT(it != s_map.end(), "propagateClone: no template entry for tkey={" + << tkey.ownerModName << "," << tkey.refName << "," + << tkey.cellPath << "} cloneCellPath='" << cloneCellPath + << "'"); - if (entry.cellp) newRefp->user2p(entry.cellp); - newRefp->user3(false); - entry.pendingClonep = newRefp; + // Create a new clone entry - ledger only. + // Target resolution (paramTypep/typedefp) happens in finalizeIfaceCapture + // where cell pointers are already wired to the correct interface clones. + CapturedEntry newEntry = it->second; + newEntry.refp = newRefp; + newEntry.cellPath = tkey.cellPath; + newEntry.cloneCellPath = cloneCellPath; + newEntry.clearStaleRefs(); + const CaptureKey newKey{tkey.ownerModName, tkey.refName, tkey.cellPath, cloneCellPath}; + s_map[newKey] = newEntry; - // If replaceTypedef was already called (interface cloned before module), - // entry.typedefp will differ from the original RefDType's typedef. - // In that case, finalize now with the updated typedef. - if (entry.typedefp && origRefp->typedefp() && entry.typedefp != origRefp->typedefp()) { - finalizeCapturedEntry(it, "ref clone"); - } + UINFO(9, "propagateClone: tkey={" << tkey.ownerModName << "," << tkey.refName << "," + << tkey.cellPath << "} refp=" << newRefp->name() + << " cloneCellPath='" << cloneCellPath << "'"); } -template -void V3LinkDotIfaceCapture::forEachImpl(FilterFn&& filter, Fn&& fn) { - std::vector keys; +template +void V3LinkDotIfaceCapture::forEachImpl(T_FilterFn&& filter, T_Fn&& fn) { + if (s_map.empty()) return; + std::vector keys; keys.reserve(s_map.size()); for (const auto& kv : s_map) keys.push_back(kv.first); - for (const AstRefDType* key : keys) { + for (const CaptureKey& key : keys) { const auto it = s_map.find(key); if (it == s_map.end()) continue; - - CapturedIfaceTypedef& entry = it->second; - if (entry.cellp && entry.refp && entry.refp->user2p() != entry.cellp) { - entry.refp->user2p(entry.cellp); - } + CapturedEntry& entry = it->second; if (!filter(entry)) continue; fn(entry); } } -void V3LinkDotIfaceCapture::forEach(const std::function& fn) { - if (!fn) return; - forEachImpl([](const CapturedIfaceTypedef&) { return true; }, fn); +void V3LinkDotIfaceCapture::forEach(const std::function& fn) { + if (!fn || s_map.empty()) return; + forEachImpl([](const CapturedEntry&) { return true; }, fn); } -void V3LinkDotIfaceCapture::forEachOwned( - const AstNodeModule* ownerModp, const std::function& fn) { - if (!ownerModp || !fn) return; +void V3LinkDotIfaceCapture::forEachOwned(const AstNodeModule* ownerModp, + const std::function& fn) { + if (!ownerModp || !fn || s_map.empty()) return; + const string ownerName = ownerModp->name(); + UINFO(9, + "iface capture forEachOwned: ownerModp=" << ownerName << " map size=" << s_map.size()); forEachImpl( - [ownerModp](const CapturedIfaceTypedef& e) { - return e.ownerModp == ownerModp || e.typedefOwnerModp == ownerModp; + [ownerModp, &ownerName](const CapturedEntry& e) { + // Only match template entries (cloneCellPath=''). + // Clone entries are created by propagateClone and must not be + // re-processed - each clone gets its own target independently. + if (!e.cloneCellPath.empty()) return false; + // Match by ownerModp pointer or typedefOwnerModName string + const bool matches = e.ownerModp == ownerModp || e.typedefOwnerModName == ownerName; + UINFO(9, "iface capture forEachOwned filter: ref=" + << (e.refp ? e.refp->name() : "") << " cellPath='" << e.cellPath + << "' ownerMod=" << (e.ownerModp ? e.ownerModp->name() : "") + << " typedefOwnerModName='" << e.typedefOwnerModName + << "' matches=" << matches); + return matches; }, fn); } // replaces the lambda used in V3LinkDot.cpp for iface capture -void V3LinkDotIfaceCapture::captureTypedefContext( - AstRefDType* refp, const char* stageLabel, int dotPos, bool dotIsFinal, - const std::string& dotText, VSymEnt* dotSymp, VSymEnt* curSymp, AstNodeModule* modp, - AstNode* nodep, const std::function& promoteVarCb, - const std::function& indentFn) { +void V3LinkDotIfaceCapture::captureTypedefContext(AstRefDType* refp, const char* stageLabel, + int dotPos, bool /*dotIsFinal*/, + const std::string& dotText, VSymEnt* dotSymp, + VSymEnt* curSymp, AstNodeModule* modp, + AstNode* /*nodep*/, + const std::function& indentFn) { if (!enabled() || !refp) return; UINFO(9, indentFn() << "iface capture capture request stage=" << stageLabel @@ -200,41 +604,439 @@ void V3LinkDotIfaceCapture::captureTypedefContext( << " (no iface context)"); return; } + // Skip internal interface typedef references (typedef used within its own interface) + if (ifaceCellp->modp() == modp) { + UINFO(9, indentFn() << "iface capture capture skipped typedef=" << refp + << " (internal to interface " << modp->name() << ")"); + return; + } + + // dotText is always non-empty for interface typedef captures. If this + // fires, the caller resolved to an interface Cell but did not accumulate + // a dotText path - investigate the dot-state in visitParseRef. + UASSERT_OBJ(!dotText.empty(), refp, "captureTypedefContext: dotText empty"); + const string cellPath = dotText; AstVar* ifacePortVarp = nullptr; - if (!dotText.empty() && curSymp) { + if (curSymp) { const std::string portName = extractIfacePortName(dotText); - if (const VSymEnt* const portSymp = curSymp->findIdFallback(portName)) { + if (VSymEnt* const portSymp = curSymp->findIdFallback(portName)) { ifacePortVarp = VN_CAST(portSymp->nodep(), Var); UINFO(9, indentFn() << "iface capture found port var '" << portName << "' -> " << ifacePortVarp); } } - refp->user2p(const_cast(ifaceCellp)); - V3LinkDotIfaceCapture::add(refp, const_cast(ifaceCellp), modp, refp->typedefp(), - nullptr, ifacePortVarp); + // Check if refDTypep is a ParamTypeDType - if so, use addParamType instead of add + if (AstParamTypeDType* const paramTypep = VN_CAST(refp->refDTypep(), ParamTypeDType)) { + V3LinkDotIfaceCapture::addParamType(refp, cellPath, modp, paramTypep, "", ifacePortVarp); + } else { + V3LinkDotIfaceCapture::add(refp, cellPath, modp, refp->typedefp(), "", ifacePortVarp); + } UINFO(9, indentFn() << "iface capture capture success typedef=" << refp - << " cell=" << ifaceCellp + << " cell=" << ifaceCellp << " cellPath='" << cellPath << "'" << " mod=" << (ifaceCellp->modp() ? ifaceCellp->modp()->name() : "") << " dotPos=" << dotPos); - if (!dotIsFinal) return; - - AstVar* enclosingVarp = nullptr; - for (AstNode* curp = nodep; curp; curp = curp->backp()) { - if (AstVar* const varp = VN_CAST(curp, Var)) { - enclosingVarp = varp; - break; - } - if (VN_IS(curp, ParamTypeDType)) break; - if (VN_IS(curp, NodeModule)) break; - } - if (!enclosingVarp || enclosingVarp->user3SetOnce()) return; - UINFO(9, indentFn() << "iface capture typedef owner var=" << enclosingVarp - << " name=" << enclosingVarp->prettyName()); - - if (promoteVarCb && promoteVarCb(enclosingVarp, refp)) return; - UINFO(9, indentFn() << "iface capture failed to convert owner var name=" - << enclosingVarp->prettyName()); + // Note: the enclosingVar walk + promoteVarToParamType callback was removed. + // It supported 'localparam xyz_t = iface.rq_t;' without the 'type' keyword, + // which was never valid SystemVerilog. CI-CD with v3fatalSrc asserts on + // the promoteVarCb path and replaceRef confirmed this was dead code. +} + +void V3LinkDotIfaceCapture::captureInnerParamTypeRefs(AstParamTypeDType* paramTypep, + AstRefDType* refp, const string& cellPath, + const string& ownerModName, + const string& ptOwnerName) { + if (!paramTypep) return; + paramTypep->foreach([&](AstRefDType* innerRefp) { + if (innerRefp == refp) return; + if (!innerRefp->refDTypep()) return; + + AstNodeModule* const refOwnerModp = findOwnerModule(innerRefp->refDTypep()); + if (refOwnerModp && VN_IS(refOwnerModp, Iface) && refOwnerModp->name() != ptOwnerName) { + AstNodeModule* const innerOwnerModp = findOwnerModule(innerRefp); + const string innerOwnerName = innerOwnerModp ? innerOwnerModp->name() : ownerModName; + const CaptureKey innerKey{innerOwnerName, innerRefp->name(), cellPath, ""}; + if (s_map.find(innerKey) == s_map.end()) { + // Find the cell name for the nested interface + string nestedCellName; + AstNodeModule* const ptOwnerModp = findOwnerModule(paramTypep); + if (ptOwnerModp) { + for (AstNode* stmtp = ptOwnerModp->stmtsp(); stmtp; stmtp = stmtp->nextp()) { + if (AstCell* const cp = VN_CAST(stmtp, Cell)) { + if (cp->modp() == refOwnerModp) { + nestedCellName = cp->name(); + break; + } + } + } + } + if (VL_UNCOVERABLE(nestedCellName.empty())) { + // The nested interface cell should always be found in the + // owner module's statements. If this fires, either + // ptOwnerModp is wrong (findOwnerModule returned the wrong + // module) or the cell was pruned before capture. + v3fatalSrc("captureInnerParamTypeRefs: could not find cell for nested iface '" + << refOwnerModp->prettyNameQ() << "' in '" + << (ptOwnerModp ? ptOwnerModp->prettyNameQ() : "") << "'"); + } + UINFO(9, "addParamType: also capturing inner RefDType " + << innerRefp << " refDTypep owner=" << refOwnerModp->name() + << " nestedCellName='" << nestedCellName << "'"); + s_map[innerKey] = CapturedEntry{CaptureType::IFACE, + innerRefp, + nestedCellName.empty() ? cellPath : nestedCellName, + /*cloneCellPath=*/"", + /*origClassp=*/nullptr, + ptOwnerModp, + innerRefp->typedefp(), + nullptr, + refOwnerModp->name(), + nullptr, + {}}; + } + } + }); +} + +void V3LinkDotIfaceCapture::addParamType(AstRefDType* refp, const string& cellPath, + AstNodeModule* ownerModp, AstParamTypeDType* paramTypep, + const string& paramTypeOwnerModName, + AstVar* ifacePortVarp) { + UASSERT(refp, "addParamType() called with null refp"); + UASSERT(ownerModp, + "addParamType() called with null ownerModp for refp='" << refp->prettyNameQ() << "'"); + UASSERT_OBJ(paramTypep, refp, + "addParamType() called with null paramTypep for refp='" << refp->prettyNameQ() + << "'"); + const string ptOwnerName = resolveOwnerName(paramTypeOwnerModName, paramTypep); + UINFO(9, "addParamType: refp=" << refp << " cellPath='" << cellPath << "'" + << " ownerModp=" << (ownerModp ? ownerModp->name() : "") + << " paramTypep=" << paramTypep << " paramTypeOwnerModName='" + << ptOwnerName << "'"); + if (paramTypep) { + UINFO(9, "addParamType: paramTypep subDTypep chain:"); + paramTypep->foreach([&](AstRefDType* innerRefp) { + UINFO(9, + " inner RefDType: " + << innerRefp << " refDTypep=" << innerRefp->refDTypep() + << (innerRefp->refDTypep() ? " refDTypep->name=" : "") + << (innerRefp->refDTypep() ? innerRefp->refDTypep()->prettyTypeName() : "")); + }); + } + const string ownerModName = ownerModp->name(); + const CaptureKey key{ownerModName, refp->name(), cellPath, ""}; + auto it = s_map.find(key); + if (it != s_map.end()) { + // Key already exists - append this refp as an extra + it->second.extraRefps.push_back(refp); + UINFO(9, "addParamType (extra): refp=" + << refp->name() << " cellPath='" << cellPath << "'" << " ownerMod=" + << ownerModName << " extraRefps.size=" << it->second.extraRefps.size()); + } else { + s_map[key] + = CapturedEntry{CaptureType::IFACE, refp, cellPath, + /*cloneCellPath=*/"", + /*origClassp=*/nullptr, ownerModp, nullptr, paramTypep, ptOwnerName, + ifacePortVarp, {}}; + } + + // Also capture REFDTYPEs inside the PARAMTYPEDTYPE's subDTypep chain. + captureInnerParamTypeRefs(paramTypep, refp, cellPath, ownerModName, ptOwnerName); +} + +// Visitor that fixes dead references in the global type table. +// +// When interface templates are cloned, REFDTYPEs in the global type table may +// still point to the dead template module. This visitor traverses the type +// table and redirects those references to the appropriate live clone. +// +// Handles both AstRefDType (direct typedef references) and AstMemberDType +// (struct/union member types) in a single traversal for efficiency. +class TypeTableDeadRefVisitor final : public VNVisitor { + int m_fixed = 0; + + void visit(AstRefDType* refp) override { + iterateChildren(refp); + // For type table entries, find the first live module that contains + // a cell hierarchy leading to the dead target + AstNodeModule* containingModp = nullptr; + AstNodeModule* deadTargetModp = nullptr; + // Check BOTH typedefp and refDTypep for dead owners. + // Either (or both) may point to a dead module. + if (refp->typedefp()) { + AstNodeModule* const tdOwnerp + = V3LinkDotIfaceCapture::findOwnerModule(refp->typedefp()); + if (tdOwnerp && tdOwnerp->dead()) deadTargetModp = tdOwnerp; + } + if (!deadTargetModp && refp->refDTypep()) { + AstNodeModule* const rdOwnerp + = V3LinkDotIfaceCapture::findOwnerModule(refp->refDTypep()); + if (rdOwnerp && rdOwnerp->dead()) deadTargetModp = rdOwnerp; + } + if (deadTargetModp) { + V3LinkDotIfaceCapture::findLiveCloneOf(deadTargetModp, &containingModp); + } + m_fixed += V3LinkDotIfaceCapture::fixDeadRefs(refp, containingModp, "type table"); + } + + void visit(AstMemberDType* memberp) override { + iterateChildren(memberp); + if (!memberp->dtypep()) return; + AstNodeModule* const dtOwnerp = V3LinkDotIfaceCapture::findOwnerModule(memberp->dtypep()); + if (!dtOwnerp || !dtOwnerp->dead()) return; + // Try to find the clone of the dead module + AstNodeModule* const cloneModp = V3LinkDotIfaceCapture::findLiveCloneOf(dtOwnerp); + if (cloneModp) { + // Find matching type by name in the clone + const string& dtName = memberp->dtypep()->prettyName(); + // Try typedef children + for (AstNode* sp = cloneModp->stmtsp(); sp; sp = sp->nextp()) { + if (AstTypedef* const tdp = VN_CAST(sp, Typedef)) { + if (tdp->subDTypep() && tdp->subDTypep()->prettyName() == dtName) { + UINFO(9, "iface capture type table MEMBERDTYPE fixup (via typedef): " + << memberp->name() << " dtypep " << dtOwnerp->name() << " -> " + << cloneModp->name()); + memberp->dtypep(tdp->subDTypep()); + ++m_fixed; + return; + } + } + } + } + // One of the above fixup paths (prettyName or typedef) always succeeds + // when cloneModp is found. If this fires, either cloneModp is null + // (findLiveCloneOf failed - check that the dead template has a clone) + // or the dtype name doesn't match any statement in the clone (check + // memberp->dtypep()->prettyName() against cloneModp's statements). + v3fatalSrc("MemberDType fixup: could not fix member '" + << memberp->name() << "' dtypep points to dead " << dtOwnerp->name() + << " cloneModp=" << (cloneModp ? cloneModp->name() : "")); + } + + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + int fixed() const { return m_fixed; } + explicit TypeTableDeadRefVisitor(AstNode* nodep) { iterate(nodep); } +}; + +int V3LinkDotIfaceCapture::fixDeadRefsInTypeTable() { + if (!v3Global.rootp()->typeTablep()) return 0; + TypeTableDeadRefVisitor visitor{v3Global.rootp()->typeTablep()}; + return visitor.fixed(); +} + +int V3LinkDotIfaceCapture::fixDeadRefsInModules() { + int fixed = 0; + for (AstNode* nodep = v3Global.rootp()->modulesp(); nodep; nodep = nodep->nextp()) { + if (AstNodeModule* const modp = VN_CAST(nodep, NodeModule)) { + if (modp->dead()) continue; + const string modName = modp->name(); + modp->foreach( + [&](AstRefDType* refp) { fixed += fixDeadRefs(refp, modp, modName.c_str()); }); + } + } + return fixed; +} + +int V3LinkDotIfaceCapture::fixWrongCloneRefs() { + int fixed = 0; + + // TARGET RESOLUTION - the ONLY place that resolves targets and + // mutates AST. By this point all cloning is complete and cell pointers + // are wired to the correct interface clones. For each entry we walk + // cellPath from the owner module to find the correct target module, then + // locate the PARAMTYPEDTYPE / TYPEDEF by name. + // See V3LinkDotIfaceCapture.h ARCHITECTURE comment for the full picture. + + forEach([&](const CapturedEntry& entry) { + AstRefDType* const refp = entry.refp; + if (!refp) return; + // For clone entries the stored ownerModp is the template (stale cells). + // Use the actual module containing the REFDTYPE - its cells are wired + // to the correct interface clones by this point. + // findOwnerModule handles corrupted backp() chains gracefully. + AstNodeModule* const ownerModp + = !entry.cloneCellPath.empty() ? findOwnerModule(refp) : entry.ownerModp; + if (!ownerModp || ownerModp->dead() || VN_IS(ownerModp, Package)) return; + + AstNodeModule* const rdOwnerBefore + = (refp->refDTypep() ? findOwnerModule(refp->refDTypep()) : nullptr); + UINFO(9, + "finalizeIfaceCapture Phase3 entry: refp=" + << refp->name() << " (" << cvtToHex(refp) << ")" + << " ownerMod=" << ownerModp->name() << " (dead=" << ownerModp->dead() << ")" + << " storedOwnerMod=" << (entry.ownerModp ? entry.ownerModp->name() : "") + << " cellPath='" << entry.cellPath << "' cloneCellPath='" << entry.cloneCellPath + << "' typedefp=" << (refp->typedefp() ? refp->typedefp()->name() : "") + << " refDTypep=" << (refp->refDTypep() ? refp->refDTypep()->name() : "") + << " refDTypepOwner=" << (rdOwnerBefore ? rdOwnerBefore->name() : "") + << " refDTypepDead=" << (rdOwnerBefore ? rdOwnerBefore->dead() : 0)); + + // Determine the correct target module using cellPath + AstNodeModule* correctModp = nullptr; + if (!entry.cellPath.empty()) { + correctModp = followCellPath(ownerModp, entry.cellPath); + UINFO(9, " followCellPath('" + << ownerModp->name() << "', '" << entry.cellPath + << "') = " << (correctModp ? correctModp->name() : "") + << (correctModp ? (correctModp->dead() ? " (DEAD)" : " (live)") : "")); + if (correctModp && correctModp->dead()) { correctModp = nullptr; } + } + + // Proactive target resolution: when cellPath resolved to a valid + // correctModp, find the PARAMTYPEDTYPE or TYPEDEF by name and apply. + if (correctModp) { + const string& refName = refp->name(); + bool resolved = false; + if (AstParamTypeDType* const ptdp = findParamTypeInModule(correctModp, refName)) { + refp->refDTypep(ptdp); + refp->user3(true); + resolved = true; + UINFO(9, "finalizeIfaceCapture Phase3: resolved paramTypep '" + << refName << "' in " << correctModp->name() << " for refp in " + << ownerModp->name() << " cloneCellPath='" << entry.cloneCellPath + << "'"); + } else if (AstTypedef* const tdp = findTypedefInModule(correctModp, refName)) { + refp->typedefp(tdp); + refp->user3(true); + resolved = true; + UINFO(9, "finalizeIfaceCapture Phase3: resolved typedefp '" + << refName << "' in " << correctModp->name() << " for refp in " + << ownerModp->name() << " cloneCellPath='" << entry.cloneCellPath + << "'"); + } + if (resolved) { + ++fixed; + return; + } + } + + // Note: the structural disambiguation infrastructure (collectReachable, + // findCorrectClone, wrong-clone fixup blocks) was removed. All captured + // entries have non-empty cellPath, and disambiguateTarget always returns + // nullptr through the "cellPath unresolved" path. The wrong-clone fixup + // blocks were dead code - CI-CD with v3fatalSrc asserts confirmed this. + // Unresolved entries are fixed by fixDeadRefs in a later phase. + }); + + return fixed; +} + +void V3LinkDotIfaceCapture::verifyNoDeadRefs() { + // Assert: no REFDTYPE in any live module should have typedefp or refDTypep + // pointing to a dead module. + for (AstNode* nodep = v3Global.rootp()->modulesp(); nodep; nodep = nodep->nextp()) { + if (AstNodeModule* const modp = VN_CAST(nodep, NodeModule)) { + if (modp->dead()) continue; + modp->foreach([&](AstRefDType* refp) { + if (refp->typedefp()) { + AstNodeModule* const ownerModp = findOwnerModule(refp->typedefp()); + // Diagnostic block: only entered when fixup logic has a bug + // and leaves a typedefp pointing to a dead module. + if (ownerModp && ownerModp->dead()) { // LCOV_EXCL_START + bool inLedger = false; + forEach([&](const CapturedEntry& e) { + if (e.refp == refp) inLedger = true; + }); + UINFO(9, "VERIFY FAIL typedefp: refp=" + << refp->name() << " (" << cvtToHex(refp) << ")" << " in mod=" + << modp->name() << " typedefp->owner=" << ownerModp->name() + << " inLedger=" << inLedger); + } // LCOV_EXCL_STOP + UASSERT_OBJ(!ownerModp || !ownerModp->dead(), refp, // LCOV_EXCL_LINE + "REFDTYPE '" << refp->prettyNameQ() << "' in live module '" + << modp->prettyNameQ() + << "' has typedefp pointing to dead module '" + << ownerModp->prettyNameQ() << "'"); + } + if (refp->refDTypep()) { + AstNodeModule* const ownerModp = findOwnerModule(refp->refDTypep()); + // Diagnostic block: only entered when fixup logic has a bug + // and leaves a refDTypep pointing to a dead module. + if (ownerModp && ownerModp->dead()) { // LCOV_EXCL_START + bool inLedger = false; + forEach([&](const CapturedEntry& e) { + if (e.refp == refp) inLedger = true; + }); + UINFO(9, "VERIFY FAIL refDTypep: refp=" + << refp->name() << " (" << cvtToHex(refp) << ")" << " in mod=" + << modp->name() << " refDTypep->owner=" << ownerModp->name() + << " inLedger=" << inLedger); + } // LCOV_EXCL_STOP + UASSERT_OBJ(!ownerModp || !ownerModp->dead(), refp, // LCOV_EXCL_LINE + "REFDTYPE '" << refp->prettyNameQ() << "' in live module '" + << modp->prettyNameQ() + << "' has refDTypep pointing to dead module '" + << ownerModp->prettyNameQ() << "'"); + } + }); + } + } + if (v3Global.rootp()->typeTablep()) { + for (AstNode* nodep = v3Global.rootp()->typeTablep()->typesp(); nodep; + nodep = nodep->nextp()) { + nodep->foreach([&](AstRefDType* refp) { + if (refp->typedefp()) { + AstNodeModule* const ownerModp = findOwnerModule(refp->typedefp()); + // Bug-only assertion: fires only if fixup logic fails to + // resolve a type-table typedefp away from a dead module. + UASSERT_OBJ(!ownerModp || !ownerModp->dead(), refp, // LCOV_EXCL_LINE + "REFDTYPE '" + << refp->prettyNameQ() + << "' in type table has typedefp pointing to dead module '" + << ownerModp->prettyNameQ() << "'"); + } + if (refp->refDTypep()) { + AstNodeModule* const ownerModp = findOwnerModule(refp->refDTypep()); + // Bug-only assertion: fires only if fixup logic fails to + // resolve a type-table refDTypep away from a dead module. + UASSERT_OBJ(!ownerModp || !ownerModp->dead(), refp, // LCOV_EXCL_LINE + "REFDTYPE '" + << refp->prettyNameQ() + << "' in type table has refDTypep pointing to dead module '" + << ownerModp->prettyNameQ() << "'"); + } + }); + } + } +} + +void V3LinkDotIfaceCapture::finalizeIfaceCapture() { + if (!s_enabled) return; + UINFO(4, "finalizeIfaceCapture: fixing remaining cross-interface refs"); + if (!v3Global.rootp()) return; + clearModuleCache(); // Ensure fresh view after all cloning/widthing + + const int typeTableFixed = fixDeadRefsInTypeTable(); + const int moduleFixed = fixDeadRefsInModules(); + UINFO(4, "finalizeIfaceCapture: fixed " << typeTableFixed << " in type table, " << moduleFixed + << " in modules (dead refs)"); + + const int wrongCloneFixed = fixWrongCloneRefs(); + UINFO(4, "finalizeIfaceCapture: fixed " << wrongCloneFixed << " wrong-live-clone pointers"); + + if (debug() >= 9) dumpEntries("after finalizeIfaceCapture"); + + // Emit statistics for --stats + int templates = 0; + int clones = 0; + for (const auto& kv : s_map) { + if (kv.first.cloneCellPath.empty()) { + ++templates; + } else { + ++clones; + } + } + V3Stats::addStat("IfaceCapture, Entries total", s_map.size()); + V3Stats::addStat("IfaceCapture, Entries template", templates); + V3Stats::addStat("IfaceCapture, Entries cloned", clones); + V3Stats::addStat("IfaceCapture, Dead refs fixed in type table", typeTableFixed); + V3Stats::addStat("IfaceCapture, Dead refs fixed in modules", moduleFixed); + V3Stats::addStat("IfaceCapture, Wrong-clone refs fixed", wrongCloneFixed); + + verifyNoDeadRefs(); + reset(); } diff --git a/src/V3LinkDotIfaceCapture.h b/src/V3LinkDotIfaceCapture.h index 7c7f53cce..781e03788 100644 --- a/src/V3LinkDotIfaceCapture.h +++ b/src/V3LinkDotIfaceCapture.h @@ -23,74 +23,183 @@ #include "config_build.h" #include "V3Ast.h" -#include "V3SymTable.h" #include #include +#include #include +#include + +class VSymEnt; class V3LinkDotIfaceCapture final { public: enum class CaptureType { IFACE, CLASS }; - struct CapturedIfaceTypedef final { + + // Path-based map key: no pointers, only stable strings. + // {ownerModName, refName, cellPath, cloneCellPath} uniquely identifies + // every captured REFDTYPE. You cannot have two typedefs with the same + // name in the same module, so this tuple is unique. + struct CaptureKey final { + string ownerModName; // Module containing the REFDTYPE (e.g. "cca_xbar") + string refName; // REFDTYPE name (e.g. "r_chan_t") + string cellPath; // Template path (e.g. "cca_io.tlb_io") + string cloneCellPath; // Instance path (e.g. "xbar1"), empty for template + bool operator==(const CaptureKey& o) const { + return ownerModName == o.ownerModName && refName == o.refName && cellPath == o.cellPath + && cloneCellPath == o.cloneCellPath; + } + }; + struct CaptureKeyHash final { + size_t operator()(const CaptureKey& k) const { + size_t h = std::hash{}(k.ownerModName); + h ^= std::hash{}(k.refName) + 0x9e3779b9 + (h << 6) + (h >> 2); + h ^= std::hash{}(k.cellPath) + 0x9e3779b9 + (h << 6) + (h >> 2); + h ^= std::hash{}(k.cloneCellPath) + 0x9e3779b9 + (h << 6) + (h >> 2); + return h; + } + }; + + // Template key: matches ALL entries regardless of cloneCellPath. + // Used for propagateClone and debug searches. + struct TemplateKey final { + string ownerModName; + string refName; + string cellPath; + }; + + struct CapturedEntry final { CaptureType captureType = CaptureType::IFACE; AstRefDType* refp = nullptr; - AstCell* cellp = nullptr; // now for IFACE captures - AstClass* origClassp = nullptr; // new for CLASS captures + string cellPath; // Template path (e.g. "cca_io.tlb_io") - immutable key component + string cloneCellPath; // Instance-specific path (e.g. "cca_io1.tlb_io") - set by + // propagateClone when V3Param clones; empty for original entries + AstClass* origClassp = nullptr; // For CLASS captures // Module where the RefDType lives AstNodeModule* ownerModp = nullptr; // Typedef definition being referenced AstTypedef* typedefp = nullptr; - // Interface/module that owns typedefp - AstNodeModule* typedefOwnerModp = nullptr; - // Cloned RefDType awaiting typedef rebinding - AstRefDType* pendingClonep = nullptr; + // For PARAMTYPEDTYPE + AstParamTypeDType* paramTypep = nullptr; + // Name of the module/interface that owns the typedef (stable string) + string typedefOwnerModName; // Interface port variable for matching during cloning AstVar* ifacePortVarp = nullptr; + // Additional REFDTYPEs sharing the same key (e.g. from macro expansions + // that produce multiple $bits() references to the same interface typedef). + // The primary refp is stored above; extras are appended here so that + // retargeting fixes ALL of them, not just the last-writer-wins primary. + std::vector extraRefps; + // Clear template-specific targets that are stale in a clone context. + // Called by propagateClone before inserting a clone entry. + void clearStaleRefs() { + paramTypep = nullptr; + typedefp = nullptr; + extraRefps.clear(); + } + // Visit every AstNode* pointer field (analogous to AstNode::foreachLink). + // The callback receives an AstNode* by reference; if it nulls the + // pointer the typed member is nulled accordingly. + template + void foreachLink(T_func&& fn) { + auto callOnNode = [&](auto*& ptr) { + AstNode* np = ptr; + fn(np); + if (!np) ptr = nullptr; + }; + callOnNode(refp); + callOnNode(ownerModp); + callOnNode(typedefp); + callOnNode(paramTypep); + callOnNode(ifacePortVarp); + callOnNode(origClassp); + for (auto& xrefp : extraRefps) callOnNode(xrefp); + } }; - using CapturedMap = std::unordered_map; + using CapturedMap = std::unordered_map; private: + friend class TypeTableDeadRefVisitor; + static CapturedMap s_map; static bool s_enabled; - static AstNodeModule* findOwnerModule(AstNode* nodep); - static bool finalizeCapturedEntry(CapturedMap::iterator it, const char* reasonp); + // --- Internal-only methods (not called outside V3LinkDotIfaceCapture.cpp) --- + static void enable(bool flag); // LCOV_EXCL_LINE + static void reset(); + static void clearModuleCache(); + static AstIfaceRefDType* ifaceRefFromVarDType(AstNodeDType* dtypep); static string extractIfacePortName(const string& dotText); - - template - static void forEachImpl(FilterFn&& filter, Fn&& fn); + static AstNodeDType* findDTypeByPrettyName(AstNodeModule* modp, const string& prettyName); + static AstNodeModule* findCloneViaHierarchy(AstNodeModule* containingModp, + AstNodeModule* deadTargetModp, int depth = 0); + static AstNodeModule* findLiveCloneOf(AstNodeModule* deadTargetModp, + AstNodeModule** containerp = nullptr); + static int fixDeadRefs(AstRefDType* refp, AstNodeModule* containingModp, const char* location); + static void captureInnerParamTypeRefs(AstParamTypeDType* paramTypep, AstRefDType* refp, + const string& cellPath, const string& ownerModName, + const string& ptOwnerName); + static int fixDeadRefsInTypeTable(); + static int fixDeadRefsInModules(); + static int fixWrongCloneRefs(); + static void verifyNoDeadRefs(); + template + static void forEachImpl(T_FilterFn&& filter, T_Fn&& fn); public: - static void enable(bool flag) { - s_enabled = flag; - if (!flag) s_map.clear(); - } static bool enabled() { return s_enabled; } - static void reset() { s_map.clear(); } - static void add(AstRefDType* refp, AstCell* cellp, AstNodeModule* ownerModp, - AstTypedef* typedefp = nullptr, AstNodeModule* typedefOwnerModp = nullptr, + static AstNodeModule* findOwnerModule(AstNode* nodep); + // Find a Typedef by name in a module's top-level statements + static AstTypedef* findTypedefInModule(AstNodeModule* modp, const string& name); + // Find a NodeDType by name and VNType in a module's top-level statements + static AstNodeDType* findDTypeInModule(AstNodeModule* modp, const string& name, VNType type); + // Find a ParamTypeDType by name in a module's top-level statements + static AstParamTypeDType* findParamTypeInModule(AstNodeModule* modp, const string& name); + static void add(AstRefDType* refp, const string& cellPath, AstNodeModule* ownerModp, + AstTypedef* typedefp = nullptr, const string& typedefOwnerModName = "", AstVar* ifacePortVarp = nullptr); static void addClass(AstRefDType* refp, AstClass* origClassp, AstNodeModule* ownerModp, - AstTypedef* typedefp = nullptr, - AstNodeModule* typedefOwnerModp = nullptr); - static const CapturedIfaceTypedef* find(const AstRefDType* refp); - static void forEach(const std::function& fn); + AstTypedef* typedefp = nullptr, const string& typedefOwnerModName = ""); + static void addParamType(AstRefDType* refp, const string& cellPath, AstNodeModule* ownerModp, + AstParamTypeDType* paramTypep, const string& paramTypeOwnerModName, + AstVar* ifacePortVarp); + // Exact lookup by full key + static const CapturedEntry* find(const CaptureKey& key); + // Pointer-based lookup: linear scan with early exit (no std::function overhead) + static const CapturedEntry* find(const AstRefDType* refp); + static void forEach(const std::function& fn); static void forEachOwned(const AstNodeModule* ownerModp, - const std::function& fn); - static bool replaceRef(const AstRefDType* oldRefp, AstRefDType* newRefp); - static bool replaceTypedef(const AstRefDType* refp, AstTypedef* newTypedefp); - static bool erase(const AstRefDType* refp); + const std::function& fn); static std::size_t size() { return s_map.size(); } - static void propagateClone(const AstRefDType* origRefp, AstRefDType* newRefp); - static void - captureTypedefContext(AstRefDType* refp, const char* stageLabel, int dotPos, bool dotIsFinal, - const std::string& dotText, VSymEnt* dotSymp, VSymEnt* curSymp, - AstNodeModule* modp, AstNode* nodep, - const std::function& promoteVarCb, - const std::function& indentFn); + // Walk a dot-separated cell path (e.g. "cca_io.tlb_io") starting from + // startModp, returning the module at the end of the path. Returns + // nullptr if any component cannot be resolved. + static AstNodeModule* followCellPath(AstNodeModule* startModp, const string& cellPath); + + // Create a new clone entry in the ledger, inheriting from the template. + // Ledger-only: no target lookup or AST mutation. Target resolution + // happens later in finalizeIfaceCapture where cell pointers are wired up. + static void propagateClone(const TemplateKey& tkey, AstRefDType* newRefp, + const string& cloneCellPath); + + static void captureTypedefContext(AstRefDType* refp, const char* stageLabel, int dotPos, + bool dotIsFinal, const std::string& dotText, + VSymEnt* dotSymp, VSymEnt* curSymp, AstNodeModule* modp, + AstNode* nodep, + const std::function& indentFn); + + // Null out ledger refp entries that point to freed nodes (not in the live AST). + // Called once after V3Param completes, before any code touches the ledger. + static void purgeStaleRefs(); + + // Debug: dump all captured entries + static void dumpEntries(const string& label); + + // Called after V3Param but before V3Dead to fix any remaining cross-interface refs + // that still point to template nodes (which will be deleted by V3Dead). + static void finalizeIfaceCapture(); }; #endif // VERILATOR_V3LINKDOTIFACECAPTURE_H_ diff --git a/src/V3Param.cpp b/src/V3Param.cpp index aeddbbeb4..9787f13d1 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -67,6 +67,7 @@ #include #include #include +#include #include VL_DEFINE_DEBUG_FUNCTIONS; @@ -375,6 +376,39 @@ class ParamProcessor final { key += ","; } key += "}"; + } else if (const AstClassRefDType* const classRefp = VN_CAST(nodep, ClassRefDType)) { + // For parameterized class types, use the original class name (without specialization + // suffix) plus the actual type parameter values. This ensures equivalent class types + // get the same string representation regardless of which AST node is used. + if (classRefp->classp()) { + const string& className = classRefp->classp()->name(); + const string& origName = classRefp->classp()->origName(); + const bool isSpecialized = (className != origName); + UINFO(9, "paramValueString ClassRefDType: name=" + << className << " origName=" << origName + << " isSpecialized=" << isSpecialized + << " hasParams=" << (classRefp->paramsp() ? "Y" : "N") + << " classHasGParam=" << classRefp->classp()->hasGParam() << endl); + + if (classRefp->paramsp()) { + // ClassRefDType should have been deparameterized (paramsp + // consumed) before cellPinCleanup calls paramValueString. + classRefp->v3fatalSrc( // LCOV_EXCL_LINE + "ClassRefDType still has paramsp in paramValueString"); + } else if (isSpecialized) { + // Already specialized class (e.g., c1__Tz1_TBz1) - use full name + // This ensures different specializations are distinguished + key = className; + } else { + // Unspecialized class with no params - use origName + key = origName; + } + } else { + // classp() should always be set; unresolved class refs + // would have errored in LinkDot. + classRefp->v3fatalSrc( // LCOV_EXCL_LINE + "ClassRefDType has null classp in paramValueString"); + } } else if (const AstNodeDType* const dtypep = VN_CAST(nodep, NodeDType)) { key += dtypep->prettyDTypeName(true); } @@ -383,13 +417,24 @@ class ParamProcessor final { } string paramValueNumber(AstNode* nodep) { - // TODO: This parameter value number lookup via a constructed key string is not - // particularly robust for type parameters. We should really have a type - // equivalence predicate function. - if (AstRefDType* const refp = VN_CAST(nodep, RefDType)) nodep = refp->skipRefToNonRefp(); + // For type parameters (NodeDType), use only the string representation for hashing. + // Using V3Hasher::uncachedHash includes AST node pointer which differs for equivalent + // types represented by different AST nodes (e.g., parameterized class specializations). + // For value parameters, we can still use the AST hash for better collision resistance. + // All call sites resolve through skipRefToNonRefp() or pass non-DType + // nodes, so nodep should never be a bare RefDType here. + if (VN_IS(nodep, RefDType)) { // LCOV_EXCL_LINE + nodep->v3fatalSrc("Unexpected RefDType in paramValueNumber"); // LCOV_EXCL_LINE + } const string paramStr = paramValueString(nodep); - // cppcheck-suppress unreadVariable - V3Hash hash = V3Hasher::uncachedHash(nodep) + paramStr; + V3Hash hash; + if (VN_IS(nodep, NodeDType)) { + // Type parameter: use only string-based hash for type equivalence + hash = V3Hash{paramStr}; + } else { + // Value parameter: use AST hash + string for better collision resistance + hash = V3Hasher::uncachedHash(nodep) + paramStr; + } // Force hash collisions -- for testing only // cppcheck-suppress unreadVariable if (VL_UNLIKELY(v3Global.opt.debugCollision())) hash = V3Hash{paramStr}; @@ -535,7 +580,7 @@ class ParamProcessor final { // Using map with key=string so that we can scan it in deterministic order DefaultValueMap params; for (AstNode* stmtp = modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) { - if (const AstVar* const varp = VN_CAST(stmtp, Var)) { + if (AstVar* const varp = VN_CAST(stmtp, Var)) { if (varp->isGParam()) { AstConst* const constp = VN_CAST(varp->valuep(), Const); // constp can be nullptr if the parameter is not used to instantiate sub @@ -544,7 +589,8 @@ class ParamProcessor final { params.emplace(varp->name(), constp); } } else if (AstParamTypeDType* const p = VN_CAST(stmtp, ParamTypeDType)) { - params.emplace(p->name(), p->skipRefp()); + AstNode* const dtypep = static_cast(p->skipRefp()); + params.emplace(p->name(), dtypep); } } pair.first->second = std::move(params); @@ -608,6 +654,30 @@ class ParamProcessor final { } else if (AstClassOrPackageRef* const classRefp = VN_CAST(nodep, ClassOrPackageRef)) { if (classRefp->classOrPackageSkipp() == oldClassp) classRefp->classOrPackagep(newClassp); + } else if (AstTypedef* const typedefp = VN_CAST(nodep, Typedef)) { + // Update typedefs that refer to the old class to point to the new class + if (typedefp->subDTypep()) { + if (AstClassRefDType* const classRefp + = VN_CAST(typedefp->subDTypep(), ClassRefDType)) { + if (classRefp->classp() == oldClassp) { classRefp->classp(newClassp); } + } + } + } else if (AstNodeFTaskRef* const ftaskRefp = VN_CAST(nodep, NodeFTaskRef)) { + // Also update FuncRef/TaskRef packagep to point to new class + // This fixes static method calls through typedefs in parameterized classes + if (ftaskRefp->classOrPackagep() == oldClassp) ftaskRefp->classOrPackagep(newClassp); + // Also update taskp if it points to a function in the old class. + // AstNodeFTask::classOrPackagep() (op2) holds a parse-time + // Dot/ClassOrPackageRef for extern declarations, which is deleted + // in LinkDot::moveExternFuncDecl before V3Param runs. For inline + // class methods op2 is nullptr. So this should never match. + if (AstNodeFTask* const oldTaskp = ftaskRefp->taskp()) { + if (oldTaskp->classOrPackagep() == oldClassp) { + oldTaskp->v3fatalSrc( // LCOV_EXCL_LINE + "FTask classOrPackagep unexpectedly matches old class " + << oldClassp->prettyNameQ()); + } + } } if (nodep->op1p()) replaceRefsRecurse(nodep->op1p(), oldClassp, newClassp); @@ -718,6 +788,154 @@ class ParamProcessor final { void visit(AstNode* nodep) override { iterateChildren(nodep); } }; + // Returns true if entry's cellPath ends with cloneCellp->name() and + // the parent portion of the path resolves (from startModp) to expectModp. + bool cellPathMatchesClone(const string& cellPath, const AstCell* cloneCellp, + AstNodeModule* startModp, const AstNodeModule* expectModp) const { + if (!cloneCellp || cellPath.empty()) return false; + const size_t lastDot = cellPath.rfind('.'); + const string lastComp + = (lastDot == string::npos) ? cellPath : cellPath.substr(lastDot + 1); + const size_t braPos = lastComp.find("__BRA__"); + const string lastCompBase + = (braPos == string::npos) ? lastComp : lastComp.substr(0, braPos); + if (lastComp != cloneCellp->name() && lastCompBase != cloneCellp->name()) return false; + if (lastDot == string::npos) return true; // No parent portion to verify + const string parentPath = cellPath.substr(0, lastDot); + const AstNodeModule* const resolvedp + = V3LinkDotIfaceCapture::followCellPath(startModp, parentPath); + return resolvedp == expectModp; + } + + // Retarget entry.refp (and extraRefps) to the typedef/paramType found + // in targetModp. Returns true if anything was retargeted. + static bool retargetRefToModule(const V3LinkDotIfaceCapture::CapturedEntry& entry, + AstNodeModule* targetModp) { + if (entry.refp->typedefp()) { + if (AstTypedef* const tdp = V3LinkDotIfaceCapture::findTypedefInModule( + targetModp, entry.refp->typedefp()->name())) { + entry.refp->typedefp(tdp); + if (tdp->subDTypep()) { + entry.refp->refDTypep(tdp->subDTypep()); + entry.refp->dtypep(tdp->subDTypep()); + } + for (AstRefDType* const xrefp : entry.extraRefps) { + xrefp->typedefp(tdp); + if (tdp->subDTypep()) { + xrefp->refDTypep(tdp->subDTypep()); + xrefp->dtypep(tdp->subDTypep()); + } + } + return true; + } + } else if (entry.paramTypep) { + if (AstParamTypeDType* const ptp = V3LinkDotIfaceCapture::findParamTypeInModule( + targetModp, entry.paramTypep->name())) { + entry.refp->refDTypep(ptp); + entry.refp->dtypep(ptp); + for (AstRefDType* const xrefp : entry.extraRefps) { + xrefp->refDTypep(ptp); + xrefp->dtypep(ptp); + } + return true; + } + } + return false; + } + + // Fix cross-module REFDTYPE pointers in newModp after cloneTree. + // Phase A: path-based fixup using ledger entries with cellPath. + // Phase B: reachable-set fallback for remaining REFDTYPEs. + void fixupCrossModuleRefDTypes(AstNodeModule* newModp, AstNodeModule* srcModp, + AstNode* ifErrorp, const IfaceRefRefs& ifaceRefRefs) { + if (!V3LinkDotIfaceCapture::enabled()) return; + // Phase A: path-based fixup using ledger entries + std::set ledgerFixed; + { + const string cloneCP = VN_CAST(ifErrorp, Cell) ? VN_AS(ifErrorp, Cell)->name() : ""; + const string srcName = srcModp->name(); + UINFO(9, "iface capture FIXUP-A: srcName=" << srcName << " cloneCP='" << cloneCP << "'" + << endl); + V3LinkDotIfaceCapture::forEach([&](const V3LinkDotIfaceCapture::CapturedEntry& entry) { + if (!entry.refp) return; + if (entry.cloneCellPath != cloneCP) return; + if (!entry.ownerModp || entry.ownerModp->name() != srcName) return; + if (entry.cellPath.empty()) return; + + AstRefDType* const refp = entry.refp; + AstNodeModule* const correctModp + = V3LinkDotIfaceCapture::followCellPath(newModp, entry.cellPath); + UINFO(9, " path fixup: " << refp << " cellPath='" << entry.cellPath << "' -> " + << (correctModp ? correctModp->name() : "") + << endl); + if (!correctModp || correctModp->dead()) return; + + bool fixed = false; + if (refp->typedefp()) { + if (AstTypedef* const newTdp = V3LinkDotIfaceCapture::findTypedefInModule( + correctModp, refp->typedefp()->name())) { + refp->typedefp(newTdp); + fixed = true; + } + } + if (refp->refDTypep()) { + if (AstNodeDType* const newDtp = V3LinkDotIfaceCapture::findDTypeInModule( + correctModp, refp->refDTypep()->name(), refp->refDTypep()->type())) { + refp->refDTypep(newDtp); + fixed = true; + } + } + if (fixed) ledgerFixed.insert(refp); + }); + V3Stats::addStatSum("IfaceCapture, Ledger fixups in V3Param", ledgerFixed.size()); + } + + // Phase B: reachable-set fallback for REFDTYPEs not handled by ledger + std::set reachable; + reachable.insert(newModp); + std::function collectReachable; + collectReachable = [&](AstNodeModule* modp) { + for (AstNode* sp = modp->stmtsp(); sp; sp = sp->nextp()) { + if (AstCell* const cellp = VN_CAST(sp, Cell)) { + AstNodeModule* const cellModp = cellp->modp(); + if (cellModp && reachable.insert(cellModp).second) { + collectReachable(cellModp); + } + } + } + }; + for (const auto& pair : ifaceRefRefs) { + AstIface* const pinIfacep = pair.second->ifaceViaCellp(); + if (pinIfacep && reachable.insert(pinIfacep).second) { collectReachable(pinIfacep); } + } + collectReachable(newModp); + + // Phase B (reachable-set fallback): Phase A (path-based ledger fixup) + // always resolves all statement-level REFDTYPEs for current tests and + // Aerial. Assert if any REFDTYPE slips through so we can investigate. + // The loop body is assert-only (no mutations); LCOV_EXCL because + // Phase A always resolves everything and ledgerFixed catches all refs. + for (AstNode* stmtp = newModp->stmtsp(); stmtp; stmtp = stmtp->nextp()) { + AstRefDType* const refp = VN_CAST(stmtp, RefDType); + if (!refp) continue; + if (ledgerFixed.count(refp)) continue; // LCOV_EXCL_LINE + // LCOV_EXCL_START + // Check if typedefp or refDTypep points outside the reachable set + auto checkNotStale = [&](const char* label, AstNode* targetp) { + AstNodeModule* const ownerp = V3LinkDotIfaceCapture::findOwnerModule(targetp); + if (!ownerp || ownerp == newModp || VN_IS(ownerp, Package) + || reachable.count(ownerp)) + return; // OK: owner is reachable or self + v3fatalSrc("Phase B reachable-set fallback triggered for " + << refp->prettyNameQ() << " " << label << " owner=" + << ownerp->prettyNameQ() << " in " << newModp->prettyNameQ()); + }; + if (refp->typedefp()) checkNotStale("typedefp", refp->typedefp()); + if (refp->refDTypep()) checkNotStale("refDTypep", refp->refDTypep()); + // LCOV_EXCL_STOP + } + } + // Return true on success, false on error bool deepCloneModule(AstNodeModule* srcModp, AstNode* ifErrorp, AstPin* paramsp, const string& newname, const IfaceRefRefs& ifaceRefRefs) { @@ -731,73 +949,83 @@ class ParamProcessor final { newModp = srcModp->cloneTree(false); } + // Mark the source module as a parameterized template now that a specialized + // clone exists. This suppresses width/type errors on the unresolved template + // during widthParamsEdit (which runs before V3LinkDot sets dead()). + srcModp->parameterizedTemplate(true); + // The clone is a specialized instance, not a template. Clear the flag in + // case it was inherited from a prior cloneTree (when srcModp was already + // marked by an earlier specialization). + newModp->parameterizedTemplate(false); + // cloneTree(false) temporarily populates origNode->clonep() for every node under // srcModp. The capture list still stores those orig AstRefDType* pointers, so walking // it lets us follow clonep() into newModp and scrub each clone with the saved // interface context before newModp is re-linked. we have pointers to the same nodes saved // in the capture map, so we can use them to scrub the new module. + if (V3LinkDotIfaceCapture::enabled()) { + AstCell* const cloneCellp = VN_CAST(ifErrorp, Cell); + UINFO(9, "iface capture clone: " << srcModp->prettyNameQ() << " -> " + << newModp->prettyNameQ() << endl); + // First pass: register clone entries and direct-retarget + // REFDTYPEs whose owner won't be cloned later. V3LinkDotIfaceCapture::forEachOwned( - srcModp, [&](const V3LinkDotIfaceCapture::CapturedIfaceTypedef& entry) { + srcModp, [&](const V3LinkDotIfaceCapture::CapturedEntry& entry) { if (!entry.refp) return; - AstTypedef* const origTypedefp = entry.typedefp; - if (!origTypedefp) return; - - // Find the correct typedef from the correct interface clone. - // entry.typedefp points to the original interface's typedef, - // but we need the typedef in the interface clone this module connects to. - AstTypedef* targetTypedefp = nullptr; - const string& typedefName = origTypedefp->name(); - - for (auto it = ifaceRefRefs.cbegin(); it != ifaceRefRefs.cend(); ++it) { - const AstIfaceRefDType* const portIrefp = it->first; - AstNodeModule* const pinIfacep = it->second->ifaceViaCellp(); - if (!pinIfacep) continue; - - // If we have a port variable, match against it - if (entry.ifacePortVarp) { - // Get the IfaceRefDType from the captured port variable - AstNodeDType* const portDTypep = entry.ifacePortVarp->subDTypep(); - AstIfaceRefDType* entryPortIrefp = VN_CAST(portDTypep, IfaceRefDType); - if (!entryPortIrefp && arraySubDTypep(portDTypep)) { - entryPortIrefp - = VN_CAST(arraySubDTypep(portDTypep), IfaceRefDType); - } - if (entryPortIrefp != portIrefp) continue; // Not the right port + UINFO(9, "iface capture entry: " << entry.refp << " cellPath='" + << entry.cellPath << "'" << endl); + // Disambiguate via cellPath when cloning the interface + // that owns the typedef (matched via typedefOwnerModName). + if (cloneCellp && entry.ownerModp != srcModp + && entry.typedefOwnerModName == srcModp->name()) { + UASSERT_OBJ(!entry.cellPath.empty(), entry.refp, + "cellPath is empty in entry matched via typedefOwnerModName"); + if (!cellPathMatchesClone(entry.cellPath, cloneCellp, entry.ownerModp, + m_modp)) { + UINFO(9, "iface capture skipping (path mismatch)" << endl); + return; } - - // Search for typedef with same name in the connected interface clone - for (AstNode* stmtp = pinIfacep->stmtsp(); stmtp; stmtp = stmtp->nextp()) { - if (AstTypedef* const tdp = VN_CAST(stmtp, Typedef)) { - if (tdp->name() == typedefName) { - targetTypedefp = tdp; - UINFO(8, - " [iface-capture] found '" - << typedefName << "' in " << pinIfacep->name() - << " via port " - << (entry.ifacePortVarp ? entry.ifacePortVarp->name() - : "") - << endl); - break; - } - } - } - if (targetTypedefp) break; } - - // Fallback to clone of original typedef (existing behavior) - if (!targetTypedefp) targetTypedefp = origTypedefp->clonep(); - - if (targetTypedefp) { - UINFO(8, " [iface-capture] replaceTypedef " - << origTypedefp->name() << " -> " << targetTypedefp << endl); - V3LinkDotIfaceCapture::replaceTypedef(entry.refp, targetTypedefp); - } - // Propagate to cloned RefDType in new module + // Register clone entry in ledger (no AST mutation). if (AstRefDType* const clonedRefp = entry.refp->clonep()) { - V3LinkDotIfaceCapture::propagateClone(entry.refp, clonedRefp); + const string cloneCP = cloneCellp ? cloneCellp->name() : string{}; + const V3LinkDotIfaceCapture::TemplateKey tkey{ + entry.ownerModp ? entry.ownerModp->name() : "", entry.refp->name(), + entry.cellPath}; + V3LinkDotIfaceCapture::propagateClone(tkey, clonedRefp, cloneCP); + } else if (entry.ownerModp != srcModp) { + // REFDTYPE lives in a parent module; clonep() is null. + AstNodeModule* const actualOwnerp + = V3LinkDotIfaceCapture::findOwnerModule(entry.refp); + if (actualOwnerp && actualOwnerp->hasGParam()) return; + // Owner won't be cloned - directly retarget now. + if (retargetRefToModule(entry, newModp)) { + UINFO(9, "iface capture direct retarget: " << entry.refp << " -> " + << newModp->prettyNameQ() + << endl); + } } }); + + // Second pass: retarget clone entries (non-empty cloneCellPath) + // whose typedef owner matches the module being cloned. + const string srcName = srcModp->name(); + V3LinkDotIfaceCapture::forEach([&](const V3LinkDotIfaceCapture::CapturedEntry& entry) { + if (!entry.refp || entry.cloneCellPath.empty()) return; + if (entry.typedefOwnerModName != srcName) return; + AstNodeModule* const actualOwnerp + = V3LinkDotIfaceCapture::findOwnerModule(entry.refp); + if (!actualOwnerp || actualOwnerp->hasGParam()) return; + if (cloneCellp && !entry.cellPath.empty() + && !cellPathMatchesClone(entry.cellPath, cloneCellp, actualOwnerp, m_modp)) { + return; + } + if (retargetRefToModule(entry, newModp)) { + UINFO(9, "iface capture clone-entry retarget: " + << entry.refp << " -> " << newModp->prettyNameQ() << endl); + } + }); } newModp->name(newname); @@ -877,6 +1105,11 @@ class ParamProcessor final { // to find the correct interface for each VarXRef. if (!ifaceRefRefs.empty()) { VarXRefRelinkVisitor{newModp}; } + // Fix cross-module REFDTYPE pointers in newModp (Phase A path-based + // + Phase B reachable-set fallback). + UASSERT_OBJ(newModp, srcModp, "newModp null before hierarchy fixup"); + fixupCrossModuleRefDTypes(newModp, srcModp, ifErrorp, ifaceRefRefs); + // Assign parameters to the constants specified // DOES clone() so must be finished with module clonep() before here for (AstPin* pinp = paramsp; pinp; pinp = VN_AS(pinp->nextp(), Pin)) { @@ -886,7 +1119,7 @@ class ParamProcessor final { AstConst* const exprp = VN_CAST(newp, Const); AstConst* const origp = VN_CAST(modvarp->valuep(), Const); const bool overridden - = !(origp && ParameterizedHierBlocks::areSame(exprp, origp)); + = !(origp && exprp && ParameterizedHierBlocks::areSame(exprp, origp)); // Remove any existing parameter if (modvarp->valuep()) modvarp->valuep()->unlinkFrBack()->deleteTree(); // Set this parameter to value requested by cell @@ -904,6 +1137,7 @@ class ParamProcessor final { } } } + return true; } const ModInfo* moduleFindOrClone(AstNodeModule* srcModp, AstNode* ifErrorp, AstPin* paramsp, @@ -993,7 +1227,9 @@ class ParamProcessor final { += "_" + paramSmallName(srcModp, modvarp) + paramValueNumber(pinp->exprp()); any_overridesr = true; } else { + UINFO(9, "cellPinCleanup: before constify " << pinp << " " << modvarp); V3Const::constifyParamsEdit(pinp->exprp()); + UINFO(9, "cellPinCleanup: after constify " << pinp); // String constants are parsed as logic arrays and converted to strings in V3Const. // At this moment, some constants may have been already converted. // To correctly compare constants, both should be of the same type, @@ -1005,6 +1241,9 @@ class ParamProcessor final { AstConst* const exprp = VN_CAST(pinp->exprp(), Const); AstConst* const origp = VN_CAST(modvarp->valuep(), Const); if (!exprp) { + // With DepGraph architecture, all expressions should be constants + // by the time V3Param runs. If not, it's an error. + UINFO(9, "cellPinCleanup: NOT CONST after constify " << pinp); UINFOTREE(1, pinp, "", "errnode"); pinp->v3error("Can't convert defparam value to constant: Param " << pinp->prettyNameQ() << " of " << nodep->prettyNameQ()); @@ -1019,6 +1258,7 @@ class ParamProcessor final { // Setting parameter to its default value. Just ignore it. // This prevents making additional modules, and makes coverage more // obvious as it won't show up under a unique module page name. + UINFO(9, "cellPinCleanup: same as default " << pinp); } else if (exprp->num().isDouble() || exprp->num().isString() || exprp->num().isFourState() || exprp->num().width() != 32) { longnamer @@ -1036,9 +1276,37 @@ class ParamProcessor final { resolveDotToTypedef(pinp->exprp()); AstNodeDType* rawTypep = VN_CAST(pinp->exprp(), NodeDType); - if (rawTypep) V3Width::widthParamsEdit(rawTypep); + // Guard against widthing a struct/union still owned by a + // parameterized template interface (not yet specialized). + // widthParamsEdit is destructive: it evaluates range expressions, + // sets didWidth=1, and removes Range nodes, which would corrupt + // the template's BASICDTYPEs for all subsequent clones. + // Triggered by deeply nested parameterized interfaces (e.g. + // outer_if containing inner_if with struct typedefs) when the + // inner interface hasn't been specialized yet. + bool skipWidthForTemplateStruct = false; + { + // Use non-asserting skip: before widthParamsEdit, type(expr) + // constructs may contain unlinked REFDTYPEs (e.g. type(x-y)) + AstNodeDType* const resolvedp = rawTypep ? rawTypep->skipRefOrNullp() : nullptr; + if (resolvedp && (VN_IS(resolvedp, StructDType) || VN_IS(resolvedp, UnionDType))) { + AstNodeModule* const ownerModp + = V3LinkDotIfaceCapture::findOwnerModule(resolvedp); + // Skip if owned by a parameterized template (not yet specialized) + if (ownerModp && ownerModp->parameterizedTemplate()) { + skipWidthForTemplateStruct = true; + V3Stats::addStatSum("Param, Template struct width skips", 1); + UINFO(9, "SKIP-WIDTH-TEMPLATE: struct=" + << resolvedp->prettyTypeName() << " templateOwner=" + << ownerModp->prettyNameQ() << " pin=" << pinp->prettyNameQ() + << " of " << nodep->prettyNameQ() + << " srcMod=" << srcModp->prettyNameQ() << endl); + } + } + if (rawTypep && !skipWidthForTemplateStruct) V3Width::widthParamsEdit(rawTypep); + } AstNodeDType* exprp = rawTypep ? rawTypep->skipRefToNonRefp() : nullptr; - const AstNodeDType* const origp = modvarp->skipRefToNonRefp(); + const AstNodeDType* origp = modvarp->skipRefToNonRefp(); if (!exprp) { pinp->v3error("Parameter type pin value isn't a type: Param " << pinp->prettyNameQ() << " of " << nodep->prettyNameQ()); @@ -1047,10 +1315,12 @@ class ParamProcessor final { << modvarp->prettyNameQ()); } else { UINFO(9, "Parameter type assignment expr=" << exprp << " to " << origp); - V3Const::constifyParamsEdit(pinp->exprp()); // Reconcile typedefs - // Constify may have caused pinp->exprp to change - rawTypep = VN_AS(pinp->exprp(), NodeDType); - exprp = rawTypep->skipRefToNonRefp(); + if (!skipWidthForTemplateStruct) { + V3Const::constifyParamsEdit(pinp->exprp()); // Reconcile typedefs + // Constify may have caused pinp->exprp to change + rawTypep = VN_AS(pinp->exprp(), NodeDType); + exprp = rawTypep->skipRefToNonRefp(); + } if (!modvarp->fwdType().isNodeCompatible(exprp)) { pinp->v3error("Parameter type expression type " << exprp->prettyDTypeNameQ() @@ -1062,9 +1332,11 @@ class ParamProcessor final { // This prevents making additional modules, and makes coverage more // obvious as it won't show up under a unique module page name. } else { - VL_DO_DANGLING(V3Const::constifyParamsEdit(exprp), exprp); - rawTypep = VN_CAST(pinp->exprp(), NodeDType); - exprp = rawTypep ? rawTypep->skipRefToNonRefp() : nullptr; + if (!skipWidthForTemplateStruct) { + VL_DO_DANGLING(V3Const::constifyParamsEdit(exprp), exprp); + rawTypep = VN_CAST(pinp->exprp(), NodeDType); + exprp = rawTypep ? rawTypep->skipRefToNonRefp() : nullptr; + } longnamer += "_" + paramSmallName(srcModp, modvarp) + paramValueNumber(exprp); any_overridesr = true; } @@ -1302,7 +1574,6 @@ class ParamProcessor final { // Returns new or reused module // Make sure constification worked // Must be a separate loop, as constant conversion may have changed some pointers. - // UINFOTREE(1, nodep, "", "cel2"); string longname = srcModp->name() + "_"; if (debug() >= 9 && paramsp) paramsp->dumpTreeAndNext(cout, "- cellparams: "); @@ -1332,6 +1603,9 @@ class ParamProcessor final { } } + UINFO(9, "nodeDeparamCommon: " << srcModp->prettyNameQ() << " overrides=" << any_overrides + << endl); + AstNodeModule* newModp = nullptr; if (m_hierBlocks.hierSubRun() && m_hierBlocks.isHierBlock(srcModp->origName())) { AstNodeModule* const paramedModp @@ -1343,6 +1617,7 @@ class ParamProcessor final { newModp = paramedModp; // any_overrides = true; // Unused later, so not needed } else if (!any_overrides) { + UINFO(9, "nodeDeparamCommon: no overrides, reusing " << srcModp); UINFO(8, "Cell parameters all match original values, skipping expansion."); // If it's the first use of the default instance, create a copy and store it in user3p. // user3p will also be used to check if the default instance is used. @@ -1369,10 +1644,8 @@ class ParamProcessor final { } const bool cloned = (newModp != srcModp); - UINFO(9, "iface capture module clone src=" << srcModp << " new=" << newModp << " name=" - << newModp->name() << " from cell=" << nodep - << " cellName=" << nodep->name() - << " cloned=" << cloned); + UINFO(9, "nodeDeparamCommon result: " << newModp->prettyNameQ() << " cloned=" << cloned + << endl); // Link source class to its specialized version for later relinking of method references if (defaultsResolved) srcModp->user4p(newModp); @@ -1412,6 +1685,56 @@ class ParamProcessor final { return newModp; } AstNodeModule* ifaceRefDeparam(AstIfaceRefDType* const nodep, AstNodeModule* srcModp) { + // Check for self-reference pattern: typedef iface#(T) this_type inside interface iface + // When processing inside a specialized interface, the IfaceRefDType should point to + // the owner interface, not create an intermediate specialization. + if (m_modp && VN_IS(m_modp, Iface)) { + AstIface* ownerIfacep = const_cast(VN_AS(m_modp, Iface)); + const string ownerOrigName + = ownerIfacep->origName().empty() ? ownerIfacep->name() : ownerIfacep->origName(); + const string srcOrigName + = srcModp->origName().empty() ? srcModp->name() : srcModp->origName(); + string ownerBaseName = ownerOrigName; + const size_t ownerPos = ownerBaseName.find("__"); + if (ownerPos != string::npos) ownerBaseName = ownerBaseName.substr(0, ownerPos); + string srcBaseName = srcOrigName; + const size_t srcPos = srcBaseName.find("__"); + if (srcPos != string::npos) srcBaseName = srcBaseName.substr(0, srcPos); + + if (ownerBaseName == srcBaseName) { + bool allOwnParams = true; + for (AstPin* pinp = nodep->paramsp(); pinp && allOwnParams; + pinp = VN_AS(pinp->nextp(), Pin)) { + if (AstRefDType* const refp = VN_CAST(pinp->exprp(), RefDType)) { + if (AstParamTypeDType* const ptdp + = VN_CAST(refp->refDTypep(), ParamTypeDType)) { + AstNodeModule* const ptdOwnerp + = V3LinkDotIfaceCapture::findOwnerModule(ptdp); + if (ptdOwnerp != m_modp) allOwnParams = false; + } else { + pinp->v3error( // LCOV_EXCL_LINE + "Self-referencing interface typedef " + "parameter is not a type parameter of " + "the enclosing interface"); + } + } else { + pinp->v3error( // LCOV_EXCL_LINE + "Self-referencing interface typedef " + "parameter is not a type reference"); + } + } + if (allOwnParams) { + UINFO(5, "ifaceRefDeparam: self-reference pattern detected in " + << ownerIfacep->prettyNameQ() << ", using owner interface" + << endl); + V3Stats::addStatSum("Param, Self-reference iface typedefs", 1); + nodep->ifacep(ownerIfacep); + if (nodep->paramsp()) nodep->paramsp()->unlinkFrBackWithNext()->deleteTree(); + return ownerIfacep; + } + } + } + AstNodeModule* const newModp = nodeDeparamCommon(nodep, srcModp, nodep->paramsp(), nullptr, false); if (!newModp) return nullptr; @@ -1461,13 +1784,35 @@ public: // We always run this, even if no parameters, as need to look for interfaces, // and remove any recursive references UINFO(4, "De-parameterize: " << nodep); + UINFO(9, "nodeDeparam ENTER node=<" + << AstNode::nodeAddr(nodep) << ">" << " type=" << nodep->typeName() + << " srcMod=" << (srcModp ? srcModp->prettyNameQ() : "''") + << " srcSomeInstanceName='" + << (srcModp ? srcModp->someInstanceName() : string("")) << "'" + << " parentMod=" << (modp ? modp->prettyNameQ() : "''") + << " parentSomeInstanceName='" + << (modp ? modp->someInstanceName() : string("")) << "'" + << " inputSomeInstanceName='" << someInstanceName << "'" << endl); // Create new module name with _'s between the constants UINFOTREE(10, nodep, "", "cell"); // Evaluate all module constants V3Const::constifyParamsEdit(nodep); // Set name for warnings for when we param propagate the module - const string instanceName = someInstanceName + "." + nodep->name(); + // For AstIfaceRefDType, name() returns the modport name (often empty), + // so use cellName() which is the actual cell instance name. + // If both are empty (interface port, not a cell), skip appending + // to avoid double-dots in the path. + string nodeName = nodep->name(); + if (AstIfaceRefDType* const ifaceRefp = VN_CAST(nodep, IfaceRefDType)) { + if (nodeName.empty()) nodeName = ifaceRefp->cellName(); + } + const string instanceName + = nodeName.empty() ? someInstanceName : (someInstanceName + "." + nodeName); srcModp->someInstanceName(instanceName); + UINFO(9, "nodeDeparam SET-SRC-INST srcMod=" + << srcModp->prettyNameQ() << " someInstanceName='" + << srcModp->someInstanceName() << "'" << " node=<" << AstNode::nodeAddr(nodep) + << ">" << " nodeType=" << nodep->typeName() << endl); AstNodeModule* newModp = nullptr; if (AstCell* const cellp = VN_CAST(nodep, Cell)) { @@ -1487,6 +1832,15 @@ public: // Set name for later warnings newModp->someInstanceName(instanceName); + UINFO(9, "nodeDeparam EXIT node=<" + << AstNode::nodeAddr(nodep) << ">" << " type=" << nodep->typeName() + << " srcMod=" << (srcModp ? srcModp->prettyNameQ() : "''") + << " srcSomeInstanceName='" + << (srcModp ? srcModp->someInstanceName() : string("")) << "'" + << " newMod=" << (newModp ? newModp->prettyNameQ() : "''") + << " newSomeInstanceName='" + << (newModp ? newModp->someInstanceName() : string("")) << "'" << endl); + UINFO(8, " Done with orig " << nodep); // if (debug() >= 10) // v3Global.rootp()->dumpTreeFile(v3Global.debugFilename("param-out.tree")); @@ -1592,6 +1946,12 @@ class ParamVisitor final : public VNVisitor { // Process once; note user2 will be cleared on specialization, so we will do the // specialized module if needed if (!modp->user2SetOnce()) { + UINFO(9, "processWorkQ module begin mod='" + << modp->name() << "' orig='" << modp->origName() << "'" + << " someInstanceName='" << modp->someInstanceName() << "'" + << " hasGParam=" << (modp->hasGParam() ? "yes" : "no") + << " user3p=" << (modp->user3p() ? "set" : "null") + << " dead=" << (modp->dead() ? "yes" : "no") << endl); // TODO: this really should be an assert, but classes and hier_blocks are // special... @@ -1643,6 +2003,10 @@ class ParamVisitor final : public VNVisitor { if (AstNodeModule* const newModp = m_processor.nodeDeparam(cellp, srcModp, modp, someInstanceName)) { + if (VN_IS(srcModp, Iface)) { + logTemplateLeakRefs(modp, srcModp, "after queued nodeDeparam", cellp); + } + // Add the (now potentially specialized) child module to the work queue workQueue.emplace(newModp->level(), newModp); @@ -1662,18 +2026,105 @@ class ParamVisitor final : public VNVisitor { return dotted.substr(0, dotted.find('.')); } + // Debug-only diagnostic (requires --debugi-V3Param 9). + // Walks parentModp looking for RefDTypes or VarRefs whose typedef, + // refDType, or variable target is still owned by templateModp (the + // unspecialized interface template). Any such "leak" indicates a + // pointer that was not properly redirected to the clone during + // deparameterization. Logs each leak with ancestry for triage. + void logTemplateLeakRefs(AstNodeModule* parentModp, AstNodeModule* templateModp, + const char* stage, AstNode* contextp) { + if (debug() < 9 || !parentModp || !templateModp || !VN_IS(templateModp, Iface)) return; + // LCOV_EXCL_START // Debug-only diagnostic + int leakCount = 0; + const auto ancestryOf = [](const AstNode* nodep) { + string ancestry; + for (const AstNode* curp = nodep; curp; curp = curp->backp()) { + if (!ancestry.empty()) ancestry += "<-"; + ancestry += curp->typeName(); + if (VN_IS(curp, NodeModule) || VN_IS(curp, Netlist) || VN_IS(curp, TypeTable)) { + break; + } + } + return ancestry; + }; + + parentModp->foreach([&](AstRefDType* refp) { + if (refp->typedefp()) { + AstNodeModule* const tdOwnerp + = V3LinkDotIfaceCapture::findOwnerModule(refp->typedefp()); + if (tdOwnerp == templateModp) { + ++leakCount; + UINFO(9, "TEMPLATE-LEAK " + << stage << " parent=" << parentModp->prettyNameQ() + << " template=" << templateModp->prettyNameQ() << " contextType=" + << (contextp ? contextp->typeName() : string("")) + << " contextName=" + << (contextp ? contextp->prettyNameQ() : "''") + << " leak=REFDTYPE typedef owner" << " ref=<" + << AstNode::nodeAddr(refp) << ">" + << " refName=" << refp->prettyNameQ() + << " ancestry=" << ancestryOf(refp) << endl); + } + } + if (refp->refDTypep()) { + AstNodeModule* const rdOwnerp + = V3LinkDotIfaceCapture::findOwnerModule(refp->refDTypep()); + if (rdOwnerp == templateModp) { + ++leakCount; + UINFO(9, "TEMPLATE-LEAK " + << stage << " parent=" << parentModp->prettyNameQ() + << " template=" << templateModp->prettyNameQ() << " contextType=" + << (contextp ? contextp->typeName() : string("")) + << " contextName=" + << (contextp ? contextp->prettyNameQ() : "''") + << " leak=REFDTYPE refDType owner" << " ref=<" + << AstNode::nodeAddr(refp) << ">" + << " refName=" << refp->prettyNameQ() + << " ancestry=" << ancestryOf(refp) << endl); + } + } + }); + + parentModp->foreach([&](AstVarRef* varrefp) { + if (!varrefp->varp()) return; + AstNodeModule* const varOwnerp + = V3LinkDotIfaceCapture::findOwnerModule(varrefp->varp()); + if (varOwnerp != templateModp) return; + ++leakCount; + UINFO(9, "TEMPLATE-LEAK " + << stage << " parent=" << parentModp->prettyNameQ() + << " template=" << templateModp->prettyNameQ() + << " contextType=" << (contextp ? contextp->typeName() : string("")) + << " contextName=" << (contextp ? contextp->prettyNameQ() : "''") + << " leak=VARREF target owner" << " ref=<" << AstNode::nodeAddr(varrefp) + << ">" << " var=" << varrefp->prettyNameQ() + << " ancestry=" << ancestryOf(varrefp) << endl); + }); + + if (leakCount > 0) { + UINFO(9, "TEMPLATE-LEAK summary stage='" + << stage << "' parent=" << parentModp->prettyNameQ() << " template=" + << templateModp->prettyNameQ() << " count=" << leakCount << endl); + } + // LCOV_EXCL_STOP + } + void checkParamNotHier(AstNode* valuep) { if (!valuep) return; valuep->foreachAndNext([&](const AstNodeExpr* exprp) { if (const AstVarXRef* const refp = VN_CAST(exprp, VarXRef)) { // Allow hierarchical ref to interface params through interface/modport ports - bool isIfacePortRef = false; + // or local interface instances + bool isIfaceRef = false; if (refp->varp() && refp->varp()->isIfaceParam()) { const string refname = getRefBaseName(refp); - isIfacePortRef = !refname.empty() && m_ifacePortNames.count(refname); + isIfaceRef + = !refname.empty() + && (m_ifacePortNames.count(refname) || m_ifaceInstNames.count(refname)); } - if (!isIfacePortRef) { + if (!isIfaceRef) { refp->v3warn(HIERPARAM, "Parameter values cannot use hierarchical values" " (IEEE 1800-2023 6.20.2)"); } @@ -1737,13 +2188,12 @@ class ParamVisitor final : public VNVisitor { AstCell* const cellp = VN_CAST(nodep, Cell); if (!cellParamsReferenceIfacePorts(cellp)) { AstNodeModule* const srcModp = cellp->modp(); - if (AstNodeModule* const newModp = m_processor.nodeDeparam( - cellp, srcModp, m_modp, m_modp->someInstanceName())) { - // For specialized interfaces, recursively process nested interface cells. - // This ensures nested interfaces are already specialized when modules - // using the interface are processed (parameter passthrough fix). - if (newModp != srcModp) specializeNestedIfaceCells(newModp); - } + // DISABLED: specializeNestedIfaceCells causes early nested + // iface specialization where PARAMTYPEDTYPE child REFDTYPEs + // point to template structs instead of clone structs, + // destructively widthing the template with default (zero) + // values. See t_interface_nested_struct_param.v. + m_processor.nodeDeparam(cellp, srcModp, m_modp, m_modp->someInstanceName()); } } @@ -1756,6 +2206,12 @@ class ParamVisitor final : public VNVisitor { void visit(AstNodeModule* nodep) override { if (nodep->recursiveClone()) nodep->dead(true); // Fake, made for recursive elimination if (nodep->dead()) return; // Marked by LinkDot (and above) + UINFO(9, "V3Param: visit module name=" + << nodep->prettyNameQ() << " orig='" << nodep->origName() + << "' someInstanceName='" << nodep->someInstanceName() + << "' hasGParam=" << (nodep->hasGParam() ? "yes" : "no") + << " user3p=" << (nodep->user3p() ? "set" : "null") + << " dead=" << (nodep->dead() ? "yes" : "no") << endl); if (AstClass* const classp = VN_CAST(nodep, Class)) { if (classp->hasGParam()) { // Don't enter into a definition. @@ -1836,8 +2292,22 @@ class ParamVisitor final : public VNVisitor { if (!nodep->valuep() && !VN_IS(m_modp, Class)) { nodep->v3error("Parameter without default value is never given value" << " (IEEE 1800-2023 6.20.1): " << nodep->prettyNameQ()); - } else { - V3Const::constifyParamsEdit(nodep); // The variable, not just the var->init() + } else if (nodep->valuep()) { + // In visit(AstVar*) for localparams, check if expression contains VARXREF + // to another localparam (not parameter). Parameters are already const, + // but localparams may not be evaluated yet. + bool hasVarXRefToLparam = false; + nodep->valuep()->foreach([&](const AstVarXRef* xrefp) { + if (xrefp->varp() && xrefp->varp()->varType() == VVarType::LPARAM) { + hasVarXRefToLparam = true; + } + }); + if (hasVarXRefToLparam) { + // Don't constify - let it be evaluated later + return; + } + + V3Const::constifyParamsEdit(nodep); } } } @@ -2127,6 +2597,7 @@ public: explicit ParamVisitor(ParamState& state, AstNetlist* netlistp) : m_state{state} , m_processor{netlistp} { + // Relies on modules already being in top-down-order iterate(netlistp); } @@ -2214,22 +2685,28 @@ public: netlistp->foreach([](AstNodeFTaskRef* ftaskrefp) { AstNodeFTask* ftaskp = ftaskrefp->taskp(); if (!ftaskp || !ftaskp->classMethod()) return; - const string funcName = ftaskp->name(); - for (AstNode* backp = ftaskrefp->backp(); backp; backp = backp->backp()) { - if (VN_IS(backp, Class)) { - if (backp == ftaskrefp->classOrPackagep()) - return; // task is in the same class as reference - break; + string funcName = ftaskp->name(); + // Find the nearest containing (ancestor) class for a node. + // Uses aboveLoopp() which correctly skips sibling links + // (e.g. covergroup classes) to find the true parent. + const auto ancestorClassOf = [](AstNode* startp) -> AstClass* { + for (AstNode* np = startp->aboveLoopp(); np; np = np->aboveLoopp()) { + if (AstClass* const cp = VN_CAST(np, Class)) return cp; } - } - AstClass* classp = nullptr; - for (AstNode* backp = ftaskp->backp(); backp; backp = backp->backp()) { - if (VN_IS(backp, Class)) { - classp = VN_AS(backp, Class); - break; - } - } + return nullptr; + }; + AstClass* refClassp = ancestorClassOf(ftaskrefp); + if (refClassp == ftaskrefp->classOrPackagep()) + return; // task is in the same class as reference + AstClass* classp = ancestorClassOf(ftaskp); UASSERT_OBJ(classp, ftaskrefp, "Class method has no class above it"); + // If the FUNCREF and its task are both in the same (clone) class but + // classOrPackagep still points to the old template, just retarget it + if (refClassp && refClassp == classp && ftaskrefp->classOrPackagep() + && ftaskrefp->classOrPackagep() != refClassp) { + ftaskrefp->classOrPackagep(refClassp); + return; + } if (classp->user3p()) return; // will not get removed, no need to relink AstClass* const parametrizedClassp = VN_CAST(classp->user4p(), Class); if (!parametrizedClassp) return; @@ -2280,11 +2757,11 @@ public: } // Set all links pointing to a user3 (deleting) node as null netlistp->foreach([](AstNode* const nodep) { - nodep->foreachLink([&](AstNode** const linkpp, const char*) { + nodep->foreachLink([&](AstNode** const linkpp, const char* namep) { if (*linkpp && (*linkpp)->user3()) { - UINFO(9, "clear link " << nodep); + UINFO(9, "clear link " << namep << " on " << nodep); *linkpp = nullptr; - UINFO(9, "cleared link " << nodep); + UINFO(9, "cleared link " << namep << " on " << nodep); } }); }); @@ -2298,6 +2775,11 @@ public: void V3Param::param(AstNetlist* rootp) { UINFO(2, __FUNCTION__ << ":"); - { ParamTop{rootp}; } // Destruct before checking + + if (dumpTreeEitherLevel() >= 9) V3LinkDotIfaceCapture::dumpEntries("before V3Param"); + { ParamTop{rootp}; } + V3LinkDotIfaceCapture::purgeStaleRefs(); + if (dumpTreeEitherLevel() >= 9) V3LinkDotIfaceCapture::dumpEntries("after V3Param"); + V3Global::dumpCheckGlobalTree("param", 0, dumpTreeEitherLevel() >= 3); } diff --git a/src/V3Width.cpp b/src/V3Width.cpp index aa6be6838..c96dcc561 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -72,6 +72,7 @@ #include "V3Const.h" #include "V3Error.h" #include "V3Global.h" +#include "V3LinkDotIfaceCapture.h" #include "V3LinkLValue.h" #include "V3MemberMap.h" #include "V3Number.h" @@ -270,6 +271,13 @@ class WidthVisitor final : public VNVisitor { nodep->findLogicDType(unpackBits, unpackMinBits, VSigning::UNSIGNED)}); } } + // When fromp() is a DType (e.g. unlinked RefDType), resolve through + // the ref chain; when it's an expression, dtypep() is already resolved. + static AstNodeDType* fromDTypep(AstNode* fromp) { + if (AstNodeDType* const dtypep = VN_CAST(fromp, NodeDType)) + return dtypep->skipRefOrNullp(); + return fromp ? fromp->dtypep() : nullptr; + } // VISITORS // Naming: width_O{outputtype}_L{lhstype}_R{rhstype}_W{widthing}_S{signing} // Where type: @@ -1006,8 +1014,17 @@ class WidthVisitor final : public VNVisitor { << std::hex << width << std::dec); } // Note width() not set on range; use elementsConst() + const bool inDeadModule = m_modep && m_modep->dead(); + // Suppress ASCRANGE in parameterized template modules whose parameter-dependent + // ranges haven't been resolved yet, or when the type has no owning module + // (e.g. moved to TypeTable during DepGraph resolution). + const bool inParameterizedTemplate + = m_modep && (m_modep->dead() || m_modep->parameterizedTemplate()); + const bool inTypeTable = !m_modep; if (nodep->ascending() && !VN_IS(nodep->backp(), UnpackArrayDType) - && !VN_IS(nodep->backp(), Cell)) { // For cells we warn in V3Inst + && !VN_IS(nodep->backp(), Cell) // For cells we warn in V3Inst + && !m_paramsOnly // Skip during parameter evaluation + && !inDeadModule && !inParameterizedTemplate && !inTypeTable) { nodep->v3warn(ASCRANGE, "Ascending bit range vector: left < right of bit range: [" << nodep->leftConst() << ":" << nodep->rightConst() << "]"); @@ -1035,6 +1052,11 @@ class WidthVisitor final : public VNVisitor { } UASSERT_OBJ(nodep->dtypep(), nodep, "dtype wasn't set"); // by V3WidthSel + // Suppress SELRANGE in parameterized template modules where + // parameter-dependent widths haven't been resolved yet. + const bool inParameterizedTemplate + = m_modep && (m_modep->dead() || m_modep->parameterizedTemplate()); + if (VN_IS(nodep->lsbp(), Const) && nodep->msbConst() < nodep->lsbConst()) { // Likely impossible given above width check nodep->v3warn(E_UNSUPPORTED, @@ -1047,7 +1069,7 @@ class WidthVisitor final : public VNVisitor { nodep->lsbp()->replaceWith(new AstConst{nodep->lsbp()->fileline(), 0}); } // We're extracting, so just make sure the expression is at least wide enough. - if (nodep->fromp()->width() < width) { + if (nodep->fromp()->width() < width && !inParameterizedTemplate) { nodep->v3warn(SELRANGE, "Extracting " << width << " bits from only " << nodep->fromp()->width() << " bit number"); // Extend it. @@ -1102,7 +1124,7 @@ class WidthVisitor final : public VNVisitor { AstNodeVarRef* lrefp = AstNodeVarRef::varRefLValueRecurse(nodep); if (m_doGenerate) { UINFO(5, "Selection index out of range inside generate"); - } else { + } else if (!inParameterizedTemplate) { nodep->v3warn(SELRANGE, "Selection index out of range: " << nodep->msbConst() << ":" << nodep->lsbConst() << " outside " << frommsb << ":" << fromlsb); @@ -1196,11 +1218,13 @@ class WidthVisitor final : public VNVisitor { if (VN_IS(nodep->bitp(), Const) && (VN_AS(nodep->bitp(), Const)->toSInt() > (frommsb - fromlsb) || VN_AS(nodep->bitp(), Const)->toSInt() < 0)) { - nodep->v3warn(SELRANGE, - "Selection index out of range: " - << (VN_AS(nodep->bitp(), Const)->toSInt() + fromlsb) - << " outside " << frommsb << ":" << fromlsb); - UINFO(1, " Related node: " << nodep); + // Suppress in dead/parameterized template modules + if (!(m_modep && (m_modep->dead() || m_modep->parameterizedTemplate()))) { + nodep->v3warn(SELRANGE, + "Selection index out of range: " + << (VN_AS(nodep->bitp(), Const)->toSInt() + fromlsb) + << " outside " << frommsb << ":" << fromlsb); + } } widthCheckSized(nodep, "Extract Range", nodep->bitp(), selwidthDTypep, EXTEND_EXP, false /*NOWARN*/); @@ -1842,8 +1866,9 @@ class WidthVisitor final : public VNVisitor { switch (nodep->attrType()) { case VAttrType::DIM_DIMENSIONS: case VAttrType::DIM_UNPK_DIMENSIONS: { - UASSERT_OBJ(nodep->fromp() && nodep->fromp()->dtypep(), nodep, "Unsized expression"); - const std::pair dim = nodep->fromp()->dtypep()->dimensions(true); + AstNodeDType* const dtypep = fromDTypep(nodep->fromp()); + UASSERT_OBJ(dtypep, nodep, "Unsized expression"); + const std::pair dim = dtypep->dimensions(true); const int val = (nodep->attrType() == VAttrType::DIM_UNPK_DIMENSIONS ? dim.second : (dim.first + dim.second)); @@ -1872,8 +1897,8 @@ class WidthVisitor final : public VNVisitor { case VAttrType::DIM_LOW: case VAttrType::DIM_RIGHT: case VAttrType::DIM_SIZE: { - UASSERT_OBJ(nodep->fromp() && nodep->fromp()->dtypep(), nodep, "Unsized expression"); - AstNodeDType* const dtypep = nodep->fromp()->dtypep(); + AstNodeDType* const dtypep = fromDTypep(nodep->fromp()); + UASSERT_OBJ(dtypep, nodep, "Unsized expression"); if (VN_IS(dtypep, QueueDType) || VN_IS(dtypep, DynArrayDType)) { switch (nodep->attrType()) { case VAttrType::DIM_SIZE: { @@ -1940,9 +1965,11 @@ class WidthVisitor final : public VNVisitor { nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } else { + AstNodeDType* const baseDTypep = dtypep->skipRefp(); + UASSERT_OBJ(baseDTypep, nodep, "Unsized expression"); const int dim = 1; - AstConst* const newp - = dimensionValue(nodep->fileline(), dtypep, nodep->attrType(), dim); + AstConst* const newp = dimensionValue(nodep->fileline(), baseDTypep, + nodep->attrType(), dim); nodep->replaceWith(newp); VL_DO_DANGLING(nodep->deleteTree(), nodep); } @@ -2033,6 +2060,34 @@ class WidthVisitor final : public VNVisitor { // Only used in CStmts which don't care.... } + void visit(AstCellArrayRef* nodep) override { + if (nodep->didWidthAndSet()) return; + userIterateAndNext(nodep->selp(), WidthVP{SELF, PRELIM}.p()); + userIterateAndNext(nodep->selp(), WidthVP{SELF, FINAL}.p()); + nodep->dtypeSetVoid(); // placeholder; this node shouldnt survive beyond linking + nodep->didWidth(true); + } + + void visit(AstCellRef* nodep) override { + if (nodep->didWidthAndSet()) return; + + if (AstNodeExpr* const cellExprp = VN_CAST(nodep->cellp(), NodeExpr)) { + userIterateAndNext(cellExprp, WidthVP{SELF, PRELIM}.p()); + userIterateAndNext(cellExprp, WidthVP{SELF, FINAL}.p()); + } + + if (AstNodeExpr* const exprp = VN_CAST(nodep->exprp(), NodeExpr)) { + userIterateAndNext(exprp, WidthVP{SELF, PRELIM}.p()); + nodep->dtypeFrom(exprp); + userIterateAndNext(exprp, WidthVP{SELF, FINAL}.p()); + } else { + nodep->v3fatalSrc("CellRef exprp is not a NodeExpr: " // LCOV_EXCL_LINE + << nodep->exprp()->prettyTypeName()); + } + + nodep->didWidth(true); + } + // DTYPES void visit(AstNodeArrayDType* nodep) override { if (nodep->didWidthAndSet()) return; // This node is a dtype & not both PRELIMed+FINALed @@ -2226,6 +2281,16 @@ class WidthVisitor final : public VNVisitor { } // Effectively nodep->dtypeFrom(nodep->dtypeSkipRefp()); // But might be recursive, so instead manually recurse into the referenced type + if (!nodep->subDTypep()) { + // Defer unlinked RefDTypes in parameterized template modules (or types + // with no owning module, e.g. moved to TypeTable) until specialization + // resolves them. + const bool inTemplateModule = !m_modep || m_modep->parameterizedTemplate(); + if (inTemplateModule) { + nodep->doingWidth(false); + return; + } + } UASSERT_OBJ(nodep->subDTypep(), nodep, "Unlinked"); nodep->dtypeFrom(nodep->subDTypep()); nodep->widthFromSub(nodep->subDTypep()); @@ -2251,9 +2316,13 @@ class WidthVisitor final : public VNVisitor { } void visit(AstParamTypeDType* nodep) override { if (nodep->didWidthAndSet()) return; // This node is a dtype & not both PRELIMed+FINALed + nodep->dtypep(iterateEditMoveDTypep(nodep, nodep->subDTypep())); userIterateChildren(nodep, nullptr); nodep->widthFromSub(nodep->subDTypep()); + // Clear childDTypep after dtypep is set to satisfy V3Broken invariant. + // The child dtype has been moved to the type table by iterateEditMoveDTypep. + if (nodep->dtypep() && nodep->childDTypep()) { nodep->childDTypep(nullptr); } } void visit(AstRequireDType* nodep) override { userIterateAndNext(nodep->lhsp(), WidthVP{SELF, BOTH}.p()); @@ -2631,6 +2700,10 @@ class WidthVisitor final : public VNVisitor { const bool implicitParam = nodep->isParam() && bdtypep && bdtypep->implicit(); if (implicitParam) { if (nodep->valuep()) { + // Remove blanket deferral. We must attempt to visit the value to determine + // type/deps. If it remains unresolved, specific node visitors (like AttrOf) should + // handle deferral by setting a placeholder type to prevent "No dtype" errors + // later. userIterateAndNext(nodep->valuep(), WidthVP{nodep->dtypep(), PRELIM}.p()); UINFO(9, "implicitParamPRELIMIV " << nodep->valuep()); // Although nodep will get a different width for parameters @@ -2645,7 +2718,10 @@ class WidthVisitor final : public VNVisitor { VL_DANGLING(bdtypep); } else { int width = 0; - const AstBasicDType* const valueBdtypep = nodep->valuep()->dtypep()->basicp(); + AstNodeDType* const valueDTypep = nodep->valuep()->dtypep(); + UASSERT_OBJ(valueDTypep, nodep->valuep(), + "Null dtype on implicit param value"); + const AstBasicDType* const valueBdtypep = valueDTypep->basicp(); bool issigned = false; if (bdtypep->isNosign()) { if (valueBdtypep && valueBdtypep->isSigned()) issigned = true; @@ -3278,6 +3354,14 @@ class WidthVisitor final : public VNVisitor { const bool isHardPackedUnion = nodep->packed() && VN_IS(nodep, UnionDType) && !VN_CAST(nodep, UnionDType)->isSoft(); + // Suppress union size errors in parameterized template modules where member + // widths depend on unresolved parameters. Also suppress when the type has no + // owning module (e.g. moved to TypeTable during DepGraph resolution). + // TODO: Revisit this gate if DepGraph becomes the sole flow and widthing + // can assume all types are already specialized. + const bool inTemplateModule = (m_modep && m_modep->parameterizedTemplate()) + || (VN_IS(nodep, UnionDType) && !m_modep); + // Determine bit assignments and width if (VN_IS(nodep, UnionDType) || nodep->packed()) { int lsb = 0; @@ -3293,7 +3377,8 @@ class WidthVisitor final : public VNVisitor { itemp->lsb(lsb); if (VN_IS(nodep, UnionDType)) { const int itemWidth = itemp->width(); - if (!first && isHardPackedUnion && itemWidth != width) { + // Skip union size check for template modules with unresolved parameters + if (!first && isHardPackedUnion && itemWidth != width && !inTemplateModule) { itemp->v3error("Hard packed union members must have equal size " "(IEEE 1800-2023 7.3.1)"); } @@ -7899,6 +7984,7 @@ class WidthVisitor final : public VNVisitor { "Under node " << nodep->prettyTypeName() << " has no dtype?? Missing Visitor func?"); if (expDTypep->basicp()->untyped() || nodep->dtypep()->basicp()->untyped()) return false; + // During DepGraph execution, expected width may be 0 if the type hasn't been UASSERT_OBJ(nodep->width() != 0, nodep, "Under node " << nodep->prettyTypeName() << " has no expected width?? Missing Visitor func?"); @@ -8871,6 +8957,7 @@ class WidthVisitor final : public VNVisitor { UASSERT_OBJ(dtnodep->didWidth(), parentp, "iterateEditMoveDTypep didn't get width resolution of " << dtnodep->prettyTypeName()); + // Move to under netlist UINFO(9, "iterateEditMoveDTypep child moving " << dtnodep); dtnodep->unlinkFrBack(); diff --git a/src/Verilator.cpp b/src/Verilator.cpp index f30e13820..1d8c523f8 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -69,6 +69,7 @@ #include "V3LifePost.h" #include "V3LiftExpr.h" #include "V3LinkDot.h" +#include "V3LinkDotIfaceCapture.h" #include "V3LinkInc.h" #include "V3LinkJump.h" #include "V3LinkLValue.h" @@ -178,10 +179,15 @@ static void process() { // This requires some width calculations and constant propagation // No more AstGenCase/AstGenFor/AstGenIf after this V3Param::param(v3Global.rootp()); + V3LinkDot::linkDotParamed(v3Global.rootp()); // Cleanup as made new modules V3LinkLValue::linkLValue(v3Global.rootp()); // Resolve new VarRefs V3Error::abortIfErrors(); + // Fix any remaining cross-interface refs created during V3Width::widthParamsEdit + // that weren't captured earlier. Must run before V3Dead deletes template modules. + V3LinkDotIfaceCapture::finalizeIfaceCapture(); + // Remove any modules that were parameterized and are no longer referenced. V3Dead::deadifyModules(v3Global.rootp()); diff --git a/test_regress/t/t_class_param_extends_static_member_function_access.py b/test_regress/t/t_class_param_extends_static_member_function_access.py new file mode 100755 index 000000000..84b274f68 --- /dev/null +++ b/test_regress/t/t_class_param_extends_static_member_function_access.py @@ -0,0 +1,18 @@ +#!/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: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_class_param_extends_static_member_function_access.v b/test_regress/t/t_class_param_extends_static_member_function_access.v new file mode 100644 index 000000000..4cfc9a1b4 --- /dev/null +++ b/test_regress/t/t_class_param_extends_static_member_function_access.v @@ -0,0 +1,32 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// 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: 2025 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +class Class1 #( + type T +); + static function int get_p(); + return 7; + endfunction +endclass + +class Class2 #( + type T +) extends Class1 #(T); + static function int get_p2; + return T::get_p(); + endfunction +endclass + +module t; + initial begin + typedef Class2#(Class1#(int)) Class; + if (Class::get_p2() != Class1#(int)::get_p()) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_self_ref_typedef.py b/test_regress/t/t_iface_self_ref_typedef.py new file mode 100755 index 000000000..bc0edcf81 --- /dev/null +++ b/test_regress/t/t_iface_self_ref_typedef.py @@ -0,0 +1,24 @@ +#!/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 by Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +# Verifies that the self-referential interface typedef pattern +# (typedef iface#(T) self_t inside interface iface) is detected +# and handled correctly during deparameterization. + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(v_flags2=["--binary --stats"]) + +test.file_grep(test.stats, r'Param, Self-reference iface typedefs\s+(\d+)', 1) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_iface_self_ref_typedef.v b/test_regress/t/t_iface_self_ref_typedef.v new file mode 100644 index 000000000..298f795a1 --- /dev/null +++ b/test_regress/t/t_iface_self_ref_typedef.v @@ -0,0 +1,24 @@ +// DESCRIPTION: Verilator: Test for self-referential interface typedef +// +// 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 + +// Self-referential typedef: typedef iface#(T) this_type inside interface iface +interface my_iface #(type T = logic); + typedef my_iface #(T) self_t; + T data; +endinterface + +module t (); + my_iface #(logic [7:0]) if0 (); + + initial begin + if0.data = 8'hAB; + if ($bits(if0.data) != 8) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_typedef_scale.py b/test_regress/t/t_iface_typedef_scale.py new file mode 100755 index 000000000..3d1724e3e --- /dev/null +++ b/test_regress/t/t_iface_typedef_scale.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Stress test for interface typedef scaling. Generates an interface with +# many typedefs and two parameterised instances to exercise the +# findTypedefInModule / findDTypeInModule caching path. +# +# 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 time + +import vltest_bootstrap + +test.scenarios('vlt') +test.top_filename = test.obj_dir + "/t_iface_typedef_scale.sv" + +N_TYPEDEFS = 5000 +MAX_COMPILE_SECS = 10 # Generous budget; catches O(N^2) explosion where 5k typedefs would take minutes without the cache + + +def gen(filename, n): + with open(filename, 'w', encoding="utf8") as fh: + fh.write("// Generated by t_iface_typedef_scale.py\n") + fh.write("// Stress test: interface with {} typedefs, two instances\n".format(n)) + fh.write("\n") + # Interface with N typedefs + fh.write("interface iface_many_types #(parameter int CFG = 8);\n") + for i in range(n): + fh.write(" typedef logic [CFG-1:0] td_{};\n".format(i)) + fh.write("endinterface\n\n") + # Module that uses the interface and references typedefs via the port + fh.write("module sub (\n") + fh.write(" iface_many_types ifc,\n") + fh.write(" input logic clk\n") + fh.write(");\n") + fh.write(" typedef ifc.td_0 local_td_0;\n") + fh.write(" typedef ifc.td_{} local_td_n;\n".format(n - 1)) + fh.write(" local_td_0 r0;\n") + fh.write(" local_td_n rn;\n") + fh.write(" always @(posedge clk) begin\n") + fh.write(" r0 <= '0;\n") + fh.write(" rn <= '0;\n") + fh.write(" end\n") + fh.write("endmodule\n\n") + # Top module with two instances using different parameters + fh.write("module t (input logic clk);\n") + fh.write(" iface_many_types #(.CFG(16)) ifc_a();\n") + fh.write(" iface_many_types #(.CFG(32)) ifc_b();\n") + fh.write(" sub sub_a (.ifc(ifc_a), .clk(clk));\n") + fh.write(" sub sub_b (.ifc(ifc_b), .clk(clk));\n") + fh.write(" initial begin\n") + fh.write(' $write("*-* All Finished *-*\\n");\n') + fh.write(" $finish;\n") + fh.write(" end\n") + fh.write("endmodule\n") + + +gen(test.top_filename, N_TYPEDEFS) + +test.timeout(MAX_COMPILE_SECS) + +t0 = time.time() +test.compile(verilator_flags2=["-x-assign fast --x-initial fast"]) +elapsed = time.time() - t0 + +print("t_iface_typedef_scale: {} typedefs compiled in {:.3f}s (limit {:.1f}s)".format( + N_TYPEDEFS, elapsed, MAX_COMPILE_SECS)) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_iface_typedef_struct_member.py b/test_regress/t/t_iface_typedef_struct_member.py new file mode 100755 index 000000000..95e4478dd --- /dev/null +++ b/test_regress/t/t_iface_typedef_struct_member.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Test for MemberDType fixup in type table +# +# 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: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') +test.top_filename = "t/t_iface_typedef_struct_member.v" + +test.compile(v_flags2=["--binary"]) +test.execute() + +test.passes() diff --git a/test_regress/t/t_iface_typedef_struct_member.v b/test_regress/t/t_iface_typedef_struct_member.v new file mode 100644 index 000000000..9c7fcb3c0 --- /dev/null +++ b/test_regress/t/t_iface_typedef_struct_member.v @@ -0,0 +1,80 @@ +// DESCRIPTION: Verilator: Test for MemberDType fixup and fixDeadRefs in type table (coverage) +// +// Parameterized interface with struct/union typedefs, instantiated in two +// modules. The sub-module uses $bits() on a typedef, which forces widthing +// of the template's type chain during V3Param. This moves template RefDTypes +// into the global type table before the template dies, exercising +// fixDeadRefsInTypeTable and the MemberDType fixup in V3LinkDotIfaceCapture. +// +// 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: 2025 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package cfg_pkg; + typedef struct packed { + int unsigned NumUnits; + int unsigned LineSize; + } cfg_t; +endpackage + +// Parameterized interface with nested struct/union typedefs. +// The struct-in-union pattern triggers MemberDType fixup (line 1056). +// The $bits() usage in sub_mod forces widthing during V3Param, moving +// template RefDTypes into the type table before the template dies. +interface types_if #(parameter cfg_pkg::cfg_t cfg = 0)(); + typedef logic [$clog2(cfg.NumUnits)-1:0] idx_t; + + typedef struct packed { + logic [31:$clog2(cfg.LineSize)] tag; + logic [$clog2(cfg.LineSize)-1:0] offset; + } addr_t; + + typedef struct packed { + logic [3:0] cmd; + union packed { + addr_t addr; // struct-in-union: MemberDType trigger + logic [31:0] raw; + } payload; + } req_t; +endinterface + +module sub_mod #(parameter cfg_pkg::cfg_t cfg = 0)(); + types_if #(cfg) types(); + typedef types.idx_t idx_t; + typedef types.req_t req_t; + typedef types.addr_t addr_t; + + localparam int ReqWidth = $bits(req_t); + + idx_t s_idx; + req_t s_req; + addr_t s_addr; +endmodule + +module t; + localparam cfg_pkg::cfg_t CFG = '{NumUnits: 5, LineSize: 32}; + + types_if #(CFG) types(); + typedef types.req_t req_t; + req_t top_req; + + sub_mod #(.cfg(CFG)) sub(); + + initial begin + #1; + `checkd($bits(top_req), 36); + `checkd($bits(sub.s_req), 36); + `checkd($bits(sub.s_idx), 3); + `checkd($bits(sub.s_addr), 32); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_typedef_wrong_clone.py b/test_regress/t/t_iface_typedef_wrong_clone.py new file mode 100755 index 000000000..44d191244 --- /dev/null +++ b/test_regress/t/t_iface_typedef_wrong_clone.py @@ -0,0 +1,18 @@ +#!/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.compile(v_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_iface_typedef_wrong_clone.v b/test_regress/t/t_iface_typedef_wrong_clone.v new file mode 100644 index 000000000..2b79650b3 --- /dev/null +++ b/test_regress/t/t_iface_typedef_wrong_clone.v @@ -0,0 +1,85 @@ +// DESCRIPTION: Verilator: Test for structural disambiguation and wrong-clone +// fixup in V3LinkDotIfaceCapture. +// +// Modeled after Aerial's simple_cache_if pattern: +// - A parameterized interface (sc_if) contains a nested sub-interface (sc_io) +// - The sub-interface has typedefs (addr_t) +// - A wrapper module (sc_wrap) instantiates sc_if and uses its typedefs +// - Two different parameterizations of sc_wrap exist +// - The re-exported typedefs from sc_io create entries where followCellPath +// may fail, triggering the structural disambiguation path +// +// 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 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +typedef struct packed { + int unsigned AddrBits; + int unsigned DataBits; +} sc_cfg_t; + +// Inner types interface - parameterized with struct typedef +interface sc_types_if #(parameter sc_cfg_t cfg = 0)(); + typedef logic [cfg.AddrBits-1:0] addr_t; + typedef logic [cfg.DataBits-1:0] data_t; + + typedef struct packed { + addr_t addr; + data_t data; + } pkt_t; +endinterface + +// Cache interface - wraps types interface and re-exports typedefs +interface sc_if #(parameter sc_cfg_t cfg = 0)(); + sc_types_if #(cfg) sc_io(); + + typedef sc_io.addr_t addr_t; + typedef sc_io.data_t data_t; + typedef sc_io.pkt_t pkt_t; + + addr_t rq_addr_i; +endinterface + +// Wrapper module that uses the cache interface +module sc_wrap #(parameter sc_cfg_t cfg = 0)(); + sc_if #(cfg) cache(); + + typedef cache.addr_t addr_t; + typedef cache.pkt_t pkt_t; + + addr_t local_addr; + pkt_t local_pkt; + + assign cache.rq_addr_i = local_addr; +endmodule + +// Top-level: two wrappers with DIFFERENT configs +// This creates two clones of sc_if (and sc_types_if) with different params +module t; + localparam sc_cfg_t cfg_narrow = '{AddrBits: 16, DataBits: 32}; + localparam sc_cfg_t cfg_wide = '{AddrBits: 32, DataBits: 64}; + + sc_wrap #(.cfg(cfg_narrow)) narrow(); + sc_wrap #(.cfg(cfg_wide)) wide(); + + initial begin + #1; + // narrow: addr=16, data=32, pkt=16+32=48 + `checkd($bits(narrow.local_addr), 16); + `checkd($bits(narrow.local_pkt), 48); + + // wide: addr=32, data=64, pkt=32+64=96 + `checkd($bits(wide.local_addr), 32); + `checkd($bits(wide.local_pkt), 96); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_interface_array_parameter_aggregate.py b/test_regress/t/t_interface_array_parameter_aggregate.py index df81265b7..096e10e28 100755 --- a/test_regress/t/t_interface_array_parameter_aggregate.py +++ b/test_regress/t/t_interface_array_parameter_aggregate.py @@ -11,8 +11,8 @@ import vltest_bootstrap test.scenarios('simulator_st') -test.compile(fails=True) +test.compile(verilator_flags2=['--binary']) -#test.execute(fails=True) +test.execute() test.passes() diff --git a/test_regress/t/t_interface_nested_port_array.out b/test_regress/t/t_interface_nested_port_array.out index a2f56f9b1..b37571a25 100644 --- a/test_regress/t/t_interface_nested_port_array.out +++ b/test_regress/t/t_interface_nested_port_array.out @@ -1,22 +1,10 @@ -%Error: t/t_interface_nested_port_array.v:160:5: Interface 'l3_if' not connected as parent's interface not connected - : ... note: In instance 't.m_l3' - : ... Perhaps caused by another error on the parent interface that needs resolving - : ... Or, perhaps intended an interface instantiation but are missing parenthesis (IEEE 1800-2023 25.3)? - 160 | l3_if#(W, L0A_W) l3, - | ^~~~~ - ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error: t/t_interface_nested_port_array.v:123:5: Interface 'l2_if' not connected as parent's interface not connected - : ... note: In instance 't.m_l3.m_l2' - : ... Perhaps caused by another error on the parent interface that needs resolving - : ... Or, perhaps intended an interface instantiation but are missing parenthesis (IEEE 1800-2023 25.3)? - 123 | l2_if#(W, L0A_W) l2s[1:0], - | ^~~~~ %Error: t/t_interface_nested_port_array.v:91:5: Interface 'l2_if' not connected as parent's interface not connected : ... note: In instance 't.m_l3.m_l2.m_l2b' : ... Perhaps caused by another error on the parent interface that needs resolving : ... Or, perhaps intended an interface instantiation but are missing parenthesis (IEEE 1800-2023 25.3)? 91 | l2_if#(W, L0A_W) l2, | ^~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. %Error: Internal Error: t/t_interface_nested_port_array.v:27:11: ../V3LinkDot.cpp:#: Module/etc never assigned a symbol entry? : ... note: In instance 't.m_l3.m_l2.m_l2b.m_l1_1' 27 | interface l2_if #( diff --git a/test_regress/t/t_interface_nested_port_array_noinl.out b/test_regress/t/t_interface_nested_port_array_noinl.out index a2f56f9b1..b37571a25 100644 --- a/test_regress/t/t_interface_nested_port_array_noinl.out +++ b/test_regress/t/t_interface_nested_port_array_noinl.out @@ -1,22 +1,10 @@ -%Error: t/t_interface_nested_port_array.v:160:5: Interface 'l3_if' not connected as parent's interface not connected - : ... note: In instance 't.m_l3' - : ... Perhaps caused by another error on the parent interface that needs resolving - : ... Or, perhaps intended an interface instantiation but are missing parenthesis (IEEE 1800-2023 25.3)? - 160 | l3_if#(W, L0A_W) l3, - | ^~~~~ - ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error: t/t_interface_nested_port_array.v:123:5: Interface 'l2_if' not connected as parent's interface not connected - : ... note: In instance 't.m_l3.m_l2' - : ... Perhaps caused by another error on the parent interface that needs resolving - : ... Or, perhaps intended an interface instantiation but are missing parenthesis (IEEE 1800-2023 25.3)? - 123 | l2_if#(W, L0A_W) l2s[1:0], - | ^~~~~ %Error: t/t_interface_nested_port_array.v:91:5: Interface 'l2_if' not connected as parent's interface not connected : ... note: In instance 't.m_l3.m_l2.m_l2b' : ... Perhaps caused by another error on the parent interface that needs resolving : ... Or, perhaps intended an interface instantiation but are missing parenthesis (IEEE 1800-2023 25.3)? 91 | l2_if#(W, L0A_W) l2, | ^~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. %Error: Internal Error: t/t_interface_nested_port_array.v:27:11: ../V3LinkDot.cpp:#: Module/etc never assigned a symbol entry? : ... note: In instance 't.m_l3.m_l2.m_l2b.m_l1_1' 27 | interface l2_if #( diff --git a/test_regress/t/t_interface_nested_struct_param.py b/test_regress/t/t_interface_nested_struct_param.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_interface_nested_struct_param.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=['--binary']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_nested_struct_param.v b/test_regress/t/t_interface_nested_struct_param.v new file mode 100644 index 000000000..a4932167b --- /dev/null +++ b/test_regress/t/t_interface_nested_struct_param.v @@ -0,0 +1,186 @@ +// 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 +// + +// Test: nested parameterized interface with struct typedef used as type parameter +// +// Reproduces a bug where specializeNestedIfaceCells causes early +// specialization of a nested interface, leaving PARAMTYPEDTYPE child +// REFDTYPEs pointing to the template struct instead of the clone struct. +// The struct's width then resolves using the template's default parameter +// values instead of the actual overridden values. +// +// Pattern (mirrors Aerial's simple_cache / simple_cache_if / simple_cache_types_if): +// 1. A wrapper interface instantiates a nested types interface +// 2. The types interface computes localparams from cfg (using $clog2/division) +// 3. Those localparams define typedef ranges for struct members +// 4. A module receives the wrapper interface as a port, also instantiates +// the types interface locally, and uses the struct typedef +// 5. Both the wrapper and the module use the same cfg, so they share +// the same types_if clone via "De-parameterize to prev" + +package cfg_pkg; + typedef struct packed { + logic [31:0] AddrBits; + logic [31:0] Capacity; + logic [31:0] LineSize; + logic [31:0] Associativity; + } cfg_t; +endpackage + +// Nested types interface: derives struct typedef from computed localparams +interface types_if #( + parameter cfg_pkg::cfg_t cfg = '0 +)(); + // Computed localparams - these use division and $clog2 of cfg fields. + // With default cfg='0, these produce X/undefined values. + localparam int NUM_LINES = cfg.Capacity / cfg.LineSize; + localparam int LINES_PER_WAY = NUM_LINES / cfg.Associativity; + localparam int BLOCK_BITS = $clog2(cfg.LineSize); + localparam int ROW_BITS = $clog2(LINES_PER_WAY); + localparam int TAG_BITS = cfg.AddrBits - ROW_BITS - BLOCK_BITS; + + typedef logic [TAG_BITS-1:0] tag_t; + typedef logic [ROW_BITS-1:0] row_t; + typedef logic [BLOCK_BITS-1:0] block_t; + + typedef struct packed { + logic vld; + tag_t tag; + row_t row; + block_t block; + } entry_t; +endinterface + +// Wrapper interface: instantiates types_if as a nested cell +// (mirrors simple_cache_if which instantiates simple_cache_types_if) +interface wrapper_if #( + parameter cfg_pkg::cfg_t cfg = '0 +)(); + types_if #(cfg) types(); + + typedef types.tag_t tag_t; + + logic req_vld; + tag_t req_tag; +endinterface + +// Sub-module parameterized by entry width +// (mirrors flop_nr / sram_generic_1r1w parameterized by $bits(sc_tag_t)) +module entry_store #( + parameter int ENTRY_WIDTH = 8, + parameter int DEPTH = 4 +)( + input logic clk, + input logic wr_en, + input logic [ENTRY_WIDTH-1:0] wr_data, + output logic [ENTRY_WIDTH-1:0] rd_data +); + logic [ENTRY_WIDTH-1:0] mem [DEPTH]; + always_ff @(posedge clk) begin + if (wr_en) mem[0] <= wr_data; + end + assign rd_data = mem[0]; +endmodule + +// Inner module: receives wrapper_if as port, instantiates types_if locally, +// uses struct typedef from types_if +// (mirrors simple_cache which receives simple_cache_if, instantiates +// simple_cache_types_if, and uses types.sc_tag_t) +module inner_mod #( + parameter cfg_pkg::cfg_t cfg = '0 +)( + input logic clk, + wrapper_if io +); + // Local instantiation of types_if - same cfg, so gets same clone + // as the one inside wrapper_if via "De-parameterize to prev" + types_if #(cfg) types(); + + typedef types.entry_t entry_t; + typedef types.tag_t tag_t; + + entry_t wr_entry; + entry_t rd_entry; + + assign wr_entry.vld = io.req_vld; + assign wr_entry.tag = io.req_tag; + assign wr_entry.row = '0; + assign wr_entry.block = '0; + + // Use $bits of the struct typedef as a value parameter to sub-module. + // This is the critical pattern: $bits(entry_t) must resolve using the + // clone's struct (correct width), not the template's (zero/X width). + entry_store #( + .ENTRY_WIDTH($bits(entry_t)), + .DEPTH(8) + ) u_store ( + .clk(clk), + .wr_en(io.req_vld), + .wr_data(wr_entry), + .rd_data(rd_entry) + ); +endmodule + +// Outer wrapper module: instantiates wrapper_if and inner_mod +// (mirrors mblit_simple_cache_wrap) +module outer_mod #( + parameter cfg_pkg::cfg_t cfg = '0 +)( + input logic clk +); + wrapper_if #(cfg) wif(); + + inner_mod #(cfg) u_inner ( + .clk(clk), + .io(wif) + ); +endmodule + +module t; + logic clk = 0; + always #5 clk = ~clk; + + int cyc = 0; + + // Non-default config: + // AddrBits=64, Capacity=1024, LineSize=64, Associativity=2 + // NUM_LINES = 1024/64 = 16 + // LINES_PER_WAY = 16/2 = 8 + // BLOCK_BITS = $clog2(64) = 6 + // ROW_BITS = $clog2(8) = 3 + // TAG_BITS = 64 - 3 - 6 = 55 + // entry_t = 1 + 55 + 3 + 6 = 65 bits + localparam cfg_pkg::cfg_t MY_CFG = '{ + AddrBits: 64, + Capacity: 1024, + LineSize: 64, + Associativity: 2 + }; + + outer_mod #(.cfg(MY_CFG)) u_outer (.clk(clk)); + + always @(posedge clk) begin + cyc <= cyc + 1; + + u_outer.wif.req_vld <= (cyc[0] == 1'b1); + u_outer.wif.req_tag <= 55'(cyc); + + if (cyc > 5) begin + // Verify the struct round-trips correctly + if (u_outer.u_inner.rd_entry.vld !== 1'b1 && cyc > 10) begin + $display("FAIL cyc=%0d: rd_entry.vld=%b expected 1", + cyc, u_outer.u_inner.rd_entry.vld); + $stop; + end + end + + if (cyc == 20) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_lparam_assign_iface_const.py b/test_regress/t/t_lparam_assign_iface_const.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_lparam_assign_iface_const.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_lparam_assign_iface_const.v b/test_regress/t/t_lparam_assign_iface_const.v new file mode 100644 index 000000000..9237520e4 --- /dev/null +++ b/test_regress/t/t_lparam_assign_iface_const.v @@ -0,0 +1,40 @@ +// 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 + +typedef struct { + int BAR_INT; + bit BAR_BIT; + byte BAR_ARRAY [0:3]; +} foo_t; + +interface intf + #(parameter foo_t FOO = '{4, 1'b1, '{8'd1, 8'd2, 8'd4, 8'd8}}) + (); +endinterface + +module sub (intf the_intf_port [4]); + localparam int intf_foo_bar_int = the_intf_port[0].FOO.BAR_INT; + + initial begin + #1; + if (intf_foo_bar_int != 4) $stop; + end +endmodule + +module t (); + intf the_intf [4] (); + + sub + the_sub ( + .the_intf_port (the_intf) + ); + + initial begin + #2; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_lparam_assign_iface_typedef.v b/test_regress/t/t_lparam_assign_iface_typedef.v index eb527f398..1d219d5f8 100644 --- a/test_regress/t/t_lparam_assign_iface_typedef.v +++ b/test_regress/t/t_lparam_assign_iface_typedef.v @@ -31,8 +31,8 @@ module top(); .p_dwidth(8) ) if0(); - localparam p0_rq_t = if0.rq_t; - localparam p0_rs_t = if0.rs_t; + localparam type p0_rq_t = if0.rq_t; + localparam type p0_rs_t = if0.rs_t; p0_rq_t rq; p0_rs_t rs; diff --git a/test_regress/t/t_lparam_assign_iface_typedef_bad.out b/test_regress/t/t_lparam_assign_iface_typedef_bad.out new file mode 100644 index 000000000..1f360a195 --- /dev/null +++ b/test_regress/t/t_lparam_assign_iface_typedef_bad.out @@ -0,0 +1,5 @@ +%Error: t/t_lparam_assign_iface_typedef_bad.v:28:3: Expecting a data type: 'p0_rq_t' + 28 | p0_rq_t rq; + | ^~~~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: Exiting due to diff --git a/test_regress/t/t_lparam_assign_iface_typedef_bad.py b/test_regress/t/t_lparam_assign_iface_typedef_bad.py new file mode 100755 index 000000000..1d5ccb8f4 --- /dev/null +++ b/test_regress/t/t_lparam_assign_iface_typedef_bad.py @@ -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: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('linter') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_lparam_assign_iface_typedef_bad.v b/test_regress/t/t_lparam_assign_iface_typedef_bad.v new file mode 100644 index 000000000..2a030945b --- /dev/null +++ b/test_regress/t/t_lparam_assign_iface_typedef_bad.v @@ -0,0 +1,29 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2025 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 +// +// localparam without 'type' keyword should error, not silently ignore. +// Correct syntax is: localparam type p0_rq_t = if0.rq_t; + +interface x_if #( + parameter int p_awidth = 4, + parameter int p_dwidth = 7 +)(); + typedef struct packed { + logic [p_awidth-1:0] addr; + logic [p_dwidth-1:0] data; + } rq_t; +endinterface + +module t(); + x_if #( + .p_awidth(16), + .p_dwidth(8) + ) if0(); + + localparam p0_rq_t = if0.rq_t; // Bad: missing 'type' keyword + + p0_rq_t rq; // Should fail: p0_rq_t is not a data type +endmodule diff --git a/test_regress/t/t_lparam_assign_iface_typedef_nested.v b/test_regress/t/t_lparam_assign_iface_typedef_nested.v index 65db21dd1..ecc0ae3fb 100644 --- a/test_regress/t/t_lparam_assign_iface_typedef_nested.v +++ b/test_regress/t/t_lparam_assign_iface_typedef_nested.v @@ -37,7 +37,7 @@ module top(); .p_dwidth(8) ) if0(); - localparam p0_rq2_t = if0.y_if0.rq2_t; + localparam type p0_rq2_t = if0.y_if0.rq2_t; initial begin #1; diff --git a/test_regress/t/t_lparam_assign_iface_typedef_nested2.v b/test_regress/t/t_lparam_assign_iface_typedef_nested2.v index 8cc63cd26..4e92ee22a 100644 --- a/test_regress/t/t_lparam_assign_iface_typedef_nested2.v +++ b/test_regress/t/t_lparam_assign_iface_typedef_nested2.v @@ -39,7 +39,7 @@ module top (); .p_dwidth(8) ) if0 (); - localparam p0_rq2_t = if0.y_if0.rq2_t; + localparam type p0_rq2_t = if0.y_if0.rq2_t; p0_rq2_t p0_rq2; diff --git a/test_regress/t/t_lparam_assign_iface_typedef_nested3.v b/test_regress/t/t_lparam_assign_iface_typedef_nested3.v index 634282662..ba37423fc 100644 --- a/test_regress/t/t_lparam_assign_iface_typedef_nested3.v +++ b/test_regress/t/t_lparam_assign_iface_typedef_nested3.v @@ -40,9 +40,9 @@ module top (); .p_dwidth(8) ) if0 (); - localparam p0_rq2_t = if0.y_if0.rq2_t; - localparam p0_rq_t = if0.rq_t; - localparam p0_rs_t = if0.rs_t; + localparam type p0_rq2_t = if0.y_if0.rq2_t; + localparam type p0_rq_t = if0.rq_t; + localparam type p0_rs_t = if0.rs_t; p0_rq2_t p0_rq2; p0_rq_t p0_rq; diff --git a/test_regress/t/t_lparam_assign_iface_typedef_nested5.v b/test_regress/t/t_lparam_assign_iface_typedef_nested5.v index 7179a0685..bc7ade5ef 100644 --- a/test_regress/t/t_lparam_assign_iface_typedef_nested5.v +++ b/test_regress/t/t_lparam_assign_iface_typedef_nested5.v @@ -46,10 +46,10 @@ module top (); .p_dwidth(8) ) if0 (); - localparam p0_rq2_t = if0.y_if0.rq2_t; - localparam p0_rq_t = if0.rq_t; - localparam p0_rs_t = if0.rs_t; - localparam p0_req_t = if0.z_if0.req_t; + localparam type p0_rq2_t = if0.y_if0.rq2_t; + localparam type p0_rq_t = if0.rq_t; + localparam type p0_rs_t = if0.rs_t; + localparam type p0_req_t = if0.z_if0.req_t; p0_rq2_t p0_rq2; p0_rq_t p0_rq; diff --git a/test_regress/t/t_lparam_assign_iface_typedef_nested_mod1.v b/test_regress/t/t_lparam_assign_iface_typedef_nested_mod1.v index d34a43490..27895f5cd 100644 --- a/test_regress/t/t_lparam_assign_iface_typedef_nested_mod1.v +++ b/test_regress/t/t_lparam_assign_iface_typedef_nested_mod1.v @@ -20,7 +20,7 @@ endinterface module a_mod( bus_if bus_io ); - localparam bus_rq_t = bus_io.rq_t; + localparam type bus_rq_t = bus_io.rq_t; endmodule module top(); diff --git a/test_regress/t/t_lparam_assign_iface_typedef_nested_mod2.v b/test_regress/t/t_lparam_assign_iface_typedef_nested_mod2.v index 56f0e8b33..6c39c30d1 100644 --- a/test_regress/t/t_lparam_assign_iface_typedef_nested_mod2.v +++ b/test_regress/t/t_lparam_assign_iface_typedef_nested_mod2.v @@ -28,8 +28,8 @@ endinterface module a_mod ( bus_if bus_io ); - localparam bus_rq_t = bus_io.rq_t; - localparam bus_rs_t = bus_io.rs_t; + localparam type bus_rq_t = bus_io.rq_t; + localparam type bus_rs_t = bus_io.rs_t; localparam p_awidth = bus_io.p_awidth; localparam p_dwidth = bus_io.p_dwidth; diff --git a/test_regress/t/t_lparam_assign_iface_typedef_nested_mod3.v b/test_regress/t/t_lparam_assign_iface_typedef_nested_mod3.v index 403e8587a..cce59e1c4 100644 --- a/test_regress/t/t_lparam_assign_iface_typedef_nested_mod3.v +++ b/test_regress/t/t_lparam_assign_iface_typedef_nested_mod3.v @@ -36,9 +36,9 @@ module top (); .p_dwidth(8) ) if0 (); - localparam p0_rq2_t = if0.y_if0.rq2_t; - localparam p0_rq_t = if0.rq_t; - localparam p0_rs_t = if0.rs_t; + localparam type p0_rq2_t = if0.y_if0.rq2_t; + localparam type p0_rq_t = if0.rq_t; + localparam type p0_rs_t = if0.rs_t; p0_rq2_t p0_rq2; p0_rq_t p0_rq; diff --git a/test_regress/t/t_lparam_assign_iface_typedef_nested_mpkg1.v b/test_regress/t/t_lparam_assign_iface_typedef_nested_mpkg1.v index 23d299d47..0fb11bbfa 100644 --- a/test_regress/t/t_lparam_assign_iface_typedef_nested_mpkg1.v +++ b/test_regress/t/t_lparam_assign_iface_typedef_nested_mpkg1.v @@ -37,8 +37,8 @@ module a_mod #( ) ( bus_if bus_io ); - localparam bus_rq_t = bus_io.rq_t; - localparam bus_rs_t = bus_io.rs_t; + localparam type bus_rq_t = bus_io.rq_t; + localparam type bus_rs_t = bus_io.rs_t; bus_rq_t rq; bus_rs_t rs; diff --git a/test_regress/t/t_lparam_assign_iface_typedef_nested_pkg.v b/test_regress/t/t_lparam_assign_iface_typedef_nested_pkg.v index 0249ac563..111a06852 100644 --- a/test_regress/t/t_lparam_assign_iface_typedef_nested_pkg.v +++ b/test_regress/t/t_lparam_assign_iface_typedef_nested_pkg.v @@ -54,10 +54,10 @@ module top (); x_if #(.CFG(CFG)) if0 (); - localparam p0_rq2_t = if0.y_if0.rq2_t; - localparam p0_rq_t = if0.rq_t; - localparam p0_rs_t = if0.rs_t; - localparam p0_req_t = if0.z_if0.req_t; + localparam type p0_rq2_t = if0.y_if0.rq2_t; + localparam type p0_rq_t = if0.rq_t; + localparam type p0_rs_t = if0.rs_t; + localparam type p0_req_t = if0.z_if0.req_t; p0_rq2_t p0_rq2; p0_rq_t p0_rq; diff --git a/test_regress/t/t_lparam_dep_iface0.py b/test_regress/t/t_lparam_dep_iface0.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface0.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_lparam_dep_iface0.v b/test_regress/t/t_lparam_dep_iface0.v new file mode 100644 index 000000000..54abb8d2e --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface0.v @@ -0,0 +1,47 @@ +// DESCRIPTION: Verilator: Get agregate type parameter from array of interfaces +// +// 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 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +typedef struct { + int BAR_INT; + bit BAR_BIT; + byte BAR_ARRAY [0:3]; +} foo_t; + +interface intf + #(parameter foo_t FOO = '{4, 1'b1, '{8'd1, 8'd2, 8'd4, 8'd8}}) + (); +endinterface + +module sub (intf single_intf_port); + localparam byte single_foo_bar_byte = single_intf_port.FOO.BAR_ARRAY[3]; + + initial begin + #1; + `checkd(single_foo_bar_byte, 8'd8); + end +endmodule + +module t (); + intf single_intf (); + + sub + the_sub ( + .single_intf_port(single_intf) + ); + + initial begin + #2; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_lparam_dep_iface1.py b/test_regress/t/t_lparam_dep_iface1.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface1.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_lparam_dep_iface1.v b/test_regress/t/t_lparam_dep_iface1.v new file mode 100644 index 000000000..c9029fc01 --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface1.v @@ -0,0 +1,47 @@ +// DESCRIPTION: Verilator: Get agregate type parameter from array of interfaces +// +// 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 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +typedef struct { + int BAR_INT; + bit BAR_BIT; + byte BAR_ARRAY [0:3]; +} foo_t; + +interface intf + #(parameter foo_t FOO = '{4, 1'b1, '{8'd1, 8'd2, 8'd4, 8'd8}}) + (); +endinterface + +module sub (intf the_intf_port [4]); + localparam int intf_foo_bar_int = the_intf_port[0].FOO.BAR_INT; + + initial begin + #1; + `checkd(intf_foo_bar_int, 4); + end +endmodule + +module t (); + intf the_intf [4] (); + + sub + the_sub ( + .the_intf_port (the_intf) + ); + + initial begin + #2; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_lparam_dep_iface10.py b/test_regress/t/t_lparam_dep_iface10.py new file mode 100755 index 000000000..cfbb2aee3 --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface10.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 by Wilson Snyder. This program is free # 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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_lparam_dep_iface10.v b/test_regress/t/t_lparam_dep_iface10.v new file mode 100644 index 000000000..0e34e4793 --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface10.v @@ -0,0 +1,72 @@ +// DESCRIPTION: Verilator: Multiple interface instances with different params +// +// 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 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package scp; + typedef struct packed { + int unsigned ABits; + int unsigned BBits; + } cfg_t; +endpackage + +interface a_if #( + parameter a_p = 0 +)(); + localparam int LP0 = a_p * 2; + typedef logic [LP0-1:0] a_t; +endinterface + +interface sc_if #( + parameter scp::cfg_t cfg = 0 +)(); + localparam int LP_MUL = cfg.ABits * cfg.BBits; + localparam int LP_ADD = cfg.ABits + cfg.BBits; + + a_if #(LP_MUL) types_mul(); + a_if #(LP_ADD) types_add(); +endinterface + +module sc #(parameter scp::cfg_t cfg=0) ( + sc_if io +); + + typedef io.types_mul.a_t a_mul_t; + typedef io.types_add.a_t a_add_t; + + initial begin + #1; + // cfg.ABits=2, cfg.BBits=3 + // LP_MUL=6 -> a_p=6 -> LP0=12 -> a_mul_t is 12 bits + // LP_ADD=5 -> a_p=5 -> LP0=10 -> a_add_t is 10 bits + `checkd($bits(a_mul_t), 12); + `checkd($bits(a_add_t), 10); + end +endmodule + +module t(); + localparam scp::cfg_t sc_cfg = '{ + ABits : 2, + BBits : 3 + }; + + sc_if #(sc_cfg) sc_io (); + + sc #(sc_cfg) sc( + .io(sc_io) + ); + + initial begin + #2; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_lparam_dep_iface11.py b/test_regress/t/t_lparam_dep_iface11.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface11.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_lparam_dep_iface11.v b/test_regress/t/t_lparam_dep_iface11.v new file mode 100644 index 000000000..6ed0ccd3c --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface11.v @@ -0,0 +1,82 @@ +// DESCRIPTION: Verilator: Cross-interface typedef references +// +// 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 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package scp; + typedef struct packed { + int unsigned ABits; + int unsigned BBits; + } cfg_t; +endpackage + +interface a_if #( + parameter a_p = 0 +)(); + localparam int LP0 = a_p * 2; + typedef logic [LP0-1:0] a_t; +endinterface + +interface b_if #( + parameter b_p = 0 +)(); + localparam int LP0 = b_p + 3; + typedef logic [LP0-1:0] b_t; +endinterface + +interface sc_if #( + parameter scp::cfg_t cfg = 0 +)(); + localparam int LP0 = cfg.ABits * cfg.BBits; + + a_if #(LP0) a_inst(); + b_if #(LP0) b_inst(); + + typedef a_inst.a_t a_t; + typedef b_inst.b_t b_t; +endinterface + +module sc #(parameter scp::cfg_t cfg=0) ( + sc_if io +); + + typedef io.a_t a_t; + typedef io.b_t b_t; + + initial begin + #1; + // cfg.ABits=2, cfg.BBits=3 -> LP0=6 + // a_if: a_p=6 -> LP0=12 -> a_t is 12 bits + // b_if: b_p=6 -> LP0=9 -> b_t is 9 bits + `checkd(12, $bits(a_t)); + `checkd(9, $bits(b_t)); + end +endmodule + +module t(); + localparam scp::cfg_t sc_cfg = '{ + ABits : 2, + BBits : 3 + }; + + sc_if #(sc_cfg) sc_io (); + + sc #(sc_cfg) sc( + .io(sc_io) + ); + + initial begin + #2; + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_lparam_dep_iface12.py b/test_regress/t/t_lparam_dep_iface12.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface12.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_lparam_dep_iface12.v b/test_regress/t/t_lparam_dep_iface12.v new file mode 100644 index 000000000..e462e4cca --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface12.v @@ -0,0 +1,90 @@ +// DESCRIPTION: Verilator: 4-level deep nested interface typedef with dependent localparams +// +// 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 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package scp; + typedef struct packed { + int unsigned ABits; + int unsigned BBits; + } cfg_t; +endpackage + +// Level 4: innermost interface +interface d_if #( + parameter d_p = 0 +)(); + localparam int LP0 = d_p + 1; + typedef logic [LP0-1:0] d_t; +endinterface + +// Level 3 +interface c_if #( + parameter c_p = 0 +)(); + localparam int LP0 = c_p * 2; + d_if #(LP0) d_inst(); + typedef d_inst.d_t c_t; +endinterface + +// Level 2 +interface b_if #( + parameter scp::cfg_t cfg = 0 +)(); + localparam int LP0 = cfg.ABits * cfg.BBits; + c_if #(LP0) c_inst(); + typedef c_inst.c_t b_t; +endinterface + +// Level 1: outermost interface +interface a_if #( + parameter scp::cfg_t cfg = 0 +)(); + b_if #(cfg) b_inst(); + typedef b_inst.b_t a_t; +endinterface + +module m #(parameter scp::cfg_t cfg=0) ( + a_if io +); + + typedef io.a_t a_t; + + initial begin + #1; + // cfg.ABits=2, cfg.BBits=3 + // b_if: LP0 = 2*3 = 6 + // c_if: c_p=6, LP0 = 6*2 = 12 + // d_if: d_p=12, LP0 = 12+1 = 13 + // d_t is 13 bits + `checkd($bits(a_t), 13); + end +endmodule + +module t(); + localparam scp::cfg_t cfg = '{ + ABits : 2, + BBits : 3 + }; + + a_if #(cfg) a_io (); + + m #(cfg) m_inst( + .io(a_io) + ); + + initial begin + #2; + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_lparam_dep_iface13.py b/test_regress/t/t_lparam_dep_iface13.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface13.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_lparam_dep_iface13.v b/test_regress/t/t_lparam_dep_iface13.v new file mode 100644 index 000000000..4feff82f7 --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface13.v @@ -0,0 +1,107 @@ +// DESCRIPTION: Verilator: Get agregate type parameter from array of interfaces +// +// 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 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package sc; + typedef struct packed { + int unsigned CmdTagBits; + int unsigned Associativity; + int unsigned Capacity; + int unsigned LineSize; + int unsigned StateBits; + int unsigned AddrBits; + int unsigned MissQSize; + + // fetch (hit) width. this must be >= to refill width. FgWidth / RefillWidth is the number of array slices for data. + int unsigned FgWidth; + // number of expected beats for refill is LineSize/RefillWidth + int unsigned RefillWidth; + } cfg_t; +endpackage + +interface simple_cache_types_if #( + parameter sc::cfg_t cfg = 0 +)(); + + localparam int SC_NUM_LINES = cfg.Capacity / cfg.LineSize; + localparam int SC_LINES_PER_WAY = SC_NUM_LINES / cfg.Associativity; + localparam int SC_BLOCK_BITS = $clog2(cfg.LineSize); + localparam int SC_ROW_BITS = $clog2(SC_LINES_PER_WAY); + localparam int SC_TAG_BITS = cfg.AddrBits - SC_ROW_BITS - SC_BLOCK_BITS; + localparam int SC_DROWS_PER_LINE = cfg.LineSize / cfg.FgWidth; + localparam int SC_NUM_DROWS = SC_NUM_LINES * SC_DROWS_PER_LINE; + +endinterface + +interface simple_cache_if #( + parameter sc::cfg_t cfg = 0 +)(); + simple_cache_types_if #(cfg) types(); + +endinterface + +module simple_cache #(parameter sc::cfg_t cfg=0) ( + simple_cache_if io +); + + localparam num_rld_beats = cfg.LineSize / cfg.RefillWidth; + localparam num_arrays = cfg.FgWidth / cfg.RefillWidth; + localparam dat_array_width = cfg.RefillWidth*8; + localparam int SC_DROWS_PER_LINE = io.types.SC_DROWS_PER_LINE; + localparam int SC_NUM_LINES = io.types.SC_NUM_LINES; + localparam int SC_LINES_PER_WAY = io.types.SC_LINES_PER_WAY; + localparam int SC_NUM_DROWS = io.types.SC_NUM_DROWS; + + initial begin + #1; + `checkd(SC_DROWS_PER_LINE, 4); + `checkd(SC_NUM_LINES, 16); + `checkd(SC_LINES_PER_WAY, 8); + `checkd(SC_NUM_DROWS, 64); + `checkd(num_rld_beats, 8); + `checkd(num_arrays, 2); + `checkd(dat_array_width, 64); + end + +endmodule + + +module t(); + + localparam sc::cfg_t sc_cfg = '{ + CmdTagBits : $clog2(6), + Associativity : 2, + Capacity : 1024, + LineSize : 64, + StateBits : 2, + AddrBits : 64, + MissQSize : 2, + + FgWidth : 16, + RefillWidth : 8 + }; + + simple_cache_if #(sc_cfg) sc_io (); + + simple_cache #(sc_cfg) simple_cache( + .io(sc_io) + ); + + localparam int SC_DROWS_PER_LINE = sc_io.types.SC_DROWS_PER_LINE; + + initial begin + #2; + `checkd(SC_DROWS_PER_LINE, 4); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_lparam_dep_iface14.py b/test_regress/t/t_lparam_dep_iface14.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface14.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_lparam_dep_iface14.v b/test_regress/t/t_lparam_dep_iface14.v new file mode 100644 index 000000000..04ac60423 --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface14.v @@ -0,0 +1,85 @@ +// DESCRIPTION: +// Combined regression model mixing PIN-assigned type param +// (t_interface_derived_type) and nested captured typedef flows +// (t_lparam_dep_iface*). Keeps both types in a single file. +// +// 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 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +typedef struct packed { + int unsigned ABits; + int unsigned BBits; +} scp_cfg_t; + +interface a_if #(parameter a_p = 0)(); + localparam int LP0 = a_p * 2; + typedef logic [LP0-1:0] a_t; +endinterface + +interface sct_if #(parameter scp_cfg_t cfg = 0)(); + localparam int LP0 = cfg.ABits * cfg.BBits; + a_if #(LP0) a_if0(); + typedef a_if0.a_t a_t; // Captured typedef from nested interface +endinterface + +interface intf #( + parameter type data_t = bit, + parameter int arr[2][4] +) (); + data_t data; + logic [$bits(data)-1:0] other_data; +endinterface + +module sub #( + parameter int width, + parameter int arr[2][4] +) (); + typedef struct packed { + logic [3:3] [0:0] [width-1:0] field; + } user_type_t; + + // This has a PIN that assigns data_t + intf #( + .data_t(user_type_t), + .arr(arr) + ) the_intf (); + + logic [width-1:0] signal; + + always_comb begin + the_intf.data.field = signal; + the_intf.other_data = signal; + end +endmodule + +module t (); + localparam scp_cfg_t sc_cfg = '{ABits: 2, BBits: 3}; + + sct_if #(sc_cfg) types (); + + sub #(.width(8), .arr('{'{8, 2, 3, 4}, '{1, 2, 3, 4}})) sub8 (); + sub #(.width(16), .arr('{'{16, 2, 3, 4}, '{1, 2, 3, 4}})) sub16 (); + + typedef types.a_if0.a_t a_t; + typedef types.a_t a2_t; + + initial begin + #1; + `checkd(12, $bits(a_t)); + `checkd(12, $bits(a2_t)); + `checkd(8, $bits(sub8.the_intf.data)); + `checkd(16, $bits(sub16.the_intf.data)); + + #1; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_lparam_dep_iface15.py b/test_regress/t/t_lparam_dep_iface15.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface15.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_lparam_dep_iface15.v b/test_regress/t/t_lparam_dep_iface15.v new file mode 100644 index 000000000..2af8515ac --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface15.v @@ -0,0 +1,101 @@ +// 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 +// +// DESCRIPTION: +// Combined test mixing PIN-assigned type param interface +// (t_interface_derived_type) with nested captured typedef/localparam +// (t_lparam_dep_iface6). + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +typedef struct packed { + int unsigned ABits; + int unsigned BBits; +} scp_cfg_t; + +interface a_if #(parameter a_p = 0)(); + localparam int LP0 = a_p; + typedef logic [LP0-1:0] a_t; +endinterface + +interface sct_if #(parameter scp_cfg_t cfg = 0)(); + localparam int LP0 = cfg.ABits * cfg.BBits; + a_if #(LP0) a_if0(); + typedef a_if0.a_t a_t; +endinterface + +interface sc_if #(parameter scp_cfg_t cfg = 0)(); + sct_if #(cfg) types(); + typedef types.a_t a_t; +endinterface + +interface intf #( + parameter type data_t = bit, + parameter int arr[2][4] +) (); + data_t data; + logic [$bits(data)-1:0] other_data; +endinterface + +module sub #( + parameter int width, + parameter int arr[2][4] +) (); + typedef struct packed { + logic [3:3] [0:0] [width-1:0] field; + } user_type_t; + + intf #( + .data_t(user_type_t), + .arr(arr) + ) the_intf (); + + logic [width-1:0] signal; + + always_comb begin + the_intf.data.field = signal; + the_intf.other_data = signal; + end +endmodule + +module sc #(parameter scp_cfg_t cfg=0) ( + sc_if io +); + typedef io.a_t a_t; + + initial begin + #1; + `checkd($bits(a_t), 6); + end +endmodule + +module t (); + localparam scp_cfg_t sc_cfg = '{ABits: 2, BBits: 3}; + + sc_if #(sc_cfg) sc_io (); + sc #(sc_cfg) sc_inst (.io(sc_io)); + + sub #(.width(8), .arr('{'{8, 2, 3, 4}, '{1, 2, 3, 4}})) sub8 (); + sub #(.width(16), .arr('{'{16, 2, 3, 4}, '{1, 2, 3, 4}})) sub16 (); + + typedef sc_io.types.a_if0.a_t inner_t; + typedef sc_io.types.a_t mid_t; + + initial begin + #1; + `checkd($bits(inner_t), 6); + `checkd($bits(mid_t), 6); + `checkd($bits(sub8.the_intf.data), 8); + `checkd($bits(sub16.the_intf.data), 16); + + #1; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_lparam_dep_iface16.py b/test_regress/t/t_lparam_dep_iface16.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface16.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_lparam_dep_iface16.v b/test_regress/t/t_lparam_dep_iface16.v new file mode 100644 index 000000000..1a68ec3e2 --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface16.v @@ -0,0 +1,98 @@ +// 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 +// +// DESCRIPTION: +// Combined test mixing PIN-assigned type param interface (t_interface_derived_type) +// with nested captured typedef/localparam (t_lparam_dep_iface6). + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +typedef struct packed { + int unsigned ABits; + int unsigned BBits; +} scp_cfg_t; + +interface a_if #(parameter a_p = 0)(); + localparam int LP0 = a_p; + typedef logic [LP0-1:0] a_t; +endinterface + +interface sct_if #(parameter scp_cfg_t cfg = 0)(); + localparam int LP0 = cfg.ABits * cfg.BBits; + a_if #(LP0) a_if0(); + typedef a_if0.a_t a_t; +endinterface + +interface sc_if #(parameter scp_cfg_t cfg = 0)(); + sct_if #(cfg) types(); + typedef types.a_t a_t; +endinterface + +interface intf #( + parameter type data_t = bit, + parameter int arr[2][4] +) (); + data_t data; + logic [$bits(data)-1:0] other_data; +endinterface + +module sub #( + parameter int width, + parameter int arr[2][4] +) (); + typedef struct packed { + logic [3:3] [0:0] [width-1:0] field; + } user_type_t; + + intf #( + .data_t(user_type_t), + .arr(arr) + ) the_intf (); + + logic [width-1:0] signal; + + always_comb begin + the_intf.data.field = signal; + the_intf.other_data = signal; + end +endmodule + +module sc #(parameter scp_cfg_t cfg=0) ( + sc_if io +); + typedef io.a_t a_t; + + initial begin + #1; + `checkd($bits(a_t), 6); + end +endmodule + +module t (input clk); + localparam scp_cfg_t sc_cfg = '{ABits: 2, BBits: 3}; + + sc_if #(sc_cfg) sc_io (); + sc #(sc_cfg) sc_inst (.io(sc_io)); + + sub #(.width(8), .arr('{'{8, 2, 3, 4}, '{1, 2, 3, 4}})) sub8 (); + + typedef sc_io.types.a_if0.a_t inner_t; + typedef sc_io.types.a_t mid_t; + + initial begin + #1; + `checkd($bits(inner_t), 6); + `checkd($bits(mid_t), 6); + `checkd($bits(sub8.the_intf.data), 8); + + #1; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_lparam_dep_iface2.py b/test_regress/t/t_lparam_dep_iface2.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface2.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_lparam_dep_iface2.v b/test_regress/t/t_lparam_dep_iface2.v new file mode 100644 index 000000000..afa27b5f2 --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface2.v @@ -0,0 +1,90 @@ +// DESCRIPTION: Verilator: Get agregate type parameter from array of interfaces +// +// 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 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package p1; + typedef struct packed { + int unsigned ABits; + int unsigned BBits; + } cfg_t; +endpackage + +package p2; + typedef struct packed { + int unsigned CBits; + int unsigned DBits; + } cfg_t; +endpackage + +interface types_if #(parameter p1::cfg_t cfg=0)(); + localparam int ABits = cfg.ABits; + localparam int BBits = cfg.BBits; + + typedef struct packed { + logic [cfg.ABits-1:0] a; + logic [cfg.BBits-1:0] b; + } a_t; +endinterface + +interface io_if #(parameter p1::cfg_t cfg=0)(); + + localparam int ABits = cfg.ABits; + localparam int BBits = cfg.BBits; + + types_if #(cfg) types (); + typedef types.a_t a_t; +endinterface + +module modA( + io_if io +); + + localparam int ABits = io.types.ABits; + localparam int BBits = io.types.BBits; + + typedef io.types.a_t a_t; + + initial begin + #1; + `checkd(ABits, 8); + `checkd(BBits, 24); + `checkd($bits(a_t), 32); + end + +endmodule + +module t (); + localparam p2::cfg_t mcfg = '{ + CBits : 8, + DBits : 16 + }; + + localparam p1::cfg_t cfg = '{ + ABits : mcfg.CBits, + BBits : mcfg.CBits + mcfg.DBits + }; + + io_if #(cfg) modA_io (); + + typedef modA_io.types.a_t a_t; + + modA modA_inst ( + .io(modA_io) + ); + + initial begin + #2; + `checkd($bits(a_t), 32); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_lparam_dep_iface3.py b/test_regress/t/t_lparam_dep_iface3.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface3.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_lparam_dep_iface3.v b/test_regress/t/t_lparam_dep_iface3.v new file mode 100644 index 000000000..5675fc8c1 --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface3.v @@ -0,0 +1,157 @@ +// DESCRIPTION: Verilator: Get agregate type parameter from array of interfaces +// +// 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 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package sc; + typedef struct packed { + int unsigned CmdTagBits; + int unsigned Associativity; + int unsigned Capacity; + int unsigned LineSize; + int unsigned StateBits; + int unsigned AddrBits; + int unsigned MissQSize; + + // fetch (hit) width. this must be >= to refill width. FgWidth / RefillWidth is the number of array slices for data. + int unsigned FgWidth; + // number of expected beats for refill is LineSize/RefillWidth + int unsigned RefillWidth; + } cfg_t; +endpackage + +interface simple_cache_types_if #( + parameter sc::cfg_t cfg = 0 +)(); + + localparam int SC_NUM_LINES = cfg.Capacity / cfg.LineSize; + localparam int SC_LINES_PER_WAY = SC_NUM_LINES / cfg.Associativity; + localparam int SC_BLOCK_BITS = $clog2(cfg.LineSize); + localparam int SC_ROW_BITS = $clog2(SC_LINES_PER_WAY); + localparam int SC_TAG_BITS = cfg.AddrBits - SC_ROW_BITS - SC_BLOCK_BITS; + localparam int SC_DROWS_PER_LINE = cfg.LineSize / cfg.FgWidth; + localparam int SC_NUM_DROWS = SC_NUM_LINES * SC_DROWS_PER_LINE; + + typedef logic [cfg.AddrBits-1:0] addr_t; + typedef logic [cfg.Associativity-1:0] assoc_oh_t; + typedef logic [cfg.Associativity-2:0] plru_t; + typedef logic [cfg.StateBits-1:0] state_t; + typedef logic [cfg.CmdTagBits-1:0] cmd_tag_t; + typedef logic [$clog2(cfg.MissQSize)-1:0] missq_tag_t; + + typedef logic [SC_TAG_BITS-1:0] tag_t; + typedef logic [SC_ROW_BITS-1:0] row_t; + typedef logic [SC_BLOCK_BITS-1:0] block_t; + typedef logic [$clog2(SC_NUM_DROWS)-1:0] drow_addr_t; + + typedef struct packed { + tag_t tag; + row_t row; + block_t block; + } sc_tag_addr_t; + + typedef struct packed { + logic vld; + tag_t tag; + state_t state; + } sc_tag_t; + + typedef struct packed { + state_t [cfg.Associativity-1:0] state_v; + assoc_oh_t hit_v; + assoc_oh_t vld_v; + plru_t plru; + } sc_tag_status_t; +endinterface + +interface simple_cache_if #( + parameter sc::cfg_t cfg = 0 +)(); + simple_cache_types_if #(cfg) types(); + + typedef types.cmd_tag_t cmd_tag_t; + typedef types.addr_t addr_t; + typedef types.missq_tag_t missq_tag_t; + +endinterface + +module simple_cache #(parameter sc::cfg_t cfg=0) ( + simple_cache_if io +); + + typedef io.types.addr_t addr_t; + typedef io.types.cmd_tag_t cmd_tag_t; + typedef io.types.drow_addr_t drow_addr_t; + typedef io.types.plru_t plru_t; + typedef io.types.row_t row_t; + typedef io.types.state_t state_t; + typedef io.types.sc_tag_addr_t sc_tag_addr_t; + typedef io.types.sc_tag_t sc_tag_t; + typedef io.types.sc_tag_status_t sc_tag_status_t; + + localparam num_rld_beats = cfg.LineSize / cfg.RefillWidth; + localparam num_arrays = cfg.FgWidth / cfg.RefillWidth; + localparam dat_array_width = cfg.RefillWidth*8; + localparam int SC_DROWS_PER_LINE = io.types.SC_DROWS_PER_LINE; + localparam int SC_NUM_LINES = io.types.SC_NUM_LINES; + localparam int SC_LINES_PER_WAY = io.types.SC_LINES_PER_WAY; + localparam int SC_NUM_DROWS = io.types.SC_NUM_DROWS; + + initial begin + #1; + `checkd(SC_DROWS_PER_LINE, 4); + `checkd(SC_NUM_LINES, 16); + `checkd(SC_LINES_PER_WAY, 8); + `checkd(SC_NUM_DROWS, 64); + `checkd(num_rld_beats, 8); + `checkd(num_arrays, 2); + `checkd(dat_array_width, 64); + end + +endmodule + + +module t(); + + localparam sc::cfg_t sc_cfg = '{ + CmdTagBits : $clog2(6), + Associativity : 2, + Capacity : 1024, + LineSize : 64, + StateBits : 2, + AddrBits : 64, + MissQSize : 2, + + FgWidth : 16, + RefillWidth : 8 + }; + + simple_cache_if #(sc_cfg) sc_io (); + + simple_cache #(sc_cfg) simple_cache( + .io(sc_io) + ); + + //localparam int SC_DROWS_PER_LINE = sc_io.types.SC_DROWS_PER_LINE; + //localparam int SC_NUM_LINES = sc_io.types.SC_NUM_LINES; + //localparam int SC_LINES_PER_WAY = sc_io.types.SC_LINES_PER_WAY; + //localparam int SC_NUM_DROWS = sc_io.types.SC_NUM_DROWS; + + initial begin + #2; + //`checkd(SC_DROWS_PER_LINE, 4); + //`checkd(SC_NUM_LINES, 16); + //`checkd(SC_LINES_PER_WAY, 8); + //`checkd(SC_NUM_DROWS, 64); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_lparam_dep_iface4.py b/test_regress/t/t_lparam_dep_iface4.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface4.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_lparam_dep_iface4.v b/test_regress/t/t_lparam_dep_iface4.v new file mode 100644 index 000000000..d81eab7c0 --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface4.v @@ -0,0 +1,78 @@ +// DESCRIPTION: Verilator: Get agregate type parameter from array of interfaces +// +// 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 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package scp; + typedef struct packed { + int unsigned Capacity; + int unsigned LineSize; + } cfg_t; +endpackage + +interface sct_if #( + parameter scp::cfg_t cfg = 0 +)(); + + localparam int SC_NUM_LINES = cfg.Capacity / cfg.LineSize; + + typedef logic [(cfg.Capacity / cfg.LineSize)-1:0] sc_num_lines_t; + + typedef logic [SC_NUM_LINES-1:0] sc_num_lines_2_t; + +endinterface + +interface sc_if #( + parameter scp::cfg_t cfg = 0 +)(); + sct_if #(cfg) types(); + +endinterface + +module sc #(parameter scp::cfg_t cfg=0) ( + sc_if io +); + + localparam int SC_NUM_LINES = io.types.SC_NUM_LINES; + + typedef io.types.sc_num_lines_t sc_num_lines_t; + typedef io.types.sc_num_lines_2_t sc_num_lines_2_t; + + initial begin + #1; + $display("SC_NUM_LINES = %d", SC_NUM_LINES); + $display("bits SC_NUM_LINES = %d", $bits(sc_num_lines_t)); + $display("bits SC_NUM_LINES_2 = %d", $bits(sc_num_lines_2_t)); + `checkd(SC_NUM_LINES, 16); + `checkd($bits(sc_num_lines_t), 16); + `checkd($bits(sc_num_lines_2_t), 16); + end +endmodule + +module t(); + + localparam scp::cfg_t sc_cfg = '{ + Capacity : 1024, + LineSize : 64 + }; + + sc_if #(sc_cfg) sc_io (); + + sc #(sc_cfg) simple_cache( + .io(sc_io) + ); + + initial begin + #2; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_lparam_dep_iface5.py b/test_regress/t/t_lparam_dep_iface5.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface5.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_lparam_dep_iface5.v b/test_regress/t/t_lparam_dep_iface5.v new file mode 100644 index 000000000..cb0c654d9 --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface5.v @@ -0,0 +1,80 @@ +// DESCRIPTION: Verilator: Get agregate type parameter from array of interfaces +// +// 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 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package scp; + typedef struct packed { + int unsigned Associativity; + int unsigned Capacity; + int unsigned LineSize; + int unsigned AddrBits; + } cfg_t; +endpackage + +interface sct_if #( + parameter scp::cfg_t cfg = 0 +)(); + + // this is intentional as I want all the dependencies to be resolved + localparam int SC_NUM_LINES = cfg.Capacity / cfg.LineSize; + localparam int SC_LINES_PER_WAY = SC_NUM_LINES / cfg.Associativity; + localparam int SC_BLOCK_BITS = $clog2(cfg.LineSize); + localparam int SC_ROW_BITS = $clog2(SC_LINES_PER_WAY); + localparam int SC_TAG_BITS = cfg.AddrBits - SC_ROW_BITS - SC_BLOCK_BITS; + + typedef logic [SC_TAG_BITS-1:0] tag_t; +endinterface + +interface sc_if #( + parameter scp::cfg_t cfg = 0 +)(); + sct_if #(cfg) types(); +endinterface + +module sc #(parameter scp::cfg_t cfg=0) ( + sc_if io +); + sct_if #(cfg) types(); + + typedef io.types.tag_t tag_t; + typedef types.tag_t tag2_t; + + initial begin + #1; + `checkd(55, $bits(tag_t)); + `checkd(55, $bits(tag2_t)); + end +endmodule + +module t(); + localparam scp::cfg_t sc_cfg = '{ + Associativity : 2, + Capacity : 1024, + LineSize : 64, + AddrBits : 64 + }; + + sc_if #(sc_cfg) sc_io (); + + typedef sc_io.types.tag_t tag_t; + + sc #(sc_cfg) sc( + .io(sc_io) + ); + + initial begin + #2; + `checkd(55, $bits(tag_t)); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_lparam_dep_iface6.py b/test_regress/t/t_lparam_dep_iface6.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface6.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_lparam_dep_iface6.v b/test_regress/t/t_lparam_dep_iface6.v new file mode 100644 index 000000000..e19b8a6a4 --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface6.v @@ -0,0 +1,76 @@ +// DESCRIPTION: Verilator: Get agregate type parameter from array of interfaces +// +// 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 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package scp; + typedef struct packed { + int unsigned ABits; + int unsigned BBits; + } cfg_t; +endpackage + +interface a_if #( + parameter a_p = 0 +)(); + localparam int LP0 = a_p; + typedef logic [LP0-1:0] a_t; +endinterface + +interface sct_if #( + parameter scp::cfg_t cfg = 0 +)(); + // this is intentional as I want all the dependencies to be resolved + localparam int LP0 = cfg.ABits * cfg.BBits; + + a_if #(LP0) a_if0(); + typedef a_if0.a_t a_t; +endinterface + +interface sc_if #( + parameter scp::cfg_t cfg = 0 +)(); + sct_if #(cfg) types(); + + typedef types.a_t a_t; +endinterface + +module sc #(parameter scp::cfg_t cfg=0) ( + sc_if io +); + + typedef io.a_t a_t; + + initial begin + #1; + `checkd(6, $bits(a_t)); + + end +endmodule + +module t(); + localparam scp::cfg_t sc_cfg = '{ + ABits : 2, + BBits : 3 + }; + + sc_if #(sc_cfg) sc_io (); + + sc #(sc_cfg) sc( + .io(sc_io) + ); + + initial begin + #2; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_lparam_dep_iface7.py b/test_regress/t/t_lparam_dep_iface7.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface7.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_lparam_dep_iface7.v b/test_regress/t/t_lparam_dep_iface7.v new file mode 100644 index 000000000..133d8a802 --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface7.v @@ -0,0 +1,67 @@ +// DESCRIPTION: Verilator: Get agregate type parameter from array of interfaces +// +// 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 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package scp; + typedef struct packed { + int unsigned ABits; + int unsigned BBits; + } cfg_t; +endpackage + +interface a_if #( + parameter a_p = 0 +)(); + localparam int LP0 = a_p * 2; + typedef logic [LP0-1:0] a_t; +endinterface + +interface sc_if #( + parameter scp::cfg_t cfg = 0 +)(); + + localparam int LP0 = cfg.ABits * cfg.BBits; + a_if #(LP0) types(); +endinterface + +module sc #(parameter scp::cfg_t cfg=0) ( + sc_if io +); + + typedef io.types.a_t a_t; + + initial begin + #1; + `checkd(12, $bits(a_t)); + + end +endmodule + +module t(); + localparam scp::cfg_t sc_cfg = '{ + ABits : 2, + BBits : 3 + }; + + sc_if #(sc_cfg) sc_io (); + + sc #(sc_cfg) sc( + .io(sc_io) + ); + + initial begin + #2; + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_lparam_dep_iface8.py b/test_regress/t/t_lparam_dep_iface8.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface8.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_lparam_dep_iface8.v b/test_regress/t/t_lparam_dep_iface8.v new file mode 100644 index 000000000..3d8c325c0 --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface8.v @@ -0,0 +1,77 @@ +// DESCRIPTION: Verilator: 3-level nested interface typedef with dependent localparams +// +// 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 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package scp; + typedef struct packed { + int unsigned ABits; + int unsigned BBits; + } cfg_t; +endpackage + +// Level 3: innermost interface +interface a_if #( + parameter a_p = 0 +)(); + localparam int LP0 = a_p * 2; + typedef logic [LP0-1:0] a_t; +endinterface + +// Level 2: middle interface +interface sct_if #( + parameter scp::cfg_t cfg = 0 +)(); + localparam int LP0 = cfg.ABits * cfg.BBits; + a_if #(LP0) a_if0(); + typedef a_if0.a_t a_t; +endinterface + +// Level 1: outermost interface +interface sc_if #( + parameter scp::cfg_t cfg = 0 +)(); + sct_if #(cfg) types(); + typedef types.a_t a_t; +endinterface + +module sc #(parameter scp::cfg_t cfg=0) ( + sc_if io +); + + typedef io.a_t a_t; + + initial begin + #1; + // cfg.ABits=2, cfg.BBits=3 -> LP0=6 -> a_p=6 -> LP0=12 -> a_t is 12 bits + `checkd(12, $bits(a_t)); + end +endmodule + +module t(); + localparam scp::cfg_t sc_cfg = '{ + ABits : 2, + BBits : 3 + }; + + sc_if #(sc_cfg) sc_io (); + + sc #(sc_cfg) sc( + .io(sc_io) + ); + + initial begin + #2; + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_lparam_dep_iface9.py b/test_regress/t/t_lparam_dep_iface9.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface9.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_lparam_dep_iface9.v b/test_regress/t/t_lparam_dep_iface9.v new file mode 100644 index 000000000..0e994e057 --- /dev/null +++ b/test_regress/t/t_lparam_dep_iface9.v @@ -0,0 +1,70 @@ +// DESCRIPTION: Verilator: Multiple dependent localparams in chain +// +// 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 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package scp; + typedef struct packed { + int unsigned ABits; + int unsigned BBits; + } cfg_t; +endpackage + +// Interface with chained localparam dependencies +interface a_if #( + parameter a_p = 0 +)(); + localparam int LP0 = a_p * 2; // LP0 = a_p * 2 + localparam int LP1 = LP0 + 1; // LP1 = LP0 + 1 + localparam int LP2 = LP1 * LP0; // LP2 = LP1 * LP0 + typedef logic [LP2-1:0] a_t; +endinterface + +interface sc_if #( + parameter scp::cfg_t cfg = 0 +)(); + localparam int LP0 = cfg.ABits * cfg.BBits; + a_if #(LP0) types(); +endinterface + +module sc #(parameter scp::cfg_t cfg=0) ( + sc_if io +); + + typedef io.types.a_t a_t; + + initial begin + #1; + // cfg.ABits=2, cfg.BBits=3 -> LP0=6 + // a_if: a_p=6 -> LP0=12, LP1=13, LP2=156 -> a_t is 156 bits + `checkd(156, $bits(a_t)); + end +endmodule + +module t(); + localparam scp::cfg_t sc_cfg = '{ + ABits : 2, + BBits : 3 + }; + + sc_if #(sc_cfg) sc_io (); + + sc #(sc_cfg) sc( + .io(sc_io) + ); + + initial begin + #2; + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_param_type_from_iface_struct.py b/test_regress/t/t_param_type_from_iface_struct.py new file mode 100755 index 000000000..eee20b937 --- /dev/null +++ b/test_regress/t/t_param_type_from_iface_struct.py @@ -0,0 +1,24 @@ +#!/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 + +# Verifies that skipWidthForTemplateStruct fires in V3Param::cellPinCleanup +# when struct typedefs from a nested parameterized interface are passed as +# type parameters through two levels of interface nesting. + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(v_flags2=["--binary --stats"]) + +test.file_grep(test.stats, r'Param, Template struct width skips\s+(\d+)', 2) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_param_type_from_iface_struct.v b/test_regress/t/t_param_type_from_iface_struct.v new file mode 100644 index 000000000..9628c9748 --- /dev/null +++ b/test_regress/t/t_param_type_from_iface_struct.v @@ -0,0 +1,91 @@ +// DESCRIPTION: Verilator: Test type parameter from interface struct +// +// 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 + +// Exercises skipWidthForTemplateStruct in V3Param::cellPinCleanup. +// +// Pattern: nested parameterized interfaces with struct type parameters: +// 1. Parameterized inner interface (inner_if) defines struct typedefs +// 2. Outer interface (outer_if) contains a nested inner_if instance +// 3. A module takes the outer interface as a port and creates typedefs +// through two levels of nesting: port.inner.req_t +// 4. Those struct typedefs are passed as type parameters to an inner module +// 5. A separate module creates inner_if clones with different configs, +// ensuring inner_if gets parameterizedTemplate()=true before the +// type parameter pins are processed + +package cfg_pkg; + typedef struct packed { + int unsigned IdBits; + int unsigned DataBits; + } cfg_t; +endpackage + +// Parameterized inner interface with struct typedefs +interface inner_if #(parameter cfg_pkg::cfg_t cfg = '0); + typedef struct packed { + logic [cfg.IdBits-1:0] id; + logic [cfg.DataBits-1:0] data; + } req_t; + typedef struct packed { + logic [cfg.IdBits-1:0] id; + logic [1:0] resp; + } resp_t; + req_t req; + resp_t resp; +endinterface + +// Outer interface containing a nested inner_if +interface outer_if #(parameter cfg_pkg::cfg_t cfg = '0); + inner_if #(cfg) inner(); +endinterface + +// Module with type parameters (consumer of struct typedefs) +module typed_mod #( + parameter type req_t = logic, + parameter type resp_t = logic +)( + input logic clk +); + req_t r; + resp_t s; + assign r = '0; + assign s = '0; +endmodule + +// Wrapper: takes outer_if ports, typedefs through two-level nesting, +// passes as type parameters to typed_mod +module wrap_mod #(parameter int NUM = 1)( + input logic clk, + outer_if ports [NUM] +); + typedef ports[0].inner.req_t local_req_t; + typedef ports[0].inner.resp_t local_resp_t; + typed_mod #(.req_t(local_req_t), .resp_t(local_resp_t)) u_sub(.clk(clk)); +endmodule + +module t(); + logic clk = 0; + localparam cfg_pkg::cfg_t CFG_A = '{IdBits: 4, DataBits: 32}; + localparam cfg_pkg::cfg_t CFG_B = '{IdBits: 8, DataBits: 64}; + + // Force inner_if to be cloned with different configs first + inner_if #(CFG_A) early_a(); + inner_if #(CFG_B) early_b(); + assign early_a.req = '0; + assign early_a.resp = '0; + assign early_b.req = '0; + assign early_b.resp = '0; + + outer_if #(CFG_A) io [2] (); + wrap_mod #(.NUM(2)) u_wrap(.clk(clk), .ports(io)); + + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_ascrange_prelim_cfg.py b/test_regress/t/t_paramgraph_ascrange_prelim_cfg.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_ascrange_prelim_cfg.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_ascrange_prelim_cfg.v b/test_regress/t/t_paramgraph_ascrange_prelim_cfg.v new file mode 100644 index 000000000..c1eda693e --- /dev/null +++ b/test_regress/t/t_paramgraph_ascrange_prelim_cfg.v @@ -0,0 +1,44 @@ +// DESCRIPTION: Verilator: Regression for prelim ASCRANGE on cfg-based interface typedefs +// +// 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 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package axis; + typedef struct packed { + int unsigned DataWidth; + } cfg_t; +endpackage + +interface axis_if #(parameter axis::cfg_t cfg = '0)(); + typedef logic [cfg.DataWidth-1:0] tdata_t; +endinterface + +module axis_chan #( + parameter axis::cfg_t chan_cfg = '0 +) (); + axis_if #(chan_cfg) axis_channel_io(); + typedef axis_channel_io.tdata_t data_t; + localparam int kWidth = $bits(data_t); + initial begin + #1; + `checkd(kWidth,32); + end +endmodule + +module t; + localparam axis::cfg_t axis_chan_cfg = '{DataWidth: 32}; + axis_chan #(.chan_cfg(axis_chan_cfg)) u_chan(); + initial begin + #2; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_bisect1.py b/test_regress/t/t_paramgraph_bisect1.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_bisect1.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_bisect1.v b/test_regress/t/t_paramgraph_bisect1.v new file mode 100644 index 000000000..4de603c87 --- /dev/null +++ b/test_regress/t/t_paramgraph_bisect1.v @@ -0,0 +1,116 @@ +// 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 +// +// DESCRIPTION: Minimal test for sibling interface typedef resolution +// This is the SIMPLEST case that demonstrates the t_lparam_dep_iface10 failure pattern: +// - Two sibling cells of the same interface type with DIFFERENT parameters +// - A module that accesses typedefs from BOTH siblings +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package rial; + +// Configuration structure + typedef struct packed { + // CCA Parameters + int unsigned NumDd; + // CC Parameters + int unsigned DDNumStuff; + int unsigned DDNumStuffThreads; + } cfg_t; +endpackage + +package cb; + typedef struct packed { + int unsigned XdatSize; // raw packet data size + } cfg_t; +endpackage + +interface ccia_types_if #(parameter rial::cfg_t cfg=0)(); + + // 'base' types + typedef logic [$clog2(cfg.DDNumStuff)-1:0] wave_index_t; + +// types for tb + typedef struct packed { + logic [3:0] e_cmd; + logic en; + logic csr; + wave_index_t wave_index; + logic [11:0] reg_addr; + logic [64-(4+1+1+$clog2(cfg.DDNumStuff)+12)-1:0] pad0; + } tl_reg_cmd_t; + + typedef struct packed { + logic [63:0] raw; + } tl_addr_cmd_t; + + typedef union packed { + tl_reg_cmd_t rcmd; + tl_addr_cmd_t acmd; + } tl_data_fld_t; + + typedef union packed { + tl_data_fld_t [cfg.DDNumStuffThreads-1:0] d_a; + } cmd_data_t; + + typedef struct packed { + cmd_data_t d; + } cmd_beat_t; + +endinterface + +module rial_top #( + parameter rial::cfg_t aer_cfg=0 +)(); + +// for the types + ccia_types_if #(aer_cfg) ccia_types(); + +// genvars and locally defined types + typedef ccia_types.cmd_beat_t cmd_beat_t; + + // CB and RBUS + localparam cb::cfg_t cb_cfg = '{ + XdatSize:$bits(cmd_beat_t) + }; + + initial begin + #1; + `checkd($bits(ccia_types.tl_data_fld_t), 64); + `checkd($bits(ccia_types.cmd_data_t), 512); + `checkd($bits(cmd_beat_t), 512); + `checkd(cb_cfg.XdatSize, 512); + end + +endmodule + +// SOC Top w/IO and SOC configuration +module rial_wrap(); + + parameter rial::cfg_t aer_cfg = '{ + NumDd : 3, + // CC Parameters + DDNumStuff : 4, + DDNumStuffThreads : 8 + }; + +// DUT + rial_top #( + .aer_cfg(aer_cfg) + ) rial_top(); + + initial begin + #2; + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_paramgraph_bits_corruption.py b/test_regress/t/t_paramgraph_bits_corruption.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_bits_corruption.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_bits_corruption.v b/test_regress/t/t_paramgraph_bits_corruption.v new file mode 100644 index 000000000..e5a34d207 --- /dev/null +++ b/test_regress/t/t_paramgraph_bits_corruption.v @@ -0,0 +1,69 @@ +// 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 +// + +// DESCRIPTION: Minimal test for sibling interface typedef resolution +// This is the SIMPLEST case that demonstrates the t_lparam_dep_iface10 failure pattern: +// - Two sibling cells of the same interface type with DIFFERENT parameters +// - A module that accesses typedefs from BOTH siblings +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package TestPkg; + // Create a struct that results in 525 bits like in aerial_wrap + typedef struct packed { + logic [31:0] field1; + logic [31:0] field2; + logic [31:0] field3; + logic [31:0] field4; + logic [31:0] field5; + logic [31:0] field6; + logic [31:0] field7; + logic [31:0] field8; + logic [31:0] field9; + logic [31:0] field10; + logic [31:0] field11; + logic [31:0] field12; + logic [31:0] field13; + logic [31:0] field14; + logic [31:0] field15; + logic [31:0] field16; + logic [12:0] field17; // 525 bits total (16*32 + 13) + } cmd_beat_t; + + typedef struct packed { + logic [31:0] Rids; + logic [31:0] Pids; + logic [31:0] Fnum; + logic [31:0] XdatSize; // 32-bit field + } cfg_t; + + // This pattern assignment should trigger the error + // The issue is that $bits(cmd_beat_t) evaluation during DepGraph causes corruption + // where the pattern literal gets a 128-bit constant instead of proper 32-bit assignment + // Note: cmd_beat_t is referenced directly, not through a localparam type alias + localparam cfg_t cb_cfg = '{ + Rids : 32'h1, + Pids : 32'h2, + Fnum : 32'h3, + XdatSize : $bits(cmd_beat_t) // Should be 525, but gets corrupted + }; +endpackage + +module TestMod; + import TestPkg::*; + + initial begin + $display("XdatSize = %d", cb_cfg.XdatSize); + `checkd(cb_cfg.XdatSize, 525); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_bits_iface_typedef.py b/test_regress/t/t_paramgraph_bits_iface_typedef.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_bits_iface_typedef.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_bits_iface_typedef.v b/test_regress/t/t_paramgraph_bits_iface_typedef.v new file mode 100644 index 000000000..81cb7e258 --- /dev/null +++ b/test_regress/t/t_paramgraph_bits_iface_typedef.v @@ -0,0 +1,81 @@ +// 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 + +// DESCRIPTION: Test for $bits() of interface typedef used as parameter value +// This reproduces the issue from axis_upsizer.sv:186 +// The issue: $bits(op_pkt_t) where op_pkt_t is a typedef from an interface port +// can't be converted to constant because the PARAMTYPEDTYPE's dtype isn't resolved + +// Interface with a packed struct typedef +interface axis_if #( + parameter int DataWidth = 8 +); + typedef struct packed { + logic [DataWidth-1:0] data; + logic valid; + } pkt_t; + + logic [DataWidth-1:0] tdata; + logic tvalid; + logic tready; + + modport initiator (output tdata, tvalid, input tready); + modport target (input tdata, tvalid, output tready); +endinterface + +// Simple buffer module that takes a width parameter +module skid_buffer #( + parameter int p_width = 8 +) ( + input logic clk, + input logic [p_width-1:0] data_i, + output logic [p_width-1:0] data_o +); + always_ff @(posedge clk) data_o <= data_i; +endmodule + +// Module that uses $bits() of an interface typedef as a parameter +module axis_upsizer #( + parameter int p_has_skid = 1 +) ( + input logic clk, + axis_if.initiator op_io +); + // Typedef from interface port + typedef op_io.pkt_t op_pkt_t; + + op_pkt_t op_pkt_int; + + generate + if (p_has_skid>0) begin : gen_skid + op_pkt_t skid_src_pkt; + + // This is the problematic line - $bits(op_pkt_t) used as parameter + // The PARAMTYPEDTYPE for op_pkt_t has REQUIREDTYPE that needs resolution + skid_buffer #(.p_width($bits(op_pkt_t))) skid ( + .clk(clk), + .data_i(op_pkt_int), + .data_o(skid_src_pkt) + ); + end + endgenerate +endmodule + +module top; + logic clk; + + axis_if #(.DataWidth(32)) op_if(); + + axis_upsizer #(.p_has_skid(1)) u_upsizer ( + .clk(clk), + .op_io(op_if.initiator) + ); + + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_cloned_refdtype.py b/test_regress/t/t_paramgraph_cloned_refdtype.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_cloned_refdtype.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_cloned_refdtype.v b/test_regress/t/t_paramgraph_cloned_refdtype.v new file mode 100644 index 000000000..92700abdc --- /dev/null +++ b/test_regress/t/t_paramgraph_cloned_refdtype.v @@ -0,0 +1,81 @@ +// 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 + +// DESCRIPTION: Verilator: Test for cloned RefDType classOrPackagep fix +// +// This test verifies that when parameterized classes are cloned, the RefDType +// nodes that reference typedefs within the class get their classOrPackagep +// and typedefp updated to point to the cloned class, not the template class. +// +// This file is part of the Verilator regression test suite. +// + +// A registry class that returns its own type +class uvm_object_registry #(type T = int, string Tname = ""); + typedef uvm_object_registry#(T, Tname) this_type; + + static function this_type get(); + static this_type m_inst; + if (m_inst == null) m_inst = new(); + return m_inst; + endfunction +endclass + +// A pool class that has a nested type_id typedef pointing to the registry +// The key pattern: type_id is a typedef to uvm_object_registry parameterized with THIS class +class uvm_object_string_pool #(type T = int); + typedef uvm_object_string_pool#(T) this_type; + typedef uvm_object_registry#(uvm_object_string_pool#(T)) type_id; + + // This function's return type references type_id - after cloning, + // the RefDType for the return type must point to THIS class's type_id, + // not the template class's type_id + static function type_id get_type(); + return type_id::get(); + endfunction + + static function T get_global(); + this_type gpool = new(); + return gpool.get(); + endfunction + + virtual function T get(); + T result; + return result; + endfunction +endclass + +// Simple wrapper classes to create different specializations +class uvm_queue #(type T = int); +endclass + +class uvm_event #(type T = int); +endclass + +// Create two different specializations of uvm_object_string_pool +// Each should get its own type_id pointing to a different registry specialization +typedef uvm_object_string_pool#(uvm_event#(int)) uvm_event_pool; +typedef uvm_object_string_pool#(uvm_queue#(string)) uvm_queue_pool; + +module t; + initial begin + // Get the type_id::get() for both pool types + // Before the fix, both would incorrectly return the same registry type + // After the fix, each returns its own correctly-specialized registry + + uvm_object_registry#(uvm_object_string_pool#(uvm_event#(int))) event_reg; + uvm_object_registry#(uvm_object_string_pool#(uvm_queue#(string))) queue_reg; + + event_reg = uvm_event_pool::get_type(); + queue_reg = uvm_queue_pool::get_type(); + + if (event_reg != null && queue_reg != null) begin + $write("*-* All Coverage Coverage *-*\n"); + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_comined_iface.py b/test_regress/t/t_paramgraph_comined_iface.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_comined_iface.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_comined_iface.v b/test_regress/t/t_paramgraph_comined_iface.v new file mode 100644 index 000000000..8f844903e --- /dev/null +++ b/test_regress/t/t_paramgraph_comined_iface.v @@ -0,0 +1,120 @@ +// 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 +// +// DESCRIPTION: Verilator: TRULY BLENDED test for interface typedef resolution +// This test BLENDS both patterns into a single interacting structure: +// - Sibling cells (like t_lparam_dep_iface10) +// - Nested interface chains (like aerial_wrap) +// - COMBINED: Sibling cells that EACH contain nested interface chains +// +// The key test: A module accesses typedefs from TWO sibling nested interface +// chains, and each must resolve to the correct parameterized type. +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +typedef struct packed { + int unsigned AddrBits; + int unsigned DataBits; + int unsigned IdBits; +} axi_cfg_t; + +// INNERMOST: Parameterized interface with typedefs +interface axi4_if #(parameter axi_cfg_t cfg = 0)(); + localparam int unsigned AddrBits = cfg.AddrBits * 2; + localparam int unsigned DataBits = cfg.DataBits * 2; + localparam int unsigned IdBits = cfg.IdBits * 2; + + typedef logic [AddrBits-1:0] addr_t; + typedef logic [DataBits-1:0] data_t; + typedef logic [IdBits-1:0] id_t; + + typedef struct packed { + id_t id; + addr_t addr; + } ar_chan_t; + + typedef struct packed { + id_t id; + data_t data; + } r_chan_t; + + ar_chan_t ar; + r_chan_t r; +endinterface + +// MIDDLE: Interface that wraps axi4_if and re-exports its typedefs +interface tlb_io_if #(parameter axi_cfg_t axi_cfg = 0)(); + axi4_if #(.cfg(axi_cfg)) axi_tlb_io(); + + // Re-export typedefs from nested interface + typedef axi_tlb_io.r_chan_t r_chan_t; + typedef axi_tlb_io.ar_chan_t ar_chan_t; +endinterface + +// OUTER: Interface with TWO SIBLING tlb_io_if instances with DIFFERENT params +// This is the BLENDED pattern: sibling cells + nested chains +interface cca_io_if #( + parameter axi_cfg_t axi_cfg_a = 0, + parameter axi_cfg_t axi_cfg_b = 0 +)(); + // SIBLING CELLS - same interface type, DIFFERENT params + tlb_io_if #(.axi_cfg(axi_cfg_a)) tlb_io_a(); + tlb_io_if #(.axi_cfg(axi_cfg_b)) tlb_io_b(); + + // Re-export from each sibling (these should be DIFFERENT types) + typedef tlb_io_a.r_chan_t r_chan_a_t; + typedef tlb_io_b.r_chan_t r_chan_b_t; +endinterface + +// MODULE: Accesses typedefs from BOTH sibling nested chains via interface port +// This is the CRITICAL test - must distinguish between tlb_io_a and tlb_io_b +module cca_xbar ( + cca_io_if cca_io +); + // Access typedefs through SIBLING nested interface chains + // These MUST resolve to DIFFERENT types based on the different params + typedef cca_io.tlb_io_a.r_chan_t m_r_chan_a_t; // From axi_cfg_a + typedef cca_io.tlb_io_b.r_chan_t m_r_chan_b_t; // From axi_cfg_b + typedef cca_io.tlb_io_a.ar_chan_t m_ar_chan_a_t; + typedef cca_io.tlb_io_b.ar_chan_t m_ar_chan_b_t; + + m_r_chan_a_t r_data_a; + m_r_chan_b_t r_data_b; + + initial begin + #1; + // axi_cfg_a: AddrBits=32, DataBits=64, IdBits=4 + // r_chan_t = id(4) + data(64) = 68 bits * 2 = 136 bits + // ar_chan_t = id(4) + addr(32) = 36 bits * 2 = 72 bits + `checkd($bits(m_r_chan_a_t), 136); + `checkd($bits(m_ar_chan_a_t), 72); + + // axi_cfg_b: AddrBits=40, DataBits=128, IdBits=8 + // r_chan_t = id(8) + data(128) = 136 bits * 2 = 272 bits + // ar_chan_t = id(8) + addr(40) = 48 bits * 2 = 96 bits + `checkd($bits(m_r_chan_b_t), 272); + `checkd($bits(m_ar_chan_b_t), 96); + end +endmodule + +// TOP MODULE +module t(); + localparam axi_cfg_t cfg_a = '{AddrBits: 32, DataBits: 64, IdBits: 4}; + localparam axi_cfg_t cfg_b = '{AddrBits: 40, DataBits: 128, IdBits: 8}; + + cca_io_if #(.axi_cfg_a(cfg_a), .axi_cfg_b(cfg_b)) cca_io(); + cca_xbar xbar(.cca_io(cca_io)); + + initial begin + #2; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_comined_iface_stats.py b/test_regress/t/t_paramgraph_comined_iface_stats.py new file mode 100755 index 000000000..53dbc323b --- /dev/null +++ b/test_regress/t/t_paramgraph_comined_iface_stats.py @@ -0,0 +1,30 @@ +#!/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 + +# Stats-variant of t_paramgraph_comined_iface: verifies IfaceCapture +# statistics for combined sibling + nested interface typedef patterns. + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_paramgraph_comined_iface.v" + +test.compile(v_flags2=["--binary --stats"]) + +test.file_grep(test.stats, r'IfaceCapture, Entries total\s+(\d+)', 18) +test.file_grep(test.stats, r'IfaceCapture, Entries template\s+(\d+)', 8) +test.file_grep(test.stats, r'IfaceCapture, Entries cloned\s+(\d+)', 10) +test.file_grep(test.stats, r'IfaceCapture, Ledger fixups in V3Param\s+(\d+)', 10) +test.file_grep(test.stats, r'IfaceCapture, Wrong-clone refs fixed\s+(\d+)', 10) +test.file_grep(test.stats, r'IfaceCapture, Dead refs fixed in modules\s+(\d+)', 6) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_iface_array_ports.py b/test_regress/t/t_paramgraph_iface_array_ports.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_array_ports.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_iface_array_ports.v b/test_regress/t/t_paramgraph_iface_array_ports.v new file mode 100644 index 000000000..be8b6d9bf --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_array_ports.v @@ -0,0 +1,56 @@ +// 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 +// +// DESCRIPTION: +// Minimal testcase for depgraph resolution with arrayed interface ports +// and typedefs pulled from interface instances. +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package axi_pkg; + typedef struct packed { + int IdBits; + int DataBits; + int UserBits; + } cfg_t; +endpackage + +interface axi4_if #(parameter axi_pkg::cfg_t cfg = '0)(); + typedef logic [cfg.IdBits-1:0] id_t; + typedef logic [cfg.DataBits-1:0] data_t; + typedef logic [cfg.UserBits-1:0] user_t; + + typedef struct packed { + id_t id; + data_t data; + user_t user; + } req_t; +endinterface + +module sink #(parameter int N = 1)(axi4_if tgt_ports [N-1:0]); + localparam type req_t = tgt_ports[0].req_t; + req_t rq; +endmodule + +module top; + localparam axi_pkg::cfg_t cfg = '{IdBits:4, DataBits:32, UserBits:2}; + axi4_if #(.cfg(cfg)) tgt_ports [1:0](); + + sink #(.N(2)) u_sink(.tgt_ports(tgt_ports)); + + initial begin + #1; + `checkd($bits(tgt_ports[0].id_t), 4); + `checkd($bits(tgt_ports[0].data_t), 32); + `checkd($bits(tgt_ports[0].user_t), 2); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_iface_cfg_zero.py b/test_regress/t/t_paramgraph_iface_cfg_zero.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_cfg_zero.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_iface_cfg_zero.v b/test_regress/t/t_paramgraph_iface_cfg_zero.v new file mode 100644 index 000000000..5efae55e5 --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_cfg_zero.v @@ -0,0 +1,49 @@ +// 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 + +// DESCRIPTION: +// Minimal testcase for depgraph interface typedef resolution +// Derived from aicc_types_if/axis_if ASCRANGE warnings +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package aerial; + typedef struct packed { + int NumCc; + int CCNumWaves; + int CCNumIds; + } cfg_t; +endpackage + +interface aicc_types_if #(parameter aerial::cfg_t cfg = '0)(); + typedef logic [$clog2(cfg.NumCc)-1:0] cc_index_t; + typedef logic [$clog2(cfg.CCNumIds)-1:0] trans_id_t; +endinterface + +module child(aicc_types_if types); + localparam type cc_index_t = types.cc_index_t; + localparam type trans_id_t = types.trans_id_t; + cc_index_t cc_idx; + trans_id_t tr_id; +endmodule + +module top; + localparam aerial::cfg_t aer_cfg = '{NumCc:4, CCNumWaves:2, CCNumIds:8}; + aicc_types_if #(.cfg(aer_cfg)) types(); + child u_child(.types(types)); + + initial begin + #2; + `checkd($bits(types.cc_index_t), 2); + `checkd($bits(types.trans_id_t), 3); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_iface_deadmod.py b/test_regress/t/t_paramgraph_iface_deadmod.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_deadmod.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_iface_deadmod.v b/test_regress/t/t_paramgraph_iface_deadmod.v new file mode 100644 index 000000000..e09cb446a --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_deadmod.v @@ -0,0 +1,61 @@ +// 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 +// +// DESCRIPTION: +// Minimal testcase to see if unused modules with interface ports +// still trigger ASCRANGE when interface params are defaulted. +// + +package axi4; + typedef struct packed { + int IdBits; + int AddrBits; + int DataBits; + int UserBits; + } cfg_t; +endpackage + +interface axi4_if #(parameter axi4::cfg_t cfg = '0)(); + typedef logic [cfg.AddrBits-1:0] addr_t; + typedef logic [cfg.DataBits-1:0] data_t; + typedef logic [cfg.DataBits/8-1:0] strb_t; + typedef logic [cfg.UserBits-1:0] user_t; + typedef logic [cfg.IdBits-1:0] id_t; + + typedef struct packed { + id_t id; + addr_t addr; + user_t user; + } aw_chan_t; +endinterface + +module dead_mod( + axi4_if axi_io +); + typedef axi_io.addr_t addr_t; + typedef axi_io.data_t data_t; + typedef axi_io.strb_t strb_t; + + addr_t addr_d; + data_t data_d; + strb_t strb_d; +endmodule + +module dead_top; + localparam axi4::cfg_t cfg = '{IdBits:4, AddrBits:32, DataBits:64, UserBits:2}; + axi4_if #(.cfg(cfg)) axi_io(); + + dead_mod u_dead(.axi_io(axi_io)); +endmodule + +module top; + dead_top dead_top(); + initial begin + #1; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_iface_dependency1.py b/test_regress/t/t_paramgraph_iface_dependency1.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_dependency1.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_iface_dependency1.v b/test_regress/t/t_paramgraph_iface_dependency1.v new file mode 100644 index 000000000..a1dcc8e6e --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_dependency1.v @@ -0,0 +1,55 @@ +// 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 +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package a_pkg; + typedef struct packed { + int unsigned a; + } cfg_t; +endpackage + +interface depgraph_if #(a_pkg::cfg_t cfg=0)(); + typedef logic [cfg.a-1:0] byte_t; + typedef struct packed { + byte_t a; + } pair_t; +endinterface + +module a_mod( + depgraph_if ifc +); + typedef ifc.pair_t pair_t; + + localparam p_a = $bits(pair_t); + + initial begin + #1; + `checkd($bits(pair_t),8); + `checkd(p_a, 8); + end +endmodule + +module t(); + localparam a_pkg::cfg_t cfg = '{ + a: 8 + }; + + depgraph_if #(cfg) ifc(); + a_mod #() a_mod_0( + .ifc(ifc) + ); + + initial begin + #2; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_iface_dependency2.py b/test_regress/t/t_paramgraph_iface_dependency2.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_dependency2.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_iface_dependency2.v b/test_regress/t/t_paramgraph_iface_dependency2.v new file mode 100644 index 000000000..5f8160bbc --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_dependency2.v @@ -0,0 +1,57 @@ +// 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 +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package a_pkg; + typedef struct packed { + int unsigned a; + } cfg_t; +endpackage + +package b_pkg; + typedef struct packed { + int unsigned a; + } cfg_t; +endpackage + +interface depgraph_if #(a_pkg::cfg_t cfg=0)(); + typedef logic [cfg.a-1:0] byte_t; + typedef logic [cfg.a*2-1:0] half_t; + typedef struct packed { + byte_t a; + half_t b; + } pair_t; + typedef union packed { + pair_t p; + logic [23:0] flat; + } pair_u_t; +endinterface + +module t(); + localparam a_pkg::cfg_t cfg = '{ + a: 8 + }; + + depgraph_if #(cfg) ifc(); + + typedef ifc.pair_u_t pair_u_t; + + localparam b_pkg::cfg_t cfg_b = '{ + a:$bits(pair_u_t) + }; + + initial begin + #2; + `checkd(cfg_b.a, 24); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_iface_dependency3.py b/test_regress/t/t_paramgraph_iface_dependency3.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_dependency3.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_iface_dependency3.v b/test_regress/t/t_paramgraph_iface_dependency3.v new file mode 100644 index 000000000..266e56b95 --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_dependency3.v @@ -0,0 +1,74 @@ +// 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 +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package a_pkg; + typedef struct packed { + int unsigned a; + } cfg_t; +endpackage + +package b_pkg; + typedef struct packed { + int unsigned a; + } cfg_t; +endpackage + +interface depgraph_if #(a_pkg::cfg_t cfg=0)(); + typedef logic [cfg.a-1:0] byte_t; + typedef logic [cfg.a*2-1:0] half_t; + typedef struct packed { + byte_t a; + half_t b; + } pair_t; +endinterface + +module a_mod( + depgraph_if ifc +); + typedef ifc.pair_t pair_t; + typedef ifc.half_t half_t; + + localparam p_a = $bits(pair_t); + localparam p_b = $bits(half_t); + + initial begin + #1; + `checkd($bits(pair_t),24); + `checkd(p_a, 24); + `checkd(p_b, 16); + end +endmodule + +module t(); + localparam a_pkg::cfg_t cfg = '{ + a: 8 + }; + + depgraph_if #(cfg) ifc(); + + typedef ifc.byte_t byte_t; + + a_mod #() a_mod_0( + .ifc(ifc) + ); + + localparam b_pkg::cfg_t cfg_b = '{ + a:$bits(byte_t) + }; + + initial begin + #2; + `checkd(cfg_b.a, 8); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_iface_param_from_port.py b/test_regress/t/t_paramgraph_iface_param_from_port.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_param_from_port.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_iface_param_from_port.v b/test_regress/t/t_paramgraph_iface_param_from_port.v new file mode 100644 index 000000000..a6f664b38 --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_param_from_port.v @@ -0,0 +1,66 @@ +// 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 +// +// DESCRIPTION: +// Regression for interface instances parameterized by interface-port params. +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package cfg_pkg; + typedef struct packed { + int DataWidth; + int IdWidth; + } cfg_t; +endpackage + +interface types_if #(parameter cfg_pkg::cfg_t cfg = '0)(); + typedef logic [cfg.DataWidth-1:0] data_t; + typedef logic [cfg.IdWidth-1:0] id_t; +endinterface + +interface bus_if #(parameter cfg_pkg::cfg_t cfg = '0)(); + types_if #(cfg) types(); + typedef types.data_t data_t; + typedef types.id_t id_t; +endinterface + +module child(bus_if io, output int data_w, output int id_w); + types_if #(io.cfg) port_types(); + typedef port_types.data_t p_data_t; + typedef port_types.id_t p_id_t; + assign data_w = $bits(p_data_t); + assign id_w = $bits(p_id_t); +endmodule + +module top; + localparam cfg_pkg::cfg_t cfg0 = '{DataWidth:32, IdWidth:4}; + localparam cfg_pkg::cfg_t cfg1 = '{DataWidth:64, IdWidth:6}; + + bus_if #(cfg0) bus0(); + bus_if #(cfg1) bus1(); + + int data_w0; + int id_w0; + int data_w1; + int id_w1; + + child u0(.io(bus0), .data_w(data_w0), .id_w(id_w0)); + child u1(.io(bus1), .data_w(data_w1), .id_w(id_w1)); + + initial begin + #1; + `checkd(data_w0, 32); + `checkd(id_w0, 4); + `checkd(data_w1, 64); + `checkd(id_w1, 6); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_iface_pin.py b/test_regress/t/t_paramgraph_iface_pin.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_pin.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_iface_pin.v b/test_regress/t/t_paramgraph_iface_pin.v new file mode 100644 index 000000000..7d1c3d6ab --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_pin.v @@ -0,0 +1,62 @@ +// 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 +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package a_pkg; + typedef struct packed { + int unsigned a; + } cfg_t; +endpackage + +interface depgraph_if #(a_pkg::cfg_t cfg=0)(); + typedef logic [cfg.a-1:0] byte_t; + typedef logic [2*cfg.a-1:0] half_t; + typedef struct packed { + byte_t a; + half_t b; + } pair_t; +endinterface + +module a_mod( + depgraph_if ifc +); + typedef ifc.byte_t byte_t; + typedef ifc.pair_t pair_t; + + pair_t p; + logic [23:0] flat; + + assign flat = {p.a, p.b}; + + initial begin + #1; + `checkd($bits(byte_t), 8); + `checkd($bits(pair_t),24); + `checkd($bits(flat), 24); + end +endmodule + +module t(); + localparam a_pkg::cfg_t cfg = '{ + a:8 + }; + + depgraph_if #(cfg) ifc(); + a_mod #() a_mod_0( + .ifc(ifc) + ); + + initial begin + #2; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_iface_port_typedef.py b/test_regress/t/t_paramgraph_iface_port_typedef.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_port_typedef.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_iface_port_typedef.v b/test_regress/t/t_paramgraph_iface_port_typedef.v new file mode 100644 index 000000000..99d9050e8 --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_port_typedef.v @@ -0,0 +1,53 @@ +// 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 +// +// DESCRIPTION: +// Minimal testcase for depgraph interface typedef resolution through +// interface ports connected to specialized interface instances. +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package acme_pkg; + typedef struct packed { + int DataBits; + } cfg_t; +endpackage + +interface acme_if #(parameter acme_pkg::cfg_t cfg = '0)(); + typedef logic [cfg.DataBits-1:0] data_t; +endinterface + +module child(acme_if io, output int width_o); + typedef io.data_t data_t; + data_t payload; + assign width_o = $bits(data_t); +endmodule + +module top; + localparam acme_pkg::cfg_t cfg0 = '{DataBits:32}; + localparam acme_pkg::cfg_t cfg1 = '{DataBits:64}; + + acme_if #(cfg0) io0(); + acme_if #(cfg1) io1(); + + int width0; + int width1; + + child u0(.io(io0), .width_o(width0)); + child u1(.io(io1), .width_o(width1)); + + initial begin + #1; + `checkd(width0, 32); + `checkd(width1, 64); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_iface_template_mismatch.py b/test_regress/t/t_paramgraph_iface_template_mismatch.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_template_mismatch.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_iface_template_mismatch.v b/test_regress/t/t_paramgraph_iface_template_mismatch.v new file mode 100644 index 000000000..92e39e58a --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_template_mismatch.v @@ -0,0 +1,68 @@ +// 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 +// +// DESCRIPTION: +// Minimal testcase for depgraph interface typedef mismatch between +// template and specialized interface instances. +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package axi4l; + typedef struct packed { + int AddrBits; + int DataBits; + int UserBits; + } cfg_t; +endpackage + +interface axi4l_if #(parameter axi4l::cfg_t cfg = '0)(); + typedef logic [cfg.AddrBits-1:0] addr_t; + typedef logic [cfg.DataBits-1:0] data_t; + typedef logic [cfg.DataBits/8-1:0] strb_t; + typedef logic [cfg.UserBits-1:0] user_t; +endinterface + +module ccom_to_axi( + axi4l_if axil_tgt_io +); + typedef axil_tgt_io.addr_t addr_t; + typedef axil_tgt_io.data_t data_t; + typedef axil_tgt_io.strb_t strb_t; + + addr_t addr_q; + data_t data_q; + strb_t strb_q; +endmodule + +module dummy_consumer(axi4l_if axil_io); + typedef axil_io.data_t data_t; + data_t sink; +endmodule + +module top; + localparam axi4l::cfg_t cfg = '{AddrBits:32, DataBits:64, UserBits:2}; + + // Live specialized instance used elsewhere. + axi4l_if #(.cfg(cfg)) axil_live(); + dummy_consumer u_consume(.axil_io(axil_live)); + + // Template/default instance used in ccom_to_axi. + axi4l_if #(cfg) axil_tgt_io(); + ccom_to_axi u_ccom_to_axi(.axil_tgt_io(axil_tgt_io)); + + initial begin + #1; + `checkd($bits(axil_live.addr_t), 32); + `checkd($bits(axil_live.data_t), 64); + `checkd($bits(axil_live.strb_t), 8); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_iface_template_mismatch2.py b/test_regress/t/t_paramgraph_iface_template_mismatch2.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_template_mismatch2.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_iface_template_mismatch2.v b/test_regress/t/t_paramgraph_iface_template_mismatch2.v new file mode 100644 index 000000000..394354d4a --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_template_mismatch2.v @@ -0,0 +1,68 @@ +// 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 +// +// DESCRIPTION: +// Variant testcase that avoids ASCRANGE by giving the template interface +// a nonzero default, while still detecting template-vs-specialized mixups. +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package axi4l; + typedef struct packed { + int AddrBits; + int DataBits; + int UserBits; + } cfg_t; +endpackage + +interface axi4l_if #(parameter axi4l::cfg_t cfg = '{AddrBits:4, DataBits:16, UserBits:1})(); + typedef logic [cfg.AddrBits-1:0] addr_t; + typedef logic [cfg.DataBits-1:0] data_t; + typedef logic [cfg.DataBits/8-1:0] strb_t; + typedef logic [cfg.UserBits-1:0] user_t; +endinterface + +module ccom_to_axi( + axi4l_if axil_tgt_io +); + typedef axil_tgt_io.addr_t addr_t; + typedef axil_tgt_io.data_t data_t; + typedef axil_tgt_io.strb_t strb_t; + + addr_t addr_q; + data_t data_q; + strb_t strb_q; +endmodule + +module dummy_consumer(axi4l_if axil_io); + typedef axil_io.data_t data_t; + data_t sink; +endmodule + +module top; + localparam axi4l::cfg_t cfg = '{AddrBits:32, DataBits:64, UserBits:2}; + + // Live specialized instance used elsewhere. + axi4l_if #(.cfg(cfg)) axil_live(); + dummy_consumer u_consume(.axil_io(axil_live)); + + // Template/default instance used in ccom_to_axi. + axi4l_if #(cfg) axil_tgt_io(); + ccom_to_axi u_ccom_to_axi(.axil_tgt_io(axil_tgt_io)); + + initial begin + #1; + `checkd($bits(axil_live.addr_t), 32); + `checkd($bits(axil_live.data_t), 64); + `checkd($bits(axil_live.strb_t), 8); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_iface_template_mismatch3.py b/test_regress/t/t_paramgraph_iface_template_mismatch3.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_template_mismatch3.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_iface_template_mismatch3.v b/test_regress/t/t_paramgraph_iface_template_mismatch3.v new file mode 100644 index 000000000..2ab9ca006 --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_template_mismatch3.v @@ -0,0 +1,59 @@ +// 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 +// +// DESCRIPTION: +// Minimal testcase for depgraph interface typedef mismatch between +// template and specialized interface instances. +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package axi4l; + typedef struct packed { + int DataBits; + } cfg_t; +endpackage + +interface axi4l_if #(parameter axi4l::cfg_t cfg = '0)(); + typedef logic [cfg.DataBits-1:0] data_t; +endinterface + +module ccom_to_axi( + axi4l_if axil_tgt_io +); + typedef axil_tgt_io.data_t data_t; + + data_t data_q; +endmodule + +module dummy_consumer(axi4l_if axil_io); + typedef axil_io.data_t data_t; + data_t sink; +endmodule + +module top; + localparam axi4l::cfg_t cfg = '{ + DataBits:64//, + }; + + // Live specialized instance used elsewhere. + axi4l_if #(.cfg(cfg)) axil_live(); + dummy_consumer u_consume(.axil_io(axil_live)); + + // Template/default instance used in ccom_to_axi. + axi4l_if #(cfg) axil_tgt_io(); + ccom_to_axi u_ccom_to_axi(.axil_tgt_io(axil_tgt_io)); + + initial begin + #1; + `checkd($bits(axil_live.data_t), 64); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_iface_template_nested.py b/test_regress/t/t_paramgraph_iface_template_nested.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_template_nested.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_iface_template_nested.v b/test_regress/t/t_paramgraph_iface_template_nested.v new file mode 100644 index 000000000..ea2c99090 --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_template_nested.v @@ -0,0 +1,72 @@ +// 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 +// +// DESCRIPTION: +// Minimal testcase for depgraph interface typedef resolution with +// deep interface nesting and specialized parameter overrides. +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package acme_pkg; + typedef struct packed { + int DataBits; + } cfg_t; +endpackage + +interface acme_types_if #(parameter acme_pkg::cfg_t cfg = '0)(); + typedef logic [cfg.DataBits-1:0] data_t; +endinterface + +interface acme_tb_if #(parameter acme_pkg::cfg_t cfg = '0)(); + acme_types_if #(cfg) acme_types(); + typedef acme_types.data_t data_t; + data_t payload; +endinterface + +interface acme_if #(parameter acme_pkg::cfg_t cfg = '0)(); + acme_tb_if #(cfg) rq_tb_io_i(); + acme_types_if #(cfg) acme_types(); + typedef acme_types.data_t data_t; + data_t passthru; +endinterface + +interface acme_wrap_if #(parameter acme_pkg::cfg_t cfg = '0)(); + acme_if #(cfg) acme_io(); + typedef acme_io.data_t data_t; + data_t leaf; +endinterface + +module consumer(acme_wrap_if wrap_io); + typedef wrap_io.data_t data_t; + data_t sink; +endmodule + +module top; + localparam acme_pkg::cfg_t cfg0 = '{ + DataBits:64 + }; + localparam acme_pkg::cfg_t cfg1 = '{ + DataBits:128 + }; + + acme_wrap_if #(cfg0) wrap0(); + acme_wrap_if #(cfg1) wrap1(); + + consumer u_consume0(.wrap_io(wrap0)); + consumer u_consume1(.wrap_io(wrap1)); + + initial begin + #1; + `checkd($bits(wrap0.data_t), 64); + `checkd($bits(wrap1.data_t), 128); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_iface_template_nested_stats.py b/test_regress/t/t_paramgraph_iface_template_nested_stats.py new file mode 100755 index 000000000..d2e824e70 --- /dev/null +++ b/test_regress/t/t_paramgraph_iface_template_nested_stats.py @@ -0,0 +1,30 @@ +#!/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 + +# Stats-variant of t_paramgraph_iface_template_nested: verifies IfaceCapture +# statistics for deeply nested interface typedef chains with clone propagation. + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_paramgraph_iface_template_nested.v" + +test.compile(v_flags2=["--binary --stats"]) + +test.file_grep(test.stats, r'IfaceCapture, Entries total\s+(\d+)', 21) +test.file_grep(test.stats, r'IfaceCapture, Entries template\s+(\d+)', 11) +test.file_grep(test.stats, r'IfaceCapture, Entries cloned\s+(\d+)', 10) +test.file_grep(test.stats, r'IfaceCapture, Ledger fixups in V3Param\s+(\d+)', 8) +test.file_grep(test.stats, r'IfaceCapture, Wrong-clone refs fixed\s+(\d+)', 8) +test.file_grep(test.stats, r'IfaceCapture, Dead refs fixed in modules\s+(\d+)', 3) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_member_refdtype.py b/test_regress/t/t_paramgraph_member_refdtype.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_member_refdtype.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_member_refdtype.v b/test_regress/t/t_paramgraph_member_refdtype.v new file mode 100644 index 000000000..de05b93d5 --- /dev/null +++ b/test_regress/t/t_paramgraph_member_refdtype.v @@ -0,0 +1,34 @@ +// 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 +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +typedef logic [7:0] byte_t; + +typedef struct packed { + byte_t a; + byte_t b; +} pair_t; + +module t_paramgraph_member_refdtype; + pair_t p; + logic [15:0] flat; + + assign flat = {p.a, p.b}; + + initial begin + #1; + `checkd($bits(byte_t), 8); + `checkd($bits(pair_t), 16); + `checkd($bits(flat), 16); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_member_refdtype_iface_chain.py b/test_regress/t/t_paramgraph_member_refdtype_iface_chain.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_member_refdtype_iface_chain.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_member_refdtype_iface_chain.v b/test_regress/t/t_paramgraph_member_refdtype_iface_chain.v new file mode 100644 index 000000000..3ca551d2b --- /dev/null +++ b/test_regress/t/t_paramgraph_member_refdtype_iface_chain.v @@ -0,0 +1,40 @@ +// 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 +// +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +interface depgraph_if; + typedef logic [7:0] byte_t; +endinterface + +module t_paramgraph_member_refdtype_iface_chain; + depgraph_if ifc(); + + typedef ifc.byte_t byte_t; + typedef byte_t byte_t2; + typedef struct packed { + byte_t2 a; + byte_t2 b; + } pair_t; + + pair_t p; + logic [15:0] flat; + + assign flat = {p.a, p.b}; + + initial begin + #1; + `checkd($bits(byte_t), 8); + `checkd($bits(byte_t2), 8); + `checkd($bits(pair_t), 16); + `checkd($bits(flat), 16); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_member_refdtype_iface_struct.py b/test_regress/t/t_paramgraph_member_refdtype_iface_struct.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_member_refdtype_iface_struct.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_member_refdtype_iface_struct.v b/test_regress/t/t_paramgraph_member_refdtype_iface_struct.v new file mode 100644 index 000000000..f38a8a755 --- /dev/null +++ b/test_regress/t/t_paramgraph_member_refdtype_iface_struct.v @@ -0,0 +1,40 @@ +// 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 +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +interface depgraph_if; + typedef logic [7:0] byte_t; + typedef struct packed { + byte_t a; + byte_t b; + } pair_t; +endinterface + +module t_paramgraph_member_refdtype_iface_struct; + depgraph_if ifc(); + + typedef ifc.byte_t byte_t; + typedef ifc.pair_t pair_t; + + pair_t p; + logic [15:0] flat; + + assign flat = {p.a, p.b}; + + initial begin + #1; + `checkd($bits(byte_t), 8); + `checkd($bits(pair_t), 16); + `checkd($bits(flat), 16); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_member_refdtype_iface_typedef.py b/test_regress/t/t_paramgraph_member_refdtype_iface_typedef.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_member_refdtype_iface_typedef.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_member_refdtype_iface_typedef.v b/test_regress/t/t_paramgraph_member_refdtype_iface_typedef.v new file mode 100644 index 000000000..dfd79a819 --- /dev/null +++ b/test_regress/t/t_paramgraph_member_refdtype_iface_typedef.v @@ -0,0 +1,39 @@ +// 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 +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +interface depgraph_if; + typedef logic [7:0] byte_t; +endinterface + +module t_paramgraph_member_refdtype_iface_typedef; + depgraph_if ifc(); + + typedef ifc.byte_t byte_t; + typedef struct packed { + byte_t a; + byte_t b; + } pair_t; + + pair_t p; + logic [15:0] flat; + + assign flat = {p.a, p.b}; + + initial begin + #1; + `checkd($bits(byte_t), 8); + `checkd($bits(pair_t), 16); + `checkd($bits(flat), 16); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_member_refdtype_pkg_iface.py b/test_regress/t/t_paramgraph_member_refdtype_pkg_iface.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_member_refdtype_pkg_iface.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_member_refdtype_pkg_iface.v b/test_regress/t/t_paramgraph_member_refdtype_pkg_iface.v new file mode 100644 index 000000000..8297b7179 --- /dev/null +++ b/test_regress/t/t_paramgraph_member_refdtype_pkg_iface.v @@ -0,0 +1,50 @@ +// 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 +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package paramgraph_pkg; + typedef struct packed { + int unsigned a; + } cfg_t; +endpackage + +interface paramgraph_if #(paramgraph_pkg::cfg_t cfg=0)(); + typedef logic [cfg.a-1:0] byte_t; + typedef struct packed { + byte_t a; + byte_t b; + } pair_t; +endinterface + +module t_paramgraph_member_refdtype_pkg_iface; + localparam paramgraph_pkg::cfg_t cfg = '{ + a: 8 + }; + + paramgraph_if #(cfg) ifc(); + + typedef ifc.byte_t byte_t; + typedef ifc.pair_t pair_t; + + pair_t p; + logic [15:0] flat; + + assign flat = {p.a, p.b}; + + initial begin + #1; + `checkd($bits(byte_t), 8); + `checkd($bits(pair_t), 16); + `checkd($bits(flat), 16); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_minimal_sibling.py b/test_regress/t/t_paramgraph_minimal_sibling.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_minimal_sibling.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_minimal_sibling.v b/test_regress/t/t_paramgraph_minimal_sibling.v new file mode 100644 index 000000000..e3d8cd042 --- /dev/null +++ b/test_regress/t/t_paramgraph_minimal_sibling.v @@ -0,0 +1,62 @@ +// 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 +// + +// DESCRIPTION: Minimal test for sibling interface typedef resolution +// This is the SIMPLEST case that demonstrates the t_lparam_dep_iface10 failure pattern: +// - Two sibling cells of the same interface type with DIFFERENT parameters +// - A module that accesses typedefs from BOTH siblings +// + +`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); + +// Parameterized interface with a typedef that depends on the parameter +interface a_if #(parameter int WIDTH = 8)(); + localparam LP_WIDTH = WIDTH*2; + typedef logic [LP_WIDTH-1:0] data_t; + data_t data; +endinterface + +// Wrapper interface with TWO SIBLING instances of a_if with DIFFERENT widths +interface wrapper_if #(parameter int WIDTH_A = 8, parameter int WIDTH_B = 16)(); + a_if #(.WIDTH(WIDTH_A)) a_inst(); + a_if #(.WIDTH(WIDTH_B)) b_inst(); + + // Re-export typedefs from each sibling + typedef a_inst.data_t a_data_t; + typedef b_inst.data_t b_data_t; +endinterface + +// Module that accesses typedefs from BOTH siblings via interface port +module consumer ( + wrapper_if wif +); + // These MUST resolve to DIFFERENT types + typedef wif.a_inst.data_t local_a_t; // Should be 10 bits + typedef wif.b_inst.data_t local_b_t; // Should be 20 bits + + local_a_t val_a; + local_b_t val_b; + + initial begin + #1; + `checkd($bits(local_a_t), 20); + `checkd($bits(local_b_t), 40); + end +endmodule + +// Top module +module t(); + wrapper_if #(.WIDTH_A(10), .WIDTH_B(20)) wif(); + consumer c(.wif(wif)); + + initial begin + #2; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_nested_iface_typedef.py b/test_regress/t/t_paramgraph_nested_iface_typedef.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_nested_iface_typedef.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_nested_iface_typedef.v b/test_regress/t/t_paramgraph_nested_iface_typedef.v new file mode 100644 index 000000000..77cf1ca1b --- /dev/null +++ b/test_regress/t/t_paramgraph_nested_iface_typedef.v @@ -0,0 +1,122 @@ +// 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 +// + +// DESCRIPTION: Verilator: Test nested interface typedef access +// This replicates the pattern from a much larger design that was +// failing with the localparam changes - accessing a typedef from +// a doubly-nested interface +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +typedef struct packed { + int unsigned AddrBits; + int unsigned DataBits; + int unsigned IdBits; + int unsigned UserBits; +} axi_cfg_t; + +// Innermost interface - like axi4_if.sv in the real design +interface axi4_if #(parameter axi_cfg_t cfg = '{32, 64, 4, 0})(); + typedef logic [cfg.AddrBits-1:0] addr_t; + typedef logic [cfg.DataBits-1:0] data_t; + typedef logic [cfg.IdBits-1:0] id_t; + typedef logic [cfg.UserBits-1:0] user_t; + typedef logic [cfg.DataBits/8-1:0] strb_t; + + // AXI channel typedef + typedef struct packed { + id_t id; + addr_t addr; + logic [7:0] len; + } ar_chan_t; + + typedef struct packed { + id_t id; + data_t data; + logic [1:0] resp; + logic last; + } r_chan_t; + + ar_chan_t ar; + r_chan_t r; +endinterface + +// Middle interface - wraps the AXI interface +interface tlb_io_if #(parameter axi_cfg_t axi_cfg = '{32, 64, 4, 0})(); + axi4_if #(.cfg(axi_cfg)) axi_tlb_io(); + + // Capture typedef from nested interface + typedef axi_tlb_io.r_chan_t r_chan_t; + typedef axi_tlb_io.ar_chan_t ar_chan_t; +endinterface + +// Outer interface - contains the middle interface +interface cca_io_if #(parameter axi_cfg_t axi_cfg = '{32, 64, 4, 0})(); + tlb_io_if #(.axi_cfg(axi_cfg)) tlb_io(); + + // Capture typedef from doubly-nested interface + typedef tlb_io.r_chan_t r_chan_t; + typedef tlb_io.ar_chan_t ar_chan_t; +endinterface + +// Module that uses the doubly-nested typedef - this is where the error occurred +module cca_xbar #(parameter axi_cfg_t axi_cfg = '{32, 64, 4, 0})( + cca_io_if cca_io +); + // This line was causing "Internal Error: Unlinked" before the fix + // because cca_io.tlb_io.r_chan_t references a typedef from a nested interface + typedef cca_io.tlb_io.r_chan_t m_r_chan_t; + typedef cca_io.tlb_io.ar_chan_t m_ar_chan_t; + + m_r_chan_t r_data; + m_ar_chan_t ar_data; + + initial begin + // Verify the typedef resolved correctly - compare against the actual interface signal + `checkd($bits(m_r_chan_t), $bits(cca_io.tlb_io.axi_tlb_io.r)); + `checkd($bits(m_ar_chan_t), $bits(cca_io.tlb_io.axi_tlb_io.ar)); + + // Verify width matches expected formula based on axi_cfg parameter + // r_chan_t = id(IdBits) + data(DataBits) + resp(2) + last(1) + // ar_chan_t = id(IdBits) + addr(AddrBits) + len(8) + `checkd($bits(m_r_chan_t), axi_cfg.IdBits + axi_cfg.DataBits + 2 + 1); + `checkd($bits(m_ar_chan_t), axi_cfg.IdBits + axi_cfg.AddrBits + 8); + end +endmodule + +module t(); + localparam axi_cfg_t cfg1 = '{AddrBits: 32, DataBits: 64, IdBits: 4, UserBits: 2}; + localparam axi_cfg_t cfg2 = '{AddrBits: 40, DataBits: 128, IdBits: 8, UserBits: 4}; + + // Instantiate outer interface + cca_io_if #(.axi_cfg(cfg1)) cca_io1(); + cca_io_if #(.axi_cfg(cfg2)) cca_io2(); + + // Instantiate modules that use doubly-nested typedefs + cca_xbar #(.axi_cfg(cfg1)) xbar1(.cca_io(cca_io1)); + cca_xbar #(.axi_cfg(cfg2)) xbar2(.cca_io(cca_io2)); + + // Also test direct typedef access in top module + typedef cca_io1.tlb_io.r_chan_t top_r_chan_t; + typedef cca_io2.tlb_io.ar_chan_t top_ar_chan_t; + + initial begin + #1; + // cfg1: DataBits=64, IdBits=4 -> r_chan_t = 4+64+2+1 = 71 + `checkd($bits(top_r_chan_t), 71); + // cfg2: AddrBits=40, IdBits=8 -> ar_chan_t = 8+40+8 = 56 + `checkd($bits(top_ar_chan_t), 56); + + #1; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_nested_iface_typedef_stats.py b/test_regress/t/t_paramgraph_nested_iface_typedef_stats.py new file mode 100755 index 000000000..4bcad9664 --- /dev/null +++ b/test_regress/t/t_paramgraph_nested_iface_typedef_stats.py @@ -0,0 +1,30 @@ +#!/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 + +# Stats-variant of t_paramgraph_nested_iface_typedef: verifies IfaceCapture +# statistics for nested interface typedefs with dead-ref fixup and clone propagation. + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_paramgraph_nested_iface_typedef.v" + +test.compile(v_flags2=["--binary --stats"]) + +test.file_grep(test.stats, r'IfaceCapture, Entries total\s+(\d+)', 18) +test.file_grep(test.stats, r'IfaceCapture, Entries template\s+(\d+)', 8) +test.file_grep(test.stats, r'IfaceCapture, Entries cloned\s+(\d+)', 10) +test.file_grep(test.stats, r'IfaceCapture, Ledger fixups in V3Param\s+(\d+)', 12) +test.file_grep(test.stats, r'IfaceCapture, Wrong-clone refs fixed\s+(\d+)', 12) +test.file_grep(test.stats, r'IfaceCapture, Dead refs fixed in modules\s+(\d+)', 10) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_param_not_const.py b/test_regress/t/t_paramgraph_param_not_const.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_param_not_const.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_param_not_const.v b/test_regress/t/t_paramgraph_param_not_const.v new file mode 100644 index 000000000..51569f711 --- /dev/null +++ b/test_regress/t/t_paramgraph_param_not_const.v @@ -0,0 +1,78 @@ +// 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 +// + +// DESCRIPTION: Verilator: Test nested interface typedef access +// This replicates the pattern from a much larger design that was +// failing with the localparam changes - accessing a typedef from +// a doubly-nested interface +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package cb; + typedef struct packed { + logic [31:0] Rids; + logic [31:0] Pids; + logic [31:0] Fnum; + logic [31:0] XdatSize; + } cfg_t; +endpackage + +package a_pkg; + typedef struct packed { + int unsigned p_a; + int unsigned p_b; + } cfg_t; +endpackage + +interface other_types_if #(parameter a_pkg::cfg_t cfg=0)(); + // Create a struct that results in 525 bits + typedef struct packed { + logic [cfg.p_a-1:0] field1; + logic [cfg.p_b-1:0] field2; + } cmd_beat_t; + +endinterface + +// Simple interface that takes a parameter +interface simple_if #(parameter cb::cfg_t cfg=0)(); + logic [cfg.Rids-1:0] rids; + logic [cfg.Pids-1:0] pids; + logic [cfg.Fnum-1:0] fnum; + logic [cfg.XdatSize-1:0] xdat; +endinterface + +module TestMod; + localparam a_pkg::cfg_t ot_cfg = '{ + p_a : 8, + p_b : 4 + }; + + other_types_if #(ot_cfg) other_types(); + + typedef other_types.cmd_beat_t cmd_beat_t; + + // This pattern assignment should work correctly + localparam cb::cfg_t cb_cfg = '{ + Rids : 32'h1, + Pids : 32'h2, + Fnum : 32'h3, + XdatSize : $bits(cmd_beat_t) + }; + + // This should trigger the error - cb_cfg is not recognized as constant + simple_if#(cb_cfg) cb_vc0_io(); + + initial begin + `checkd(cb_cfg.XdatSize, 12); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_paramtype_cast.py b/test_regress/t/t_paramgraph_paramtype_cast.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_paramtype_cast.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_paramtype_cast.v b/test_regress/t/t_paramgraph_paramtype_cast.v new file mode 100644 index 000000000..41f30ac8f --- /dev/null +++ b/test_regress/t/t_paramgraph_paramtype_cast.v @@ -0,0 +1,32 @@ +// 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 +// + +// DESCRIPTION: Verilator: Test nested interface typedef access +// This replicates the pattern from a much larger design that was +// failing with the localparam changes - accessing a typedef from +// a doubly-nested interface +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package TestPkg; + localparam type addr_t = logic [11:0]; + localparam addr_t STATUS = addr_t'('ha5); +endpackage + +module TestMod; + import TestPkg::*; + + initial begin + `checkd(STATUS, 'ha5); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_paramtype_default.py b/test_regress/t/t_paramgraph_paramtype_default.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_paramtype_default.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_paramtype_default.v b/test_regress/t/t_paramgraph_paramtype_default.v new file mode 100644 index 000000000..8e4bf9ec5 --- /dev/null +++ b/test_regress/t/t_paramgraph_paramtype_default.v @@ -0,0 +1,69 @@ +// 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 +// + +// DESCRIPTION: Verilator: Test for REQUIREDTYPE resolution with default type parameters +// +// This test verifies that modules with `parameter type T = logic` work correctly +// when instantiated WITHOUT overriding the type parameter (using the default). +// + +// Simple type flop - parameterized by type T with default = logic +module tflop #(parameter type T = logic) ( + input logic clk, + input logic reset, + input T reset_strap_i, + output T q_o, + input T d_i +); + always_ff @(posedge clk) begin + if (reset) begin + q_o <= reset_strap_i; + end else begin + q_o <= d_i; + end + end +endmodule + +// Module that uses tflop with DEFAULT type parameter (T = logic) +module user_mod ( + input logic clk, + input logic reset +); + logic d_in, d_out; + + // Use tflop with default type parameter T = logic + // This should NOT create a specialized clone - it reuses the template + tflop vld_reg ( + .clk(clk), + .reset(reset), + .reset_strap_i(1'b0), + .q_o(d_out), + .d_i(d_in) + ); + + initial begin + d_in = 1'b1; + #10; + $display("d_out = %b", d_out); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule + +module t; + logic clk = 0; + logic reset = 1; + + user_mod uut (.clk(clk), .reset(reset)); + + initial begin + #5 reset = 0; + #10 clk = 1; + #10 clk = 0; + #10 $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_paramtype_range.py b/test_regress/t/t_paramgraph_paramtype_range.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_paramtype_range.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_paramtype_range.v b/test_regress/t/t_paramgraph_paramtype_range.v new file mode 100644 index 000000000..54e1350ed --- /dev/null +++ b/test_regress/t/t_paramgraph_paramtype_range.v @@ -0,0 +1,34 @@ +// 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 +// +// DESCRIPTION: +// Regression for localparam-derived cfg structs feeding interface instances +// and their nested typedefs. +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package TestPkg; + localparam type tm_region_t = logic [1:0]; +endpackage + +module TestMod; + import TestPkg::*; + + // This should work - tm_region_t has width 2 + localparam tm_region_t tm_region_lsio = 2'b10; + + // Test logic + initial begin + $display("tm_region_lsio = %b", tm_region_lsio); + `checkd(tm_region_lsio, 2); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_refdtype_iface.py b/test_regress/t/t_paramgraph_refdtype_iface.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_refdtype_iface.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_refdtype_iface.v b/test_regress/t/t_paramgraph_refdtype_iface.v new file mode 100644 index 000000000..0577c5b43 --- /dev/null +++ b/test_regress/t/t_paramgraph_refdtype_iface.v @@ -0,0 +1,39 @@ +// 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 +// +// DESCRIPTION: +// Regression for localparam-derived cfg structs feeding interface instances +// and their nested typedefs. +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +interface depgraph_if; + typedef logic [3:0] nibble_t; +endinterface + +module depgraph_top; + depgraph_if ifc(); + + typedef ifc.nibble_t nibble_t; + + nibble_t a; + nibble_t b; + logic [3:0] sum; + + assign sum = a + b; + + initial begin + #1; + `checkd($bits(nibble_t), 4); + `checkd($bits(sum), 4); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_refdtype_unlinked.py b/test_regress/t/t_paramgraph_refdtype_unlinked.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_refdtype_unlinked.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_refdtype_unlinked.v b/test_regress/t/t_paramgraph_refdtype_unlinked.v new file mode 100644 index 000000000..b490eaa41 --- /dev/null +++ b/test_regress/t/t_paramgraph_refdtype_unlinked.v @@ -0,0 +1,46 @@ +// 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 +// + +// Test case for REFDTYPE not linked to type +// This reproduces the error where a REFDTYPE in a parameter expression +// is not properly linked to its type after DepGraph resolution + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package Include; + typedef logic [11:0] mbox_addr_t; +endpackage + +interface mbox_if #(parameter int WIDTH = 0); + typedef Include::mbox_addr_t mbox_addr_t; + + typedef struct packed { + logic [1:0] tag; + logic [WIDTH-1:0] addr; + } RFTag; +endinterface + +module mbox #(parameter int WIDTH = 0); + mbox_if #(WIDTH) if_inst(); + + // This should reproduce the REFDTYPE UNLINKED error + // Using a type cast of an interface typedef in a parameter + localparam logic [16:0] TAG_ZERO = {1'b1, if_inst.RFTag'(0)}; + + initial begin + `checkd($bits(TAG_ZERO), 17); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule + +module top; + mbox #(.WIDTH(14)) u_mbox(); +endmodule diff --git a/test_regress/t/t_paramgraph_selbit_dtype.py b/test_regress/t/t_paramgraph_selbit_dtype.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_selbit_dtype.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_selbit_dtype.v b/test_regress/t/t_paramgraph_selbit_dtype.v new file mode 100644 index 000000000..971a172ea --- /dev/null +++ b/test_regress/t/t_paramgraph_selbit_dtype.v @@ -0,0 +1,99 @@ +// 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 +// + +// Test for COND node dtype not being set when using type parameters +// This reproduces the issue from spill_register_flushable.sv:95 +// The issue involves type parameters used in ternary expressions +// Key: T defaults to logic, and a COND expression uses variables of type T + +// Spill register with type parameter (simplified from spill_register_flushable) +module spill_register #( + parameter type T = logic +) ( + input logic clk_i, + input logic rst_ni, + input logic sel_i, + input T data_i, + output T data_o +); + // Two registers of type T + T a_data_q; + T b_data_q; + logic b_full_q; + + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + a_data_q <= '0; + b_data_q <= '0; + b_full_q <= 1'b0; + end else begin + a_data_q <= data_i; + b_data_q <= a_data_q; + b_full_q <= sel_i; + end + end + + // This is the problematic line - ternary expression with type parameter variables + // The COND node's dtype should be T, but it's not being set + assign data_o = b_full_q ? b_data_q : a_data_q; +endmodule + +// Wrapper module that passes type parameter through (like spill_register_flushable wrapper) +module spill_wrapper #( + parameter type T = logic +) ( + input logic clk_i, + input logic rst_ni, + input logic sel_i, + input T data_i, + output T data_o +); + // Instantiate spill_register with the same type parameter + spill_register #(.T(T)) i_spill ( + .clk_i(clk_i), + .rst_ni(rst_ni), + .sel_i(sel_i), + .data_i(data_i), + .data_o(data_o) + ); +endmodule + +// Another level of nesting (like axi_demux) +module demux #( + parameter type T = logic +) ( + input logic clk_i, + input logic rst_ni +); + logic sel; + T data_in; + T data_out; + + spill_wrapper #(.T(T)) i_spill_wrapper ( + .clk_i(clk_i), + .rst_ni(rst_ni), + .sel_i(sel), + .data_i(data_in), + .data_o(data_out) + ); +endmodule + +module top; + logic clk; + logic rst_n; + + // Instantiate with default T (logic) + demux #(.T(logic)) u_demux ( + .clk_i(clk), + .rst_ni(rst_n) + ); + + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_simple_cache_localparam_cfg.py b/test_regress/t/t_paramgraph_simple_cache_localparam_cfg.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_simple_cache_localparam_cfg.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_simple_cache_localparam_cfg.v b/test_regress/t/t_paramgraph_simple_cache_localparam_cfg.v new file mode 100644 index 000000000..8e9d7d6a3 --- /dev/null +++ b/test_regress/t/t_paramgraph_simple_cache_localparam_cfg.v @@ -0,0 +1,103 @@ +// 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 +// +// DESCRIPTION: +// Regression for localparam-derived cfg structs feeding interface instances +// and their nested typedefs. +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package cache_pkg; + typedef struct packed { + int Capacity; + int LineSize; + int Associativity; + int AddrBits; + int FgWidth; + int MissQSize; + int CmdTagBits; + } cfg_t; +endpackage + +interface cache_types_if #( + parameter cache_pkg::cfg_t cfg = '0 +)(); + localparam int SC_NUM_LINES = cfg.Capacity / cfg.LineSize; + localparam int SC_LINES_PER_WAY = SC_NUM_LINES / cfg.Associativity; + localparam int SC_BLOCK_BITS = $clog2(cfg.LineSize); + localparam int SC_ROW_BITS = $clog2(SC_LINES_PER_WAY); + localparam int SC_TAG_BITS = cfg.AddrBits - SC_ROW_BITS - SC_BLOCK_BITS; + localparam int SC_NUM_DROWS = (cfg.LineSize / cfg.FgWidth) * SC_NUM_LINES; + + typedef logic [SC_TAG_BITS-1:0] tag_t; + typedef logic [$clog2(SC_NUM_DROWS)-1:0] drow_addr_t; +endinterface + +interface cache_if #( + parameter cache_pkg::cfg_t cfg = '0 +)(); + cache_types_if #(cfg) types(); + typedef types.tag_t tag_t; + typedef types.drow_addr_t drow_addr_t; +endinterface + +module cache_leaf(cache_if io, output int tag_bits_o, output int drow_bits_o); + typedef io.tag_t tag_t; + typedef io.drow_addr_t drow_addr_t; + assign tag_bits_o = $bits(tag_t); + assign drow_bits_o = $bits(drow_addr_t); +endmodule + +module cache_wrap #( + parameter cache_pkg::cfg_t cfg = '0 +)(output int tag_bits_o, output int drow_bits_o); + localparam cache_pkg::cfg_t sc_cfg = '{ + CmdTagBits : $clog2(cfg.Capacity), + Associativity : cfg.Associativity, + Capacity : cfg.Capacity, + LineSize : cfg.LineSize, + AddrBits : cfg.AddrBits, + FgWidth : cfg.FgWidth, + MissQSize : cfg.MissQSize + }; + + cache_if #(sc_cfg) sc_io(); + + cache_leaf u_leaf(.io(sc_io), .tag_bits_o(tag_bits_o), .drow_bits_o(drow_bits_o)); +endmodule + +module top; + localparam cache_pkg::cfg_t cfg0 = '{ + Capacity:1024, LineSize:64, Associativity:4, AddrBits:32, + FgWidth:32, MissQSize:8, CmdTagBits:0 + }; + localparam cache_pkg::cfg_t cfg1 = '{ + Capacity:2048, LineSize:32, Associativity:2, AddrBits:36, + FgWidth:16, MissQSize:16, CmdTagBits:0 + }; + + int tag_bits0; + int drow_bits0; + int tag_bits1; + int drow_bits1; + + cache_wrap #(cfg0) wrap0(.tag_bits_o(tag_bits0), .drow_bits_o(drow_bits0)); + cache_wrap #(cfg1) wrap1(.tag_bits_o(tag_bits1), .drow_bits_o(drow_bits1)); + + initial begin + #1; + `checkd(tag_bits0, 24); + `checkd(drow_bits0, 5); + `checkd(tag_bits1, 26); + `checkd(drow_bits1, 7); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_simple_cache_types_if.py b/test_regress/t/t_paramgraph_simple_cache_types_if.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_paramgraph_simple_cache_types_if.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_paramgraph_simple_cache_types_if.v b/test_regress/t/t_paramgraph_simple_cache_types_if.v new file mode 100644 index 000000000..7dff2292f --- /dev/null +++ b/test_regress/t/t_paramgraph_simple_cache_types_if.v @@ -0,0 +1,105 @@ +// 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 +// +// DESCRIPTION: +// Minimal testcase for depgraph handling of localparam-derived typedefs +// inside a parameterized cache types interface. +// + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +package sc_pkg; + typedef struct packed { + int Capacity; + int LineSize; + int Associativity; + int AddrBits; + int FgWidth; + int StateBits; + int CmdTagBits; + int MissQSize; + } cfg_t; +endpackage + +interface simple_cache_types_if #( + parameter sc_pkg::cfg_t cfg = '0 +)(); + localparam int SC_NUM_LINES = cfg.Capacity / cfg.LineSize; + localparam int SC_LINES_PER_WAY = SC_NUM_LINES / cfg.Associativity; + localparam int SC_BLOCK_BITS = $clog2(cfg.LineSize); + localparam int SC_ROW_BITS = $clog2(SC_LINES_PER_WAY); + localparam int SC_TAG_BITS = cfg.AddrBits - SC_ROW_BITS - SC_BLOCK_BITS; + localparam int SC_DROWS_PER_LINE = cfg.LineSize / cfg.FgWidth; + localparam int SC_NUM_DROWS = SC_NUM_LINES * SC_DROWS_PER_LINE; + + typedef logic [cfg.AddrBits-1:0] addr_t; + typedef logic [cfg.Associativity-1:0] assoc_oh_t; + typedef logic [cfg.Associativity-2:0] plru_t; + typedef logic [cfg.StateBits-1:0] state_t; + typedef logic [cfg.CmdTagBits-1:0] cmd_tag_t; + typedef logic [$clog2(cfg.MissQSize)-1:0] missq_tag_t; + + typedef logic [SC_TAG_BITS-1:0] tag_t; + typedef logic [SC_ROW_BITS-1:0] row_t; + typedef logic [SC_BLOCK_BITS-1:0] block_t; + typedef logic [$clog2(SC_NUM_DROWS)-1:0] drow_addr_t; + + typedef struct packed { + tag_t tag; + row_t row; + block_t block; + } sc_tag_addr_t; +endinterface + +module child(simple_cache_types_if types, output int tag_bits_o, output int tag_addr_bits_o, output int drow_bits_o); + typedef types.tag_t tag_t; + typedef types.sc_tag_addr_t sc_tag_addr_t; + typedef types.drow_addr_t drow_addr_t; + assign tag_bits_o = $bits(tag_t); + assign tag_addr_bits_o = $bits(sc_tag_addr_t); + assign drow_bits_o = $bits(drow_addr_t); +endmodule + +module top; + localparam sc_pkg::cfg_t cfg0 = '{ + Capacity:1024, LineSize:64, Associativity:4, AddrBits:32, + FgWidth:32, StateBits:2, CmdTagBits:5, MissQSize:8 + }; + localparam sc_pkg::cfg_t cfg1 = '{ + Capacity:2048, LineSize:32, Associativity:2, AddrBits:36, + FgWidth:16, StateBits:3, CmdTagBits:7, MissQSize:16 + }; + + simple_cache_types_if #(cfg0) types0(); + simple_cache_types_if #(cfg1) types1(); + + int tag_bits0; + int tag_addr_bits0; + int drow_bits0; + int tag_bits1; + int tag_addr_bits1; + int drow_bits1; + + child u0(.types(types0), .tag_bits_o(tag_bits0), .tag_addr_bits_o(tag_addr_bits0), + .drow_bits_o(drow_bits0)); + child u1(.types(types1), .tag_bits_o(tag_bits1), .tag_addr_bits_o(tag_addr_bits1), + .drow_bits_o(drow_bits1)); + + initial begin + #1; + `checkd(tag_bits0, 24); + `checkd(tag_addr_bits0, 32); + `checkd(drow_bits0, 5); + `checkd(tag_bits1, 26); + `checkd(tag_addr_bits1, 36); + `checkd(drow_bits1, 7); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_selrange_iface_type_param.py b/test_regress/t/t_selrange_iface_type_param.py new file mode 100755 index 000000000..7ded63f3a --- /dev/null +++ b/test_regress/t/t_selrange_iface_type_param.py @@ -0,0 +1,18 @@ +#!/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: 2024 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_selrange_iface_type_param.v b/test_regress/t/t_selrange_iface_type_param.v new file mode 100644 index 000000000..b9fd18340 --- /dev/null +++ b/test_regress/t/t_selrange_iface_type_param.v @@ -0,0 +1,595 @@ +// DESCRIPTION: Verilator: Test for interface typedef resolving to correct clone +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2022 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 +// +// When a non-parameterized wrapper module (#()) has multiple interface ports +// of the same parameterized interface type but with different configurations, +// typedefs extracted from those ports (e.g., typedef tgt_io.req_t local_t) +// must resolve to the struct from the CORRECT interface clone. +// +// This test models the aerial design hierarchy: +// Path A: dw_converter_wrap#()(tgt_io[Id=2,Data=64], mst_io[Id=2,Data=128]) +// -> dw_converter -> dw_upsizer -> axi_demux -> axi_demux_simple +// Path B: axi_to_axi_lite_wrap#()(axi_tgt_io[Id=8,Data=64]) +// -> axi_to_axi_lite -> axi_burst_splitter -> axi_demux -> axi_demux_simple +// +// Checks: +// 1. $bits of typedef'd types match the interface config (not a sibling clone) +// 2. Nested struct member access (slv_req_i.aw.id[0+:N]) extracts correct bits +// 3. Data written to interface ports arrives at leaf modules with correct values +// +// This file is ONLY for use with Verilator. + +package cfg_pkg; + typedef struct packed { + int unsigned IdBits; + int unsigned DataBits; + } cfg_t; +endpackage + +// Parameterized interface (like axi4_if) +interface my_if #(parameter cfg_pkg::cfg_t cfg = 0); + typedef logic [cfg.IdBits-1:0] id_t; + typedef logic [cfg.DataBits-1:0] data_t; + typedef logic [cfg.DataBits/8-1:0] strb_t; + + typedef struct packed { + id_t id; + data_t data; + logic [7:0] len; + logic [2:0] size; + logic [1:0] burst; + } aw_chan_t; + + typedef struct packed { + data_t data; + strb_t strb; + logic last; + } w_chan_t; + + typedef struct packed { + id_t id; + logic [1:0] resp; + } b_chan_t; + + typedef struct packed { + id_t id; + data_t data; + logic [7:0] len; + logic [2:0] size; + logic [1:0] burst; + } ar_chan_t; + + typedef struct packed { + id_t id; + data_t data; + logic [1:0] resp; + logic last; + } r_chan_t; + + typedef struct packed { + aw_chan_t aw; + logic aw_valid; + w_chan_t w; + logic w_valid; + logic b_ready; + ar_chan_t ar; + logic ar_valid; + logic r_ready; + } req_t; + + typedef struct packed { + logic aw_ready; + logic w_ready; + b_chan_t b; + logic b_valid; + r_chan_t r; + logic r_valid; + } resp_t; + + req_t req; + resp_t resp; + + modport target(input req, output resp); + modport initiator(output req, input resp); +endinterface + +//====================================================================== +// Leaf: axi_demux_simple skeleton +//====================================================================== +module axi_demux_simple #( + parameter int unsigned AxiIdWidth = 32'd0, + parameter type axi_req_t = logic, + parameter type axi_resp_t = logic, + parameter int unsigned AxiLookBits = 32'd3 +)( + input logic clk_i, + input logic rst_ni, + input axi_req_t slv_req_i, + output axi_resp_t slv_resp_o, + output logic [AxiLookBits-1:0] id_out, + output int unsigned req_bits_out +); + // Extract ID from nested struct - triggers SELRANGE if axi_req_t + // is from wrong interface clone (id field narrower than AxiLookBits) + assign id_out = slv_req_i.aw.id[0+:AxiLookBits]; + assign slv_resp_o = '0; + // Expose $bits so top can verify the type parameter resolved correctly + assign req_bits_out = $bits(axi_req_t); +endmodule + +//====================================================================== +// axi_demux skeleton +//====================================================================== +module axi_demux #( + parameter int unsigned AxiIdWidth = 32'd0, + parameter type aw_chan_t = logic, + parameter type w_chan_t = logic, + parameter type b_chan_t = logic, + parameter type ar_chan_t = logic, + parameter type r_chan_t = logic, + parameter type axi_req_t = logic, + parameter type axi_resp_t = logic, + parameter int unsigned NoMstPorts = 32'd0, + parameter int unsigned MaxTrans = 32'd8, + parameter int unsigned AxiLookBits = 32'd3 +)( + input logic clk_i, + input logic rst_ni, + input axi_req_t slv_req_i, + output axi_resp_t slv_resp_o, + output logic [AxiLookBits-1:0] id_out, + output int unsigned req_bits_out +); + axi_demux_simple #( + .AxiIdWidth ( AxiIdWidth ), + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_resp_t ), + .AxiLookBits ( AxiLookBits ) + ) i_demux_simple ( + .clk_i, + .rst_ni, + .slv_req_i, + .slv_resp_o, + .id_out, + .req_bits_out + ); +endmodule + +//====================================================================== +// axi_burst_splitter skeleton +//====================================================================== +module axi_burst_splitter #( + parameter int unsigned AddrWidth = 32'd0, + parameter int unsigned DataWidth = 32'd0, + parameter int unsigned IdWidth = 32'd0, + parameter int unsigned UserWidth = 32'd0, + parameter type axi_req_t = logic, + parameter type axi_resp_t = logic +)( + input logic clk_i, + input logic rst_ni, + input axi_req_t slv_req_i, + output axi_resp_t slv_resp_o, + output logic [IdWidth-1:0] id_out +); + typedef logic [AddrWidth-1:0] addr_t; + typedef logic [DataWidth-1:0] data_t; + typedef logic [IdWidth-1:0] id_t; + typedef logic [DataWidth/8-1:0] strb_t; + typedef logic [UserWidth-1:0] user_t; + + typedef struct packed { + id_t id; + addr_t addr; + logic [7:0] len; + logic [2:0] size; + logic [1:0] burst; + } local_aw_chan_t; + + typedef struct packed { data_t data; strb_t strb; logic last; } local_w_chan_t; + typedef struct packed { id_t id; logic [1:0] resp; } local_b_chan_t; + typedef struct packed { id_t id; addr_t addr; logic [7:0] len; logic [2:0] size; logic [1:0] burst; } local_ar_chan_t; + typedef struct packed { id_t id; data_t data; logic [1:0] resp; logic last; } local_r_chan_t; + + int unsigned req_bits_out; + + axi_demux #( + .AxiIdWidth ( IdWidth ), + .aw_chan_t ( local_aw_chan_t ), + .w_chan_t ( local_w_chan_t ), + .b_chan_t ( local_b_chan_t ), + .ar_chan_t ( local_ar_chan_t ), + .r_chan_t ( local_r_chan_t ), + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_resp_t ), + .NoMstPorts ( 2 ), + .MaxTrans ( 4 ), + .AxiLookBits ( IdWidth ) + ) i_demux ( + .clk_i, .rst_ni, + .slv_req_i, .slv_resp_o, .id_out, + .req_bits_out + ); +endmodule + +//====================================================================== +// axi_dw_upsizer skeleton +//====================================================================== +module axi_dw_upsizer #( + parameter int unsigned AxiIdWidth = 1, + parameter int unsigned AxiAddrWidth = 1, + parameter type aw_chan_t = logic, + parameter type mst_w_chan_t = logic, + parameter type slv_w_chan_t = logic, + parameter type b_chan_t = logic, + parameter type ar_chan_t = logic, + parameter type mst_r_chan_t = logic, + parameter type slv_r_chan_t = logic, + parameter type axi_mst_req_t = logic, + parameter type axi_mst_resp_t = logic, + parameter type axi_slv_req_t = logic, + parameter type axi_slv_resp_t = logic +)( + input logic clk_i, + input logic rst_ni, + input axi_slv_req_t slv_req_i, + output axi_slv_resp_t slv_resp_o, + output axi_mst_req_t mst_req_o, + input axi_mst_resp_t mst_resp_i +); + axi_mst_req_t mst_req; + axi_mst_resp_t mst_resp; + logic [AxiIdWidth-1:0] id_out; + int unsigned req_bits_out; + + assign mst_req = '0; + assign slv_resp_o = '0; + assign mst_req_o = mst_req; + + axi_demux #( + .AxiIdWidth ( AxiIdWidth ), + .aw_chan_t ( aw_chan_t ), + .w_chan_t ( mst_w_chan_t ), + .b_chan_t ( b_chan_t ), + .ar_chan_t ( ar_chan_t ), + .r_chan_t ( mst_r_chan_t ), + .axi_req_t ( axi_mst_req_t ), + .axi_resp_t ( axi_mst_resp_t ), + .NoMstPorts ( 2 ), + .MaxTrans ( 4 ), + .AxiLookBits ( AxiIdWidth ) + ) i_axi_demux ( + .clk_i, .rst_ni, + .slv_req_i ( mst_req ), + .slv_resp_o ( mst_resp ), + .id_out, + .req_bits_out + ); +endmodule + +//====================================================================== +// axi_dw_converter skeleton - generate if for upsize +//====================================================================== +module axi_dw_converter #( + parameter int unsigned AxiSlvPortDataWidth = 8, + parameter int unsigned AxiMstPortDataWidth = 8, + parameter int unsigned AxiAddrWidth = 1, + parameter int unsigned AxiIdWidth = 1, + parameter type aw_chan_t = logic, + parameter type mst_w_chan_t = logic, + parameter type slv_w_chan_t = logic, + parameter type b_chan_t = logic, + parameter type ar_chan_t = logic, + parameter type mst_r_chan_t = logic, + parameter type slv_r_chan_t = logic, + parameter type axi_mst_req_t = logic, + parameter type axi_mst_resp_t = logic, + parameter type axi_slv_req_t = logic, + parameter type axi_slv_resp_t = logic +)( + input logic clk_i, + input logic rst_ni, + input axi_slv_req_t slv_req_i, + output axi_slv_resp_t slv_resp_o, + output axi_mst_req_t mst_req_o, + input axi_mst_resp_t mst_resp_i +); + if (AxiMstPortDataWidth == AxiSlvPortDataWidth) begin : gen_no_dw_conversion + assign mst_req_o = slv_req_i; + assign slv_resp_o = mst_resp_i; + end + + if (AxiMstPortDataWidth > AxiSlvPortDataWidth) begin : gen_dw_upsize + axi_dw_upsizer #( + .AxiAddrWidth ( AxiAddrWidth ), + .AxiIdWidth ( AxiIdWidth ), + .aw_chan_t ( aw_chan_t ), + .mst_w_chan_t ( mst_w_chan_t ), + .slv_w_chan_t ( slv_w_chan_t ), + .b_chan_t ( b_chan_t ), + .ar_chan_t ( ar_chan_t ), + .mst_r_chan_t ( mst_r_chan_t ), + .slv_r_chan_t ( slv_r_chan_t ), + .axi_mst_req_t ( axi_mst_req_t ), + .axi_mst_resp_t ( axi_mst_resp_t ), + .axi_slv_req_t ( axi_slv_req_t ), + .axi_slv_resp_t ( axi_slv_resp_t ) + ) i_axi_dw_upsizer ( + .clk_i, .rst_ni, + .slv_req_i, .slv_resp_o, + .mst_req_o, .mst_resp_i + ); + end + + // Expose leaf req_bits from whichever generate path is active + int unsigned mst_req_bits; + if (AxiMstPortDataWidth > AxiSlvPortDataWidth) begin : gen_bits_up + assign mst_req_bits = gen_dw_upsize.i_axi_dw_upsizer.req_bits_out; + end else begin : gen_bits_eq + assign mst_req_bits = 0; + end +endmodule + +//====================================================================== +// axi_to_axi_lite skeleton +//====================================================================== +module axi_to_axi_lite #( + parameter int unsigned AxiAddrWidth = 32'd0, + parameter int unsigned AxiDataWidth = 32'd0, + parameter int unsigned AxiIdWidth = 32'd0, + parameter int unsigned AxiUserWidth = 32'd0, + parameter type full_req_t = logic, + parameter type full_resp_t = logic +)( + input logic clk_i, + input logic rst_ni, + input full_req_t slv_req_i, + output full_resp_t slv_resp_o, + output logic [AxiIdWidth-1:0] id_out +); + axi_burst_splitter #( + .AddrWidth ( AxiAddrWidth ), + .DataWidth ( AxiDataWidth ), + .IdWidth ( AxiIdWidth ), + .UserWidth ( AxiUserWidth ), + .axi_req_t ( full_req_t ), + .axi_resp_t ( full_resp_t ) + ) i_axi_burst_splitter ( + .clk_i, .rst_ni, + .slv_req_i, .slv_resp_o, .id_out + ); + + int unsigned req_bits_out; + assign req_bits_out = i_axi_burst_splitter.req_bits_out; +endmodule + +//====================================================================== +// axi_to_axi_lite_wrap - non-parameterized wrapper (#()) with iface port +//====================================================================== +module axi_to_axi_lite_wrap #()( + input logic clk_i, + input logic rst_ni, + my_if.target axi_tgt_io +); + typedef axi_tgt_io.req_t tgt_req_t; + typedef axi_tgt_io.resp_t tgt_resp_t; + + tgt_req_t tgt_req; + tgt_resp_t tgt_resp; + assign tgt_req = axi_tgt_io.req; + assign axi_tgt_io.resp = tgt_resp; + + logic [axi_tgt_io.cfg.IdBits-1:0] id_result; + int unsigned req_bits_out; + + axi_to_axi_lite #( + .AxiAddrWidth ( 32 ), + .AxiDataWidth ( axi_tgt_io.cfg.DataBits ), + .AxiIdWidth ( axi_tgt_io.cfg.IdBits ), + .AxiUserWidth ( 1 ), + .full_req_t ( tgt_req_t ), + .full_resp_t ( tgt_resp_t ) + ) axi_to_axi_lite ( + .clk_i, .rst_ni, + .slv_req_i ( tgt_req ), + .slv_resp_o ( tgt_resp ), + .id_out ( id_result ) + ); + + assign req_bits_out = axi_to_axi_lite.req_bits_out; +endmodule + +//====================================================================== +// axi_dw_converter_wrap - non-parameterized wrapper with TWO iface ports +//====================================================================== +module axi_dw_converter_wrap #()( + input logic clk_i, + input logic rst_ni, + my_if.target tgt_io, + my_if.initiator mst_io +); + typedef tgt_io.aw_chan_t tgt_aw_chan_t; + typedef tgt_io.w_chan_t tgt_w_chan_t; + typedef tgt_io.b_chan_t tgt_b_chan_t; + typedef tgt_io.ar_chan_t tgt_ar_chan_t; + typedef tgt_io.r_chan_t tgt_r_chan_t; + + typedef mst_io.w_chan_t mst_w_chan_t; + typedef mst_io.r_chan_t mst_r_chan_t; + + typedef tgt_io.req_t tgt_req_t; + typedef tgt_io.resp_t tgt_resp_t; + typedef mst_io.req_t mst_req_t; + typedef mst_io.resp_t mst_resp_t; + + tgt_req_t tgt_req; + tgt_resp_t tgt_resp; + mst_req_t mst_req; + mst_resp_t mst_resp; + + assign tgt_req = tgt_io.req; + assign tgt_io.resp = tgt_resp; + assign mst_io.req = mst_req; + assign mst_resp = mst_io.resp; + + axi_dw_converter #( + .AxiSlvPortDataWidth ( tgt_io.cfg.DataBits ), + .AxiMstPortDataWidth ( mst_io.cfg.DataBits ), + .AxiAddrWidth ( 32 ), + .AxiIdWidth ( tgt_io.cfg.IdBits ), + .aw_chan_t ( tgt_aw_chan_t ), + .mst_w_chan_t ( mst_w_chan_t ), + .slv_w_chan_t ( tgt_w_chan_t ), + .b_chan_t ( tgt_b_chan_t ), + .ar_chan_t ( tgt_ar_chan_t ), + .mst_r_chan_t ( mst_r_chan_t ), + .slv_r_chan_t ( tgt_r_chan_t ), + .axi_mst_req_t ( mst_req_t ), + .axi_mst_resp_t ( mst_resp_t ), + .axi_slv_req_t ( tgt_req_t ), + .axi_slv_resp_t ( tgt_resp_t ) + ) dw_converter ( + .clk_i, .rst_ni, + .slv_req_i ( tgt_req ), + .slv_resp_o ( tgt_resp ), + .mst_req_o ( mst_req ), + .mst_resp_i ( mst_resp ) + ); + + // Expose $bits from the mst-side leaf (through dw_converter -> upsizer -> demux) + int unsigned mst_req_bits_out; + assign mst_req_bits_out = dw_converter.mst_req_bits; + + // Also check tgt-side typedef width directly in this wrapper + int unsigned tgt_req_bits_out; + assign tgt_req_bits_out = $bits(tgt_req_t); + int unsigned mst_req_bits_local; + assign mst_req_bits_local = $bits(mst_req_t); +endmodule + +//====================================================================== +// Top module +//====================================================================== +module t; + logic clk; + logic rst_n; + + // Config A: narrow ID (2 bits), narrow data (64 bits) -> upsize to 128 + localparam cfg_pkg::cfg_t CFG_A_SLV = '{IdBits: 2, DataBits: 64}; + localparam cfg_pkg::cfg_t CFG_A_MST = '{IdBits: 2, DataBits: 128}; + + // Config B: wide ID (8 bits), data 64 bits + localparam cfg_pkg::cfg_t CFG_B = '{IdBits: 8, DataBits: 64}; + + my_if #(.cfg(CFG_A_SLV)) bus_a_slv(); + my_if #(.cfg(CFG_A_MST)) bus_a_mst(); + my_if #(.cfg(CFG_B)) bus_b(); + + // Path A: dw_converter_wrap (narrow ID, upsize 64->128) + axi_dw_converter_wrap #() u_dw_conv_wrap ( + .clk_i(clk), .rst_ni(rst_n), + .tgt_io(bus_a_slv), .mst_io(bus_a_mst) + ); + + // Path B: axi_to_axi_lite_wrap (wide ID) + axi_to_axi_lite_wrap #() u_axi_to_lite_wrap ( + .clk_i(clk), .rst_ni(rst_n), + .axi_tgt_io(bus_b) + ); + + //====================================================================== + // Expected $bits values: + // + // req_t for CFG_A_SLV (Id=2, Data=64): + // aw_chan_t = 2+64+8+3+2 = 79 + // w_chan_t = 64+8+1 = 73 + // ar_chan_t = 79 + // req_t = 79+1+73+1+1+79+1+1 = 236 + // + // req_t for CFG_A_MST (Id=2, Data=128): + // aw_chan_t = 2+128+8+3+2 = 143 + // w_chan_t = 128+16+1 = 145 + // ar_chan_t = 143 + // req_t = 143+1+145+1+1+143+1+1 = 436 + // + // req_t for CFG_B (Id=8, Data=64): + // aw_chan_t = 8+64+8+3+2 = 85 + // w_chan_t = 64+8+1 = 73 + // ar_chan_t = 85 + // req_t = 85+1+73+1+1+85+1+1 = 248 + //====================================================================== + + localparam int unsigned EXP_REQ_BITS_A_SLV = 236; + localparam int unsigned EXP_REQ_BITS_A_MST = 436; + localparam int unsigned EXP_REQ_BITS_B = 248; + + // verilator lint_off STMTDLY + initial begin + clk = 0; rst_n = 1; + + // Drive path A: narrow ID + bus_a_slv.req = '0; + bus_a_slv.req.aw.id = 2'h3; + bus_a_slv.req.aw_valid = 1'b1; + bus_a_mst.resp = '0; + + // Drive path B: wide ID + bus_b.req = '0; + bus_b.req.aw.id = 8'hAB; + bus_b.req.aw_valid = 1'b1; + + #10; + + //------------------------------------------------------------------ + // Check 1: dw_converter_wrap typedef widths + // tgt_req_t must match CFG_A_SLV, mst_req_t must match CFG_A_MST + //------------------------------------------------------------------ + if (u_dw_conv_wrap.tgt_req_bits_out !== EXP_REQ_BITS_A_SLV) begin + $display("%%Error: dw_conv_wrap tgt_req_bits=%0d expected=%0d", + u_dw_conv_wrap.tgt_req_bits_out, EXP_REQ_BITS_A_SLV); + $stop; + end + if (u_dw_conv_wrap.mst_req_bits_local !== EXP_REQ_BITS_A_MST) begin + $display("%%Error: dw_conv_wrap mst_req_bits_local=%0d expected=%0d", + u_dw_conv_wrap.mst_req_bits_local, EXP_REQ_BITS_A_MST); + $stop; + end + + //------------------------------------------------------------------ + // Check 2: mst-side leaf (through dw_upsizer -> axi_demux -> axi_demux_simple) + // axi_req_t must be mst_req_t = CFG_A_MST + //------------------------------------------------------------------ + if (u_dw_conv_wrap.mst_req_bits_out !== EXP_REQ_BITS_A_MST) begin + $display("%%Error: dw_conv_wrap mst leaf req_bits=%0d expected=%0d", + u_dw_conv_wrap.mst_req_bits_out, EXP_REQ_BITS_A_MST); + $stop; + end + + //------------------------------------------------------------------ + // Check 3: axi_to_axi_lite_wrap leaf + // axi_req_t must be tgt_req_t = CFG_B + //------------------------------------------------------------------ + if (u_axi_to_lite_wrap.req_bits_out !== EXP_REQ_BITS_B) begin + $display("%%Error: axi_to_lite leaf req_bits=%0d expected=%0d", + u_axi_to_lite_wrap.req_bits_out, EXP_REQ_BITS_B); + $stop; + end + + //------------------------------------------------------------------ + // Check 4: ID extraction through path B + // axi_to_axi_lite_wrap -> ... -> axi_demux_simple extracts aw.id[0+:8] + //------------------------------------------------------------------ + if (u_axi_to_lite_wrap.id_result !== 8'hAB) begin + $display("%%Error: axi_to_lite id_result=%0h expected=AB", + u_axi_to_lite_wrap.id_result); + $stop; + end + + $write("*-* All Finished *-*\n"); + $finish; + end + // verilator lint_on STMTDLY +endmodule diff --git a/test_regress/t/t_selrange_iface_type_param_debugi.py b/test_regress/t/t_selrange_iface_type_param_debugi.py new file mode 100755 index 000000000..b36d118f5 --- /dev/null +++ b/test_regress/t/t_selrange_iface_type_param_debugi.py @@ -0,0 +1,27 @@ +#!/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 + +# Debug-variant of t_selrange_iface_type_param: enables IfaceCapture debug +# output and verifies type-parameter capture and propagation messages. + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_selrange_iface_type_param.v" + +test.compile(v_flags2=["--binary --debug --debugi 0 --debugi-V3LinkDotIfaceCapture 9"]) + +test.file_grep(test.compile_log_filename, r"propagateClone:") +test.file_grep(test.compile_log_filename, r"iface capture add:") +test.file_grep(test.compile_log_filename, r"iface capture dumpEntries:") + +test.execute() + +test.passes()