Improve cpuif customization support. Add docs & testcases

This commit is contained in:
Alex Mykyta
2022-02-24 22:42:58 -08:00
parent 5324b594bf
commit da3ed05492
10 changed files with 179 additions and 17 deletions

View File

@@ -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

View File

@@ -1,3 +1,5 @@
.. _cpuif_axi4lite:
AMBA AXI4-Lite AMBA AXI4-Lite
============== ==============

View File

@@ -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!

View File

@@ -98,7 +98,7 @@ Links
cpuif/axi4lite cpuif/axi4lite
cpuif/passthrough cpuif/passthrough
cpuif/internal_protocol cpuif/internal_protocol
cpuif/advanced cpuif/customizing
.. toctree:: .. toctree::
:hidden: :hidden:

View File

@@ -12,7 +12,7 @@ if TYPE_CHECKING:
class CpuifBase: 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 = "" template_path = ""
def __init__(self, exp:'RegblockExporter', cpuif_reset:Optional['SignalNode'], data_width:int=32, addr_width:int=32): 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: def port_declaration(self) -> str:
raise NotImplementedError() 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: 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) loader = jj.FileSystemLoader(class_dir)
jj_env = jj.Environment( jj_env = jj.Environment(
loader=loader, loader=loader,

View File

@@ -16,15 +16,33 @@ class CpuifTestMode:
cpuif_cls = None # type: CpuifBase cpuif_cls = None # type: CpuifBase
# Files required by the DUT # Files required by the DUT
# Paths are relative to the class that assigns this
rtl_files = [] # type: List[str] rtl_files = [] # type: List[str]
# Files required by the sim testbench # Files required by the sim testbench
# Paths are relative to the class that assigns this
tb_files = [] # type: List[str] tb_files = [] # type: List[str]
# Path is relative to the class that assigns this
tb_template = "" 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() cwd = os.getcwd()
new_files = [] new_files = []
@@ -33,18 +51,23 @@ class CpuifTestMode:
os.path.join(class_dir, file), os.path.join(class_dir, file),
cwd cwd
) )
if relpath not in new_files: new_files.append(relpath)
new_files.append(relpath)
return new_files return new_files
def get_sim_files(self) -> List[str]: 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]: 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: 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) loader = jj.FileSystemLoader(class_dir)
jj_env = jj.Environment( jj_env = jj.Environment(
loader=loader, loader=loader,

View File

View File

@@ -0,0 +1,7 @@
addrmap top {
reg {
field {
sw=rw; hw=r;
} f = 0;
} r1;
};

View File

@@ -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()
)

View File

@@ -0,0 +1 @@
// USER TEMPLATE OVERRIDE