diff --git a/.github/workflows/action.yaml b/.github/workflows/action.yaml index b739854d..8c07a887 100644 --- a/.github/workflows/action.yaml +++ b/.github/workflows/action.yaml @@ -202,6 +202,11 @@ jobs: python tests/alternative_templates_toml/header_check.py --generated_package_location peakrdl_out/simple_user_template/ --top_name basic peakrdl python tests/testcases/basic.rdl -o peakrdl_out/dynamic_user_template/ --peakrdl-cfg tests/alternative_templates_dynamic_toml/peakrdl.toml python tests/alternative_templates_dynamic_toml/header_check.py --generated_package_location peakrdl_out/dynamic_user_template/ --top_name basic + + + peakrdl python tests/testcases/addr_map.rdl -o peakrdl_out/addr_map/ + peakrdl python tests/testcases/addr_map.rdl -o peakrdl_out/addr_map/ -t "child_addr_map_type_c" + python -m unittest discover -s peakrdl_out/addr_map/ - name: Check Examples run: | diff --git a/src/peakrdl_python/__about__.py b/src/peakrdl_python/__about__.py index eabb6234..a187f0c7 100644 --- a/src/peakrdl_python/__about__.py +++ b/src/peakrdl_python/__about__.py @@ -17,4 +17,4 @@ Variables that describes the peakrdl-python Package """ -__version__ = "1.2.0" +__version__ = "1.2.1" diff --git a/src/peakrdl_python/exporter.py b/src/peakrdl_python/exporter.py index fd982b91..d02f3564 100644 --- a/src/peakrdl_python/exporter.py +++ b/src/peakrdl_python/exporter.py @@ -602,40 +602,40 @@ def export(self, node: Union[RootNode, AddrmapNode], path: str, *, Generated Python Code and Testbench Args: - node (str) : Top-level node to export. Can be the top-level `RootNode` or any - internal `AddrmapNode`. - path (str) : Output package path. - asyncoutput (bool) : If set this builds a register model with async callbacks - skip_test_case_generation (bool): skip generation the generation of the test cases - delete_existing_package_content (bool): delete any python files in the package - location, normally left over from previous - operations - skip_library_copy (bool): skip copy the libraries to the generated package, this is - useful to turn off when developing peakrdl python to avoid - editing the wrong copy of the library. It also avoids the - GPL code being part of the package for distribution, - However, this means the end-user is responsible for - installing the libraries. - legacy_block_access (bool): version 0.8 changed the block access methods from using - arrays to to lists. This allows memory widths of other - than 8, 16, 32, 64 to be supported which are legal in - systemRDL. The legacy mode with Arrays is still in - the tool and will be turned on by default for a few - releases. - show_hidden (bool) : By default any item (Address Map, Regfile, Register, Memory or - Field) with the systemRDL User Defined Property (UDP) - ``python_hide`` set to true will not be included in the generated - python code. This behaviour can be overridden by setting this - property to true. + node: Top-level node to export. Can be the top-level `RootNode` or any + internal `AddrmapNode`. + path: Output package path. + asyncoutput: If set this builds a register model with async callbacks + skip_test_case_generation: skip generation the generation of the test cases + delete_existing_package_content: delete any python files in the package + location, normally left over from previous + operations + skip_library_copy: skip copy the libraries to the generated package, this is + useful to turn off when developing peakrdl python to avoid + editing the wrong copy of the library. It also avoids the + GPL code being part of the package for distribution, + However, this means the end-user is responsible for + installing the libraries. + legacy_block_access: version 0.8 changed the block access methods from using + arrays to to lists. This allows memory widths of other + than 8, 16, 32, 64 to be supported which are legal in + systemRDL. The legacy mode with Arrays is still in + the tool and will be turned on by default for a few + releases. + show_hidden: By default any item (Address Map, Regfile, Register, Memory or + Field) with the systemRDL User Defined Property (UDP) + ``python_hide`` set to true will not be included in the generated + python code. This behaviour can be overridden by setting this + property to true. user_defined_properties_to_include : A list of strings of the names of user-defined properties to include. Set to None for nothing to appear. - hidden_inst_name_regex (str) : A regular expression which will hide any fully - qualified instance name that matches, set to None to - for this to have no effect - legacy_enum_type (bool): version 1.2 introduced a new Enum type that allows system - rdl ``name`` and ``desc`` properties on field encoding - to be included. The legacy mode uses python IntEnum. + hidden_inst_name_regex: A regular expression which will hide any fully + qualified instance name that matches, set to None to + for this to have no effect + legacy_enum_type: version 1.2 introduced a new Enum type that allows system + rdl ``name`` and ``desc`` properties on field encoding + to be included. The legacy mode uses python IntEnum. Returns: @@ -789,23 +789,36 @@ def _raise_template_error(self, message: str) -> NoReturn: """ raise PythonExportTemplateError(message) + def __true_root(self, root_node: Union[AddrmapNode,RootNode]) -> RootNode: + if not isinstance(root_node, RootNode): + if isinstance(root_node, AddrmapNode): + while not isinstance(root_node, RootNode): + root_node = root_node.parent + + return root_node + def _fully_qualified_enum_type(self, field_enum: UserEnumMeta, - root_node: AddressableNode, + root_node: Union[AddrmapNode,RootNode], owning_field: FieldNode, hide_node_func: HideNodeCallback) -> str: """ Returns the fully qualified class type name, for an enum """ + # in the case where the node of the peakrdl python wrappers is not the real + # root node of the elaborated systemRDL, need to find the true Root Node. This is + # important as the concatenated name can to be outside the node being built + root_node = self.__true_root(root_node=root_node) + if not hasattr(field_enum, '_parent_scope'): - # this happens if the enum is has been declared in an IPXACT file + # this happens if the enum has been declared in an IPXACT file # which is imported return self._lookup_type_name(owning_field) + '_' + field_enum.__name__ parent_scope = getattr(field_enum, '_parent_scope') if parent_scope is None: - # this happens if the enum is has been declared in an IPXACT file + # this happens if the enum has been declared in an IPXACT file # which is imported return self._lookup_type_name(owning_field) + '_' + field_enum.__name__ @@ -820,7 +833,8 @@ def _fully_qualified_enum_type(self, raise RuntimeError('Failed to find parent node to reference') - def _get_dependent_enum(self, node: AddressableNode, hide_node_func: HideNodeCallback) -> \ + def _get_dependent_enum( + self, node: Union[AddrmapNode, RootNode], hide_node_func: HideNodeCallback) -> \ Iterable[tuple[UserEnumMeta, FieldNode]]: """ iterable of enums which is used by a descendant of the input node, diff --git a/src/peakrdl_python/templates/addrmap.py.jinja b/src/peakrdl_python/templates/addrmap.py.jinja index ebaca816..0208937b 100644 --- a/src/peakrdl_python/templates/addrmap.py.jinja +++ b/src/peakrdl_python/templates/addrmap.py.jinja @@ -396,9 +396,9 @@ class {{property_enum.type_name}}_property_enumcls({% if legacy_enum_type %}IntE {% if uses_enum %} # root level enum definitions -{%- for enum_needed, owning_field in get_dependent_enum(top_node.parent, hide_node_func) %} +{%- for enum_needed, owning_field in get_dependent_enum(top_node, hide_node_func) %} @unique -class {{get_fully_qualified_enum_type(enum_needed, top_node.parent, owning_field, hide_node_func)}}_enumcls({% if legacy_enum_type %}IntEnum{% else %}SystemRDLEnum{% endif %}): +class {{get_fully_qualified_enum_type(enum_needed, top_node, owning_field, hide_node_func)}}_enumcls({% if legacy_enum_type %}IntEnum{% else %}SystemRDLEnum{% endif %}): {% for value_of_enum_needed in enum_needed -%} {{ value_of_enum_needed.name.upper() }} = {% if legacy_enum_type %}{{ value_of_enum_needed.value }}{% else %}SystemRDLEnumEntry(int_value={{value_of_enum_needed.value}}, name={%- if value_of_enum_needed.rdl_name is not none -%}'{{value_of_enum_needed.rdl_name}}'{% else %}None{% endif %}, desc={%- if value_of_enum_needed.rdl_desc is not none -%}'{{value_of_enum_needed.rdl_desc}}'{% else %}None{% endif %}){% endif %} {%- if value_of_enum_needed.rdl_desc is not none -%}# {{ value_of_enum_needed.rdl_desc }} {%- endif %} diff --git a/src/peakrdl_python/templates/addrmap_register.py.jinja b/src/peakrdl_python/templates/addrmap_register.py.jinja index 3df74711..bb24e27a 100644 --- a/src/peakrdl_python/templates/addrmap_register.py.jinja +++ b/src/peakrdl_python/templates/addrmap_register.py.jinja @@ -75,7 +75,7 @@ class {{get_fully_qualified_type_name(node)}}_cls(Reg{% if asyncoutput %}Async{% is_volatile={{child_node.is_hw_writable}}), logger_handle=logger_handle+'.{{child_node.inst_name}}', inst_name='{{child_node.inst_name}}', - field_type={% if 'encode' in child_node.list_properties() %}{{ get_fully_qualified_enum_type(child_node.get_property('encode'), top_node.parent, child_node, hide_node_func) + '_enumcls' }}{% else %}int{% endif %}) + field_type={% if 'encode' in child_node.list_properties() %}{{ get_fully_qualified_enum_type(child_node.get_property('encode'), top_node, child_node, hide_node_func) + '_enumcls' }}{% else %}int{% endif %}) {%- endfor %} @property diff --git a/tests/testcases/addr_map.rdl b/tests/testcases/addr_map.rdl index 11075a6b..54438f3b 100644 --- a/tests/testcases/addr_map.rdl +++ b/tests/testcases/addr_map.rdl @@ -1,8 +1,14 @@ +enum an_enum { + value1 = 0; + value2 = 1; + }; + reg regtype_a { default sw = rw; default hw = r; - field {} basicfield_a[31:0]; + field {} basicfield_a[20:0]; + field { encode = an_enum; fieldwidth=1; } basicfield_b; }; addrmap child_addr_map_type_a { diff --git a/tests/unit_tests/test_building_inner_addrmap.py b/tests/unit_tests/test_building_inner_addrmap.py new file mode 100644 index 00000000..d0de10d5 --- /dev/null +++ b/tests/unit_tests/test_building_inner_addrmap.py @@ -0,0 +1,123 @@ +""" +Test that it is possible to build an addrmap inside the compiled structure and compare it to +a result of the top level +""" +import unittest +import os +import tempfile +import sys + +from pathlib import Path + +from contextlib import contextmanager + +from peakrdl_python import PythonExporter +from peakrdl_python.lib import NormalCallbackSet +from peakrdl_python.lib import AddressMap +from peakrdl_python.sim_lib.dummy_callbacks import dummy_write_block, dummy_read_block +from peakrdl_python import compiler_with_udp_registers + + +# this assumes the current file is in the unit_test folder under tests +test_path = Path(__file__).parent.parent +test_cases = test_path / 'testcases' + +class TestTopAndInner(unittest.TestCase): + """ + Test class building both the top level address map and one of the inner address maps + """ + test_case_path = test_cases + + + @contextmanager + def build_python_wrappers_and_make_instance(self): + """ + Context manager to build the python wrappers for a value of show_hidden, then import them + and clean up afterwards + """ + test_case = 'addr_map' + test_case_file = test_case +'.rdl' + test_case_reg_model_cls = test_case + '_cls' + inner_addr_map = 'child_c' + inner_case_reg_model_cls = inner_addr_map + '_cls' + + # compile the code for the test + rdlc = compiler_with_udp_registers() + rdlc.compile_file(os.path.join(self.test_case_path, test_case_file)) + spec = rdlc.elaborate(top_def_name=test_case) + + exporter = PythonExporter() + + with tempfile.TemporaryDirectory() as tmpdirname: + + exporter.export(node=spec.top, + path=tmpdirname, + asyncoutput=False, + delete_existing_package_content=False, + skip_library_copy=True, + skip_test_case_generation=True, + legacy_block_access=False, + show_hidden=False) + exporter.export(node=spec.top.get_child_by_name(inner_addr_map), + path=tmpdirname, + asyncoutput=False, + delete_existing_package_content=False, + skip_library_copy=True, + skip_test_case_generation=True, + legacy_block_access=False, + show_hidden=False) + + + # add the temp directory to the python path so that it can be imported from + sys.path.append(tmpdirname) + + def generate_instance(regmodel_name: str, regmodel_cls: str) -> AddressMap: + """ + Import the register model python code and make an instance of the address map + """ + + reg_model_module = __import__( + regmodel_name + '.reg_model.' + regmodel_name, + globals(), locals(), [regmodel_cls], + 0) + dut_cls = getattr(reg_model_module, regmodel_cls) + + return dut_cls(callbacks=NormalCallbackSet( + read_block_callback=dummy_read_block, + write_block_callback=dummy_write_block)) + + + yield (generate_instance(test_case, test_case_reg_model_cls), + generate_instance(inner_addr_map, inner_case_reg_model_cls)) + + sys.path.remove(tmpdirname) + + def test_top_and_inner(self): + """ + Test that the inner address map generated separately from the full stack are the same + """ + + def compare_addrmap_instances(a: AddressMap, b: AddressMap): + """ + Walk all the sections of two address maps to make sure they are the same + """ + self.assertEqual(a.inst_name, b.inst_name) + for a_child, b_child in zip(a.get_sections(unroll=True), b.get_sections(unroll=True)): + compare_addrmap_instances(a_child, b_child) + + for a_child, b_child in zip(a.get_registers(unroll=True), b.get_registers(unroll=True)): + self.assertEqual(a_child.inst_name, b_child.inst_name) + self.assertEqual(a_child.address, b_child.address) + + for a_child_field, b_child_field in zip(a_child.fields, b_child.fields): + self.assertEqual(a_child_field.inst_name, b_child_field.inst_name) + self.assertEqual(a_child_field.lsb, b_child_field.lsb) + self.assertEqual(a_child_field.msb, b_child_field.msb) + + + with self.build_python_wrappers_and_make_instance() as \ + (top_reg_model, inner_reg_model): + compare_addrmap_instances(top_reg_model.child_c, inner_reg_model) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/unit_tests/test_optimised_reg_array.py b/tests/unit_tests/test_optimised_reg_array.py index 77cc6dcb..6bf02eb4 100644 --- a/tests/unit_tests/test_optimised_reg_array.py +++ b/tests/unit_tests/test_optimised_reg_array.py @@ -67,7 +67,7 @@ def setUp(self) -> None: class DUTWrapper(AddressMap): """ - Address map to to wrap the register array being tested + Address map to wrap the register array being tested """ # pylint: disable=too-many-arguments,duplicate-code