Skip to content

Commit

Permalink
Merge pull request #97 from krcb197/0.6.1-Maintenance-Release
Browse files Browse the repository at this point in the history
0.6.1 maintenance release
  • Loading branch information
krcb197 authored May 1, 2023
2 parents a1f4c6b + 062ebdf commit 1c1391e
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 118 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
![CI](https://github.com/krcb197/PeakRDL-python/actions/workflows/action.yaml/badge.svg)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/peakrdl-python.svg)](https://pypi.org/project/peakrdl-python)
[![Documentation Status](https://readthedocs.org/projects/peakrdl-python/badge/?version=latest)](https://peakrdl-python.readthedocs.io/en/latest/?badge=latest)
[![Downloads](https://static.pepy.tech/badge/peakrdl-python)](https://pepy.tech/project/peakrdl-python)

# PeakRDL-python
Generate Python wrapper for a register model compiled SystemRDL input
Expand Down
133 changes: 99 additions & 34 deletions generate_testcases.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,111 @@
import sys
import os

import src.peakrdl_python.peakpython as pp

from glob import glob
from typing import Optional, List

from systemrdl import RDLCompiler # type: ignore
from systemrdl.node import Node, AddrmapNode # type: ignore
from peakrdl_ipxact import IPXACTImporter # type: ignore
from peakrdl_python import PythonExporter # type: ignore

test_case_path = os.path.join('tests', 'testcases')

if len(sys.argv) == 1:
testcases = glob(os.path.join(test_case_path,'*.rdl'))
else:
testcases = glob(os.path.join(test_case_path,'{}.rdl').format(sys.argv[1]))

#-------------------------------------------------------------------------------
results = {}
for case in testcases:
print("Case: ", case)
rdl_file = case
testcase_name = os.path.splitext(os.path.basename(case))[0]

if testcase_name == 'multifile':
# this needs the simple.xml file included
root = pp.compile_rdl(rdl_file, ipxact_files=[os.path.join(test_case_path, 'simple.xml')])
elif testcase_name == 'multi_block':
# this needs the simple.xml file included
root = pp.compile_rdl(rdl_file, ipxact_files=[os.path.join(test_case_path, 'block_a.xml'),
os.path.join(test_case_path, 'block_b.xml')])

def compile_rdl(infile: str,
incl_search_paths: Optional[List[str]] = None,
top: Optional[str] = None,
ipxact_files: Optional[List[str]] = None) -> AddrmapNode:
"""
Compile the systemRDL
Args:
infile: top level systemRDL file
incl_search_paths: list of additional paths where dependent systemRDL files can be
retrived from. Set to ```none``` if no additional paths are required.
top: name of the top level address map
ipxact_files: any IP-XACT files that must be precompiled before compiling the systemRDL
Returns:
"""
rdlc = RDLCompiler()
if ipxact_files is not None:

ipxact = IPXACTImporter(rdlc)
if isinstance(ipxact_files, list):
for ipxact_file in ipxact_files:
ipxact.import_file(ipxact_file)
else:
raise ValueError(f'This ipxact_files should be a list got {type(ipxact_files)}')

rdlc.compile_file(infile, incl_search_paths=incl_search_paths)
return rdlc.elaborate(top_def_name=top).top


def generate(root: Node, outdir: str,
autoformatoutputs: bool = True, asyncoutput: bool = False,
skip_test_case_generation: bool = False) -> List[str]:
"""
Generate a PeakRDL output package from compiled systemRDL
Args:
root: node in the systemRDL from which the code should be generated
outdir: directory to store the result in
autoformatoutputs: If set to True the code will be run through autopep8 to
clean it up. This can slow down large jobs or mask problems
asyncoutput: If set to True the code build a register model with async operations to
access the harware layer
Returns:
List of strings with the module names generated
"""
print(f'Info: Generating python for {root.inst_name} in {outdir}')
modules = PythonExporter().export(root, outdir, # type: ignore[no-untyped-call]
autoformatoutputs=autoformatoutputs,
asyncoutput=asyncoutput,
skip_test_case_generation=skip_test_case_generation)

return modules


if __name__ == '__main__':
if len(sys.argv) == 1:
testcases = glob(os.path.join(test_case_path,'*.rdl'))
else:
root = pp.compile_rdl(rdl_file)
testcases = glob(os.path.join(test_case_path,'{}.rdl').format(sys.argv[1]))

#-------------------------------------------------------------------------------
results = {}
for case in testcases:
print("Case: ", case)
rdl_file = case
testcase_name = os.path.splitext(os.path.basename(case))[0]

if testcase_name == 'multifile':
# this needs the simple.xml file included
root = compile_rdl(rdl_file, ipxact_files=[os.path.join(test_case_path, 'simple.xml')])
elif testcase_name == 'multi_block':
# this needs the simple.xml file included
root = compile_rdl(rdl_file, ipxact_files=[os.path.join(test_case_path, 'block_a.xml'),
os.path.join(test_case_path,
'block_b.xml')])
else:
root = compile_rdl(rdl_file)

for autoformatoutputs, asyncoutput, folder_name in [(False, False, 'raw'),
(True, False, 'autopep8'),
(False, True, 'raw_async'),
(True, True, 'autopep8_async')]:
_ = pp.generate(root, os.path.join('testcase_output', folder_name),
autoformatoutputs=False,
asyncoutput=asyncoutput)
for autoformatoutputs, asyncoutput, folder_name in [(False, False, 'raw'),
(True, False, 'autopep8'),
(False, True, 'raw_async'),
(True, True, 'autopep8_async')]:
_ = generate(root, os.path.join('testcase_output', folder_name),
autoformatoutputs=False,
asyncoutput=asyncoutput)

module_fqfn = os.path.join('testcase_output', folder_name, '__init__.py')
with open(module_fqfn, 'w', encoding='utf-8') as fid:
fid.write('pass\n')
module_fqfn = os.path.join('testcase_output', folder_name, '__init__.py')
with open(module_fqfn, 'w', encoding='utf-8') as fid:
fid.write('pass\n')

print("\n-----------------------------------------------------------------\n")
print("\n-----------------------------------------------------------------\n")

print("\tALL TESTS COMPLETED\n")
print("\tALL TESTS COMPLETED\n")
2 changes: 0 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@
install_requires=[
"systemrdl-compiler>=1.24.0",
"autopep8",
"pylint",
"coverage",
"jinja2",
"asynctest;python_version<'3.8'",
"peakrdl-ipxact"
Expand Down
2 changes: 1 addition & 1 deletion src/peakrdl_python/__about__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""
Variables that describes the PeakRDL Python Package
"""
__version__ = "0.6.0"
__version__ = "0.6.1"
7 changes: 7 additions & 0 deletions src/peakrdl_python/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Main Classes for the PeakRDL Python
"""
import os
import warnings
from pathlib import Path
from shutil import copyfile
from typing import List, NoReturn, Iterable, Tuple
Expand Down Expand Up @@ -122,6 +123,12 @@ def export(self, node: Node, path: str,
else:
top_block = node

# support for autopep8 will be removed from a future release
if autoformatoutputs is True:
warnings.warn('Autoformating the generated code in the export will be deprecated in a'
'future release. It is recommended this is do outside of peakrdl python',
category=PendingDeprecationWarning)

package_path = os.path.join(path, node.inst_name)
self._create_empty_package(package_path=package_path,
skip_test_case_generation=skip_test_case_generation)
Expand Down
66 changes: 62 additions & 4 deletions src/peakrdl_python/lib/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,17 @@ def readable_fields(self) -> Iterator[Union['FieldReadOnly', 'FieldReadWrite']]:
generator that produces has all the readable fields within the register
"""

@abstractmethod
def read_fields(self) -> Dict['str', Union[bool, Enum, int]]:
"""
read the register and return a dictionary of the field values
"""
return_dict: Dict['str', Union[bool, Enum, int]] = {}
with self.single_read() as reg:
for field in reg.readable_fields:
return_dict[field.inst_name] = field.read()

return return_dict



class RegWriteOnly(Reg, ABC):
Expand Down Expand Up @@ -378,12 +384,32 @@ def read(self) -> int:

return super().read()

@abstractmethod
def write_fields(self, **kwargs) -> None: # type: ignore[no-untyped-def]
"""
Do a read-modify-write to the register, updating any field included in
the arguments
"""
if len(kwargs) == 0:
raise ValueError('no command args')

with self.single_read_modify_write() as reg:
for field_name, field_value in kwargs.items():
if field_name not in reg.systemrdl_python_child_name_map.values():
raise ValueError(f'{field_name} is not a member of the register')

field = getattr(reg, field_name)
field.write(field_value)

def read_fields(self) -> Dict['str', Union[bool, Enum, int]]:
"""
read the register and return a dictionary of the field values
"""
return_dict: Dict['str', Union[bool, Enum, int]] = {}
with self.single_read_modify_write(skip_write=True) as reg:
for field in reg.readable_fields:
return_dict[field.inst_name] = field.read()

return return_dict


class RegAsyncReadOnly(Reg, ABC):
Expand Down Expand Up @@ -486,11 +512,16 @@ def readable_fields(self) -> Iterator[Union['FieldAsyncReadOnly', 'FieldAsyncRea
generator that produces has all the readable fields within the register
"""

@abstractmethod
async def read_fields(self) -> Dict[str, Union[int, Enum, bool]]:
async def read_fields(self) -> Dict['str', Union[bool, Enum, int]]:
"""
asynchronously read the register and return a dictionary of the field values
"""
return_dict: Dict['str', Union[bool, Enum, int]] = {}
async with self.single_read() as reg:
for field in reg.readable_fields:
return_dict[field.inst_name] = await field.read()

return return_dict


class RegAsyncWriteOnly(Reg, ABC):
Expand Down Expand Up @@ -681,6 +712,33 @@ async def read(self) -> int:

return await super().read()

async def read_fields(self) -> Dict['str', Union[bool, Enum, int]]:
"""
asynchronously read the register and return a dictionary of the field values
"""
return_dict: Dict['str', Union[bool, Enum, int]] = {}
async with self.single_read_modify_write(skip_write=True) as reg:
for field in reg.readable_fields:
return_dict[field.inst_name] = await field.read()

return return_dict

async def write_fields(self, **kwargs) -> None: # type: ignore[no-untyped-def]
"""
asynchronously read-modify-write to the register, updating any field included in
the arguments
"""
if len(kwargs) == 0:
raise ValueError('no command args')

async with self.single_read_modify_write() as reg:
for field_name, field_value in kwargs.items():
if field_name not in reg.systemrdl_python_child_name_map.values():
raise ValueError(f'{field_name} is not a member of the register')

field = getattr(reg, field_name)
await field.write(field_value)


ReadableRegister = Union[RegReadOnly, RegReadWrite]
WritableRegister = Union[RegWriteOnly, RegReadWrite]
Expand Down
50 changes: 12 additions & 38 deletions src/peakrdl_python/peakpython.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@
#!/usr/bin/env python3
import argparse
import os
import subprocess
import unittest.loader
import warnings
from typing import List, Optional
import pathlib

import coverage # type: ignore

from systemrdl import RDLCompiler # type: ignore
from systemrdl.node import Node, AddrmapNode # type: ignore
from peakrdl_ipxact import IPXACTImporter # type: ignore
Expand Down Expand Up @@ -51,13 +49,11 @@ def build_command_line_parser() -> argparse.ArgumentParser:
help='directory of user templates to override the default ones')
checker = parser.add_argument_group('post-generate checks')
checker.add_argument('--lint', action='store_true',
help='run pylint on the generated python')
help='run pylint on the generated python (support removed at 0.6.1)')
checker.add_argument('--test', action='store_true',
help='run unittests for the created')
checker.add_argument('--coverage', action='store_true',
help='run a coverage report on the unittests')
checker.add_argument('--html_coverage_out',
help='output director (default: %(default)s)')
help='run a coverage report on the unittests (support removed at 0.6.1)')
parser.add_argument('--skip_test_case_generation', action='store_true',
help='skip the generation of the test cases')

Expand Down Expand Up @@ -121,22 +117,6 @@ def generate(root:Node, outdir:str,

return modules

def run_lint(root:str, outdir:str) -> None:
"""
Run the lint checks using pylint on a directory
Args:
root: name of the generated package (directory)
outdir: location where the package has been written
Returns:
"""
subprocess.run(['pylint', '--rcfile',
os.path.join('tests','pylint.rc'),
os.path.join(outdir, root)],
check=False)

def main_function() -> None:
"""
Main function for the Command Line tool, this needs to be separated out so that it can be
Expand Down Expand Up @@ -165,33 +145,27 @@ def main_function() -> None:
generate(spec, args.outdir, autoformatoutputs=args.autoformat,
skip_test_case_generation=args.skip_test_case_generation,
asyncoutput=args.asyncoutput)

if args.lint:
print('***************************************************************')
print('* Lint Checks *')
print('***************************************************************')
run_lint(outdir=args.outdir, root=spec.inst_name)
raise NotImplementedError('Support for running linting checks was removed at 0.6.1, '
'pylint can be run seperatly on the generated code if needed')
if args.test:
print('***************************************************************')
print('* Unit Test Run *')
print('***************************************************************')
if args.coverage:
cov = coverage.Coverage(
include=[f'*\\{spec.inst_name}\\reg_model\\*.py',
f'*\\{spec.inst_name}\\tests\\*.py'])
cov.start()
raise NotImplementedError('Support for geneating a coverage report was removed at '
'0.6.1, this can be done separately if needed')
tests = unittest.TestLoader().discover(
start_dir=os.path.join(args.outdir, spec.inst_name, 'tests'),
top_level_dir=args.outdir)
runner = unittest.TextTestRunner()
runner.run(tests)

if args.coverage:
cov.stop()

if args.html_coverage_out is not None:
cov.html_report(directory=args.html_coverage_out)
if __name__ == '__main__':

warnings.warn('The peakpython command line option will be removed in a future release. '
'Command line functionality should be used via the see '
'https://peakrdl-python.readthedocs.io/en/latest/command_line.html',
category=PendingDeprecationWarning)

if __name__ == '__main__':
main_function()
Loading

0 comments on commit 1c1391e

Please sign in to comment.