diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 56cfe9c..af875df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,7 @@ jobs: ./py/bin/pip install -U ivpm ./py/bin/python3 -m ivpm update -a ./packages/python/bin/pip install -U typing_extensions build + ./packages/python/bin/pip install pcpp cxxheaderparser ./packages/python/bin/python3 setup.py build_ext --inplace ./packages/python/bin/python3 -m build -n ls src diff --git a/ivpm.yaml b/ivpm.yaml index a113253..d5964a6 100644 --- a/ivpm.yaml +++ b/ivpm.yaml @@ -8,14 +8,6 @@ package: dep-sets: - - name: default - deps: - - name: pytest - src: pypi - # - name: pyvsc-dataclasses - # src: pypi - - name: fusesoc - src: pypi - name: default-dev deps: @@ -35,7 +27,6 @@ package: url: https://github.com/dv-flow/dv-flow-libhdlsim.git - name: dv-flow-mgr url: https://github.com/dv-flow/dv-flow-mgr.git - branch: mballance/taskdeps - name: verilator url: https://github.com/edapack/verilator-bin src: gh-rls @@ -107,6 +98,15 @@ package: - name: doxygen-filter-sv url: https://github.com/SeanOBoyle/DoxygenFilterSystemVerilog.git + - name: default + deps: + - name: pytest + src: pypi + # - name: pyvsc-dataclasses + # src: pypi + - name: fusesoc + src: pypi + env: - name: PATH path-prepend: diff --git a/src/hdl_if/impl/call/gen_sv_class.py b/src/hdl_if/impl/call/gen_sv_class.py index cbcaa28..d36a7ba 100644 --- a/src/hdl_if/impl/call/gen_sv_class.py +++ b/src/hdl_if/impl/call/gen_sv_class.py @@ -37,6 +37,7 @@ def __init__(self, out, ind="", uvm=False, deprecated=False): self._deprecated = deprecated self._have_imp = False self._struct_types = set() # Track struct types used in API + self._emitted_struct_types = set() # Track struct types already emitted (across multiple gen() calls) pass def _collect_methods(self, api: ApiDef): @@ -93,16 +94,20 @@ def gen(self, api : ApiDef): # Collect struct types used in the API self._collect_struct_types(api) - # Generate struct typedefs - if self._struct_types: - for struct_type in sorted(self._struct_types, key=lambda t: t.__name__): + # Generate struct typedefs only for types not already emitted + new_struct_types = self._struct_types - self._emitted_struct_types + if new_struct_types: + for struct_type in sorted(new_struct_types, key=lambda t: t.__name__): self.gen_struct_typedef(struct_type) self.println() - # Generate conversion functions for each struct - for struct_type in sorted(self._struct_types, key=lambda t: t.__name__): + # Generate conversion functions for each new struct + for struct_type in sorted(new_struct_types, key=lambda t: t.__name__): self.gen_struct_conversion_functions(struct_type) self.println() + + self._emitted_struct_types.update(new_struct_types) + self._struct_types = set() # Existing class generation (unchanged) # self.gen_class_interface_exp(api) diff --git a/src/hdl_if/share/dpi/pyhdl_if.sv b/src/hdl_if/share/dpi/pyhdl_if.sv index 7829538..417e6a9 100644 --- a/src/hdl_if/share/dpi/pyhdl_if.sv +++ b/src/hdl_if/share/dpi/pyhdl_if.sv @@ -207,7 +207,7 @@ package pyhdl_if; function automatic PyObject pyhdl_pi_if_HandleErr(PyObject obj); if (obj == null) begin $display("--> HandleErr"); - $stacktrace; + `STACKTRACE; PyErr_Print(); $display("<-- HandleErr"); end diff --git a/src/hdl_if/share/dpi/pyhdl_if_macros.svh b/src/hdl_if/share/dpi/pyhdl_if_macros.svh index 7e410ba..1501f17 100644 --- a/src/hdl_if/share/dpi/pyhdl_if_macros.svh +++ b/src/hdl_if/share/dpi/pyhdl_if_macros.svh @@ -29,7 +29,11 @@ $display x ; \ $finish ; +`ifdef XILINX_SIMULATOR +`define STACKTRACE $display("TODO: no stacktrace support in Xilinx simulator") +`else `define STACKTRACE $stacktrace +`endif `endif /* INCLUDED_PYHDL_IF_MACROS_SVH */ diff --git a/tests/unit/data/test_struct/struct_bfm.sv b/tests/unit/data/test_struct/struct_bfm.sv index c425b8a..5309de4 100644 --- a/tests/unit/data/test_struct/struct_bfm.sv +++ b/tests/unit/data/test_struct/struct_bfm.sv @@ -30,8 +30,8 @@ module struct_bfm Point_t stored_point; initial begin - StructBFM_Impl impl = new(); - StructTest_exp_impl test; + automatic StructBFM_Impl impl = new(); + automatic StructTest_exp_impl test; int fd; string line; diff --git a/tests/unit/data/test_struct/struct_comprehensive_bfm.sv b/tests/unit/data/test_struct/struct_comprehensive_bfm.sv index 98dcecf..7603a9b 100644 --- a/tests/unit/data/test_struct/struct_comprehensive_bfm.sv +++ b/tests/unit/data/test_struct/struct_comprehensive_bfm.sv @@ -90,8 +90,8 @@ module struct_comprehensive_bfm ComprehensiveBFM_imp_impl#(ComprehensiveBFM_Impl) bfm; initial begin - ComprehensiveBFM_Impl impl = new(); - ComprehensiveTest_exp_impl test; + automatic ComprehensiveBFM_Impl impl = new(); + automatic ComprehensiveTest_exp_impl test; int fd; string line; diff --git a/tests/unit/test_api_gen_sv_filtering.py b/tests/unit/test_api_gen_sv_filtering.py index 1a137d4..177e71f 100644 --- a/tests/unit/test_api_gen_sv_filtering.py +++ b/tests/unit/test_api_gen_sv_filtering.py @@ -450,3 +450,45 @@ def __init__(self): assert len(filtered) == 3 names = [a.name for a in filtered] assert names == ["First", "Second", "Third"] + + def test_no_duplicate_struct_typedefs_across_apis(self): + """Test that struct typedefs are not duplicated when multiple APIs share the same struct type.""" + + class Point(ct.Structure): + _fields_ = [ + ("x", ct.c_int32), + ("y", ct.c_int32), + ] + + @api + class FirstAPI(object): + @imp + async def func1(self, pt: Point): + pass + + @api + class SecondAPI(object): + @exp + def func2(self, pt: Point): + pass + + all_apis = ApiDefRgy.inst().getApis() + + out = io.StringIO() + gen = GenSVClass(out, ind="", uvm=False, deprecated=False) + for a in all_apis: + gen.gen(a) + sv_content = out.getvalue() + + # Count occurrences of the struct typedef — must appear exactly once + typedef_count = sv_content.count("typedef struct") + assert typedef_count == 1, ( + f"Expected 1 struct typedef for Point_t, but found {typedef_count}. " + f"Duplicate typedefs cause SV compilation errors." + ) + # Conversion function definitions must also appear exactly once + # (the name may also appear in calls within API method bodies) + assert sv_content.count("function Point_t pyhdl_if_py_to_struct_Point") == 1, \ + "pyhdl_if_py_to_struct_Point conversion function definition duplicated" + assert sv_content.count("function pyhdl_if::PyObject pyhdl_if_struct_to_py_Point") == 1, \ + "pyhdl_if_struct_to_py_Point conversion function definition duplicated"