Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
2 changes: 1 addition & 1 deletion src/peakrdl_python/__about__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@

Variables that describes the peakrdl-python Package
"""
__version__ = "1.2.0"
__version__ = "1.2.1"
84 changes: 49 additions & 35 deletions src/peakrdl_python/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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__

Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions src/peakrdl_python/templates/addrmap.py.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
Expand Down
2 changes: 1 addition & 1 deletion src/peakrdl_python/templates/addrmap_register.py.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion tests/testcases/addr_map.rdl
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
123 changes: 123 additions & 0 deletions tests/unit_tests/test_building_inner_addrmap.py
Original file line number Diff line number Diff line change
@@ -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()
2 changes: 1 addition & 1 deletion tests/unit_tests/test_optimised_reg_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down