From da3ed054923a874b2c34edb104490d6b6e4f7dee Mon Sep 17 00:00:00 2001 From: Alex Mykyta Date: Thu, 24 Feb 2022 22:42:58 -0800 Subject: [PATCH] Improve cpuif customization support. Add docs & testcases --- docs/cpuif/advanced.rst | 7 --- docs/cpuif/axi4lite.rst | 2 + docs/cpuif/customizing.rst | 74 ++++++++++++++++++++++++++ docs/index.rst | 2 +- peakrdl/regblock/cpuif/base.py | 17 +++++- test/lib/cpuifs/base.py | 37 ++++++++++--- test/test_user_cpuif/__init__.py | 0 test/test_user_cpuif/regblock.rdl | 7 +++ test/test_user_cpuif/testcase.py | 49 +++++++++++++++++ test/test_user_cpuif/user_apb3_tmpl.sv | 1 + 10 files changed, 179 insertions(+), 17 deletions(-) delete mode 100644 docs/cpuif/advanced.rst create mode 100644 docs/cpuif/customizing.rst create mode 100644 test/test_user_cpuif/__init__.py create mode 100644 test/test_user_cpuif/regblock.rdl create mode 100644 test/test_user_cpuif/testcase.py create mode 100644 test/test_user_cpuif/user_apb3_tmpl.sv diff --git a/docs/cpuif/advanced.rst b/docs/cpuif/advanced.rst deleted file mode 100644 index b56496f..0000000 --- a/docs/cpuif/advanced.rst +++ /dev/null @@ -1,7 +0,0 @@ -Advanced Topics -=============== - -TODO: - -* How to override an interface's name, modport, signal names, whatever -* Creating your own custom CPU interface definition diff --git a/docs/cpuif/axi4lite.rst b/docs/cpuif/axi4lite.rst index a8861f7..446ae5a 100644 --- a/docs/cpuif/axi4lite.rst +++ b/docs/cpuif/axi4lite.rst @@ -1,3 +1,5 @@ +.. _cpuif_axi4lite: + AMBA AXI4-Lite ============== diff --git a/docs/cpuif/customizing.rst b/docs/cpuif/customizing.rst new file mode 100644 index 0000000..1be28f1 --- /dev/null +++ b/docs/cpuif/customizing.rst @@ -0,0 +1,74 @@ +Customizing your own CPU interface +================================== + +Bring your own SystemVerilog interface +-------------------------------------- + +This exporter comes pre-bundled with its own SystemVerilog interface declarations. +What if you already have your own SystemVerilog interface declaration that you prefer? + +Not a problem! As long as your interface definition is similar enough, it is easy +to customize and existing CPUIF definition. + + +The SystemVerilog interface definition bundled with this project for :ref:`cpuif_axi4lite` +uses the following style and naming conventions: + +* SystemVerilog interface type name is ``axi4lite_intf`` +* Defines modports named ``master`` and ``slave`` +* Interface signals are all upper-case: ``AWREADY``, ``AWVALID``, etc... + +Lets assume your preferred SV interface uses a slightly different naming convention: + +* SystemVerilog interface type name is ``axi4_lite_interface`` +* Modports are capitalized and use suffixes ``Master_mp`` and ``Slave_mp`` +* Interface signals are all lower-case: ``awready``, ``awvalid``, etc... + +Rather than rewriting a new CPU interface definition, you can extend and adjust the existing one: + +.. code-block:: python + + from peakrdl.regblock.cpuif.axi4lite import AXI4Lite_Cpuif + + class My_AXI4Lite(AXI4Lite_Cpuif): + @property + def port_declaration(self) -> str: + return "axi4_lite_interface.Slave_mp s_axil" + + def signal(self, name:str) -> str: + return "s_axil." + name.lower() + +Then use your custom CPUIF during export: + +.. code-block:: python + + exporter = RegblockExporter() + exporter.export( + root, "path/to/output_dir", + cpuif_cls=My_AXI4Lite + ) + + + +Custom CPU Interface Protocol +----------------------------- + +If you require a CPU interface protocol that is not included in this project, +you can define your own. + +1. Create a SystemVerilog CPUIF implementation template file. + + This contains the SystemVerilog implementation of the bus protocol. The logic + in this shall implement a translation between your custom protocol and the + :ref:`cpuif_protocol`. + + Reminder that this template will be preprocessed using Jinja, so you can use + some templating tags to dynamically render content. See the implementations of + existing CPU interfaces as an example. + +2. Create a Python class that defines your CPUIF + + Extend your class from :class:`peakrdl.regblock.cpuif.CpuifBase`. + Define the port declaration string, and provide a reference to your template file. + +3. Use your new CPUIF definition when exporting! diff --git a/docs/index.rst b/docs/index.rst index 2b0b814..8391e6e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -98,7 +98,7 @@ Links cpuif/axi4lite cpuif/passthrough cpuif/internal_protocol - cpuif/advanced + cpuif/customizing .. toctree:: :hidden: diff --git a/peakrdl/regblock/cpuif/base.py b/peakrdl/regblock/cpuif/base.py index 869ad51..090007f 100644 --- a/peakrdl/regblock/cpuif/base.py +++ b/peakrdl/regblock/cpuif/base.py @@ -12,7 +12,7 @@ if TYPE_CHECKING: class CpuifBase: - # Path is relative to class that defines it + # Path is relative to the location of the class that assigns this variable template_path = "" def __init__(self, exp:'RegblockExporter', cpuif_reset:Optional['SignalNode'], data_width:int=32, addr_width:int=32): @@ -25,8 +25,21 @@ class CpuifBase: def port_declaration(self) -> str: raise NotImplementedError() + + def _get_template_path_class_dir(self) -> str: + """ + Traverse up the MRO and find the first class that explicitly assigns + template_path. Returns the directory that contains the class definition. + """ + for cls in inspect.getmro(self.__class__): + if "template_path" in cls.__dict__: + class_dir = os.path.dirname(inspect.getfile(cls)) + return class_dir + raise RuntimeError + + def get_implementation(self) -> str: - class_dir = os.path.dirname(inspect.getfile(self.__class__)) + class_dir = self._get_template_path_class_dir() loader = jj.FileSystemLoader(class_dir) jj_env = jj.Environment( loader=loader, diff --git a/test/lib/cpuifs/base.py b/test/lib/cpuifs/base.py index 0adba78..6ee0d45 100644 --- a/test/lib/cpuifs/base.py +++ b/test/lib/cpuifs/base.py @@ -16,15 +16,33 @@ class CpuifTestMode: cpuif_cls = None # type: CpuifBase # Files required by the DUT + # Paths are relative to the class that assigns this rtl_files = [] # type: List[str] # Files required by the sim testbench + # Paths are relative to the class that assigns this tb_files = [] # type: List[str] + # Path is relative to the class that assigns this tb_template = "" - def _translate_paths(self, files: List[str]) -> List[str]: - class_dir = os.path.dirname(inspect.getfile(self.__class__)) + + def _get_class_dir_of_variable(self, varname:str) -> str: + """ + Traverse up the MRO and find the first class that explicitly assigns + the variable of name varname. Returns the directory that contains the + class definition. + """ + for cls in inspect.getmro(self.__class__): + if varname in cls.__dict__: + class_dir = os.path.dirname(inspect.getfile(cls)) + return class_dir + raise RuntimeError + + + def _get_file_paths(self, varname:str) -> List[str]: + class_dir = self._get_class_dir_of_variable(varname) + files = getattr(self, varname) cwd = os.getcwd() new_files = [] @@ -33,18 +51,23 @@ class CpuifTestMode: os.path.join(class_dir, file), cwd ) - if relpath not in new_files: - new_files.append(relpath) + new_files.append(relpath) return new_files + def get_sim_files(self) -> List[str]: - return self._translate_paths(self.rtl_files + self.tb_files) + files = self._get_file_paths("rtl_files") + self._get_file_paths("tb_files") + unique_files = [] + [unique_files.append(f) for f in files if f not in unique_files] + return unique_files + def get_synth_files(self) -> List[str]: - return self._translate_paths(self.rtl_files) + return self._get_file_paths("rtl_files") + def get_tb_inst(self, tb_cls: 'SimTestCase', exporter: 'RegblockExporter') -> str: - class_dir = os.path.dirname(inspect.getfile(self.__class__)) + class_dir = self._get_class_dir_of_variable("tb_template") loader = jj.FileSystemLoader(class_dir) jj_env = jj.Environment( loader=loader, diff --git a/test/test_user_cpuif/__init__.py b/test/test_user_cpuif/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test_user_cpuif/regblock.rdl b/test/test_user_cpuif/regblock.rdl new file mode 100644 index 0000000..edc1829 --- /dev/null +++ b/test/test_user_cpuif/regblock.rdl @@ -0,0 +1,7 @@ +addrmap top { + reg { + field { + sw=rw; hw=r; + } f = 0; + } r1; +}; diff --git a/test/test_user_cpuif/testcase.py b/test/test_user_cpuif/testcase.py new file mode 100644 index 0000000..ce2383c --- /dev/null +++ b/test/test_user_cpuif/testcase.py @@ -0,0 +1,49 @@ +import os + +from peakrdl.regblock.cpuif.apb3 import APB3_Cpuif +from ..lib.cpuifs.apb3 import APB3 +from ..lib.base_testcase import BaseTestCase + + +#------------------------------------------------------------------------------- + +class ClassOverride_Cpuif(APB3_Cpuif): + @property + def port_declaration(self) -> str: + return "user_apb3_intf.slave s_apb" + +class ClassOverride_cpuiftestmode(APB3): + cpuif_cls = ClassOverride_Cpuif + + +class Test_class_override(BaseTestCase): + cpuif = ClassOverride_cpuiftestmode() + + def test_override_success(self): + output_file = os.path.join(self.get_run_dir(), "regblock.sv") + with open(output_file, "r") as f: + self.assertIn( + "user_apb3_intf.slave s_apb", + f.read() + ) + +#------------------------------------------------------------------------------- + +class TemplateOverride_Cpuif(APB3_Cpuif): + # contains the text "USER TEMPLATE OVERRIDE" + template_path = "user_apb3_tmpl.sv" + +class TemplateOverride_cpuiftestmode(APB3): + cpuif_cls = TemplateOverride_Cpuif + + +class Test_template_override(BaseTestCase): + cpuif = TemplateOverride_cpuiftestmode() + + def test_override_success(self): + output_file = os.path.join(self.get_run_dir(), "regblock.sv") + with open(output_file, "r") as f: + self.assertIn( + "USER TEMPLATE OVERRIDE", + f.read() + ) diff --git a/test/test_user_cpuif/user_apb3_tmpl.sv b/test/test_user_cpuif/user_apb3_tmpl.sv new file mode 100644 index 0000000..bea1976 --- /dev/null +++ b/test/test_user_cpuif/user_apb3_tmpl.sv @@ -0,0 +1 @@ +// USER TEMPLATE OVERRIDE