From 861a020aff390cbb55fa0d9555aee273c631c678 Mon Sep 17 00:00:00 2001 From: Dennis Date: Sun, 2 May 2021 00:58:43 +0200 Subject: [PATCH] Initial commit of SRDL2SV The compiler in this commit is still useless and only contains a very rough skeleton of the code. SRDL2SV is only able to create a simple register with hw=rw/sw=rw fields. --- .gitignore | 141 +++++++++++++++++ LIMITATIONS.md | 2 + srdl2sv/__init__.py | 0 srdl2sv/components/__init__.py | 0 srdl2sv/components/addrmap.py | 48 ++++++ srdl2sv/components/field.py | 183 +++++++++++++++++++++++ srdl2sv/components/register.py | 81 ++++++++++ srdl2sv/components/templates/__init__.py | 0 srdl2sv/components/templates/addrmap.sv | 9 ++ srdl2sv/components/templates/fields.yaml | 31 ++++ srdl2sv/components/templates/regs.yaml | 20 +++ srdl2sv/main.py | 36 +++++ 12 files changed, 551 insertions(+) create mode 100644 .gitignore create mode 100644 LIMITATIONS.md create mode 100644 srdl2sv/__init__.py create mode 100644 srdl2sv/components/__init__.py create mode 100644 srdl2sv/components/addrmap.py create mode 100644 srdl2sv/components/field.py create mode 100644 srdl2sv/components/register.py create mode 100644 srdl2sv/components/templates/__init__.py create mode 100644 srdl2sv/components/templates/addrmap.sv create mode 100644 srdl2sv/components/templates/fields.yaml create mode 100644 srdl2sv/components/templates/regs.yaml create mode 100755 srdl2sv/main.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0e9cc79 --- /dev/null +++ b/.gitignore @@ -0,0 +1,141 @@ +# Grabbed from https://github.com/github/gitignore/blob/master/Python.gitignore on 05/02/2021 + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + diff --git a/LIMITATIONS.md b/LIMITATIONS.md new file mode 100644 index 0000000..e1697a2 --- /dev/null +++ b/LIMITATIONS.md @@ -0,0 +1,2 @@ +- Depth of an array is limited to X +- [Any limitations to the systemrdl-compiler](https://systemrdl-compiler.readthedocs.io/en/latest/known_issues.html) also apply to the SystemRDL2SystemVerilog compiler. diff --git a/srdl2sv/__init__.py b/srdl2sv/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/srdl2sv/components/__init__.py b/srdl2sv/components/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/srdl2sv/components/addrmap.py b/srdl2sv/components/addrmap.py new file mode 100644 index 0000000..651d5c2 --- /dev/null +++ b/srdl2sv/components/addrmap.py @@ -0,0 +1,48 @@ +import yaml +import re + +from systemrdl import RDLCompiler, RDLCompileError, RDLWalker, RDLListener, node +from systemrdl.node import FieldNode + + +# Local packages +from components.register import Register +from . import templates + +# Import templates +try: + import importlib.resources as pkg_resources +except ImportError: + # Try backported to PY<37 `importlib_resources`. + import importlib_resources as pkg_resources + +class AddrMap: + def __init__(self, rdlc: RDLCompiler, obj: node.RootNode): + + self.rdlc = rdlc + + template = pkg_resources.read_text(templates, 'addrmap.sv') + + # Read template for SystemVerilog module + tmpl_addrmap = re.compile("{addrmap_name}") + self.rtl = tmpl_addrmap.sub(obj.inst_name, template) + + # Empty list of register logic + self.registers = set() + + # Traverse through children + for child in obj.children(): + if isinstance(child, node.AddrmapNode): + pass + elif isinstance(child, node.RegfileNode): + pass + elif isinstance(child, node.RegNode): + self.registers.add(Register(child)) + + for i in self.registers: + print("\n\n") + for j in i.rtl: + print(j) + + def get_rtl(self) -> str: + return '\n'.join(self.rtl) diff --git a/srdl2sv/components/field.py b/srdl2sv/components/field.py new file mode 100644 index 0000000..11cc271 --- /dev/null +++ b/srdl2sv/components/field.py @@ -0,0 +1,183 @@ +import yaml +import math + +from systemrdl import RDLCompiler, RDLCompileError, RDLWalker, RDLListener, node +from systemrdl.node import FieldNode +from systemrdl.rdltypes import PrecedenceType, AccessType + + +TAB = " " + +class Field: + # Save YAML template as class variable + with open('srdl2sv/components/templates/fields.yaml', 'r') as file: + templ_dict = yaml.load(file, Loader=yaml.FullLoader) + + def __init__(self, obj: node.RootNode, indent_lvl: int, dimensions: int): + self.obj = obj + self.rtl = [] + self.bytes = math.ceil(obj.width / 8) + + ################################################################################## + # LIMITATION: + # v1.x of the systemrdl-compiler does not support non-homogeneous arrays. + # It is planned, however, for v2.0.0 of the compiler. More information + # can be found here: https://github.com/SystemRDL/systemrdl-compiler/issues/51 + ################################################################################## + + # Determine resets. This includes checking for async/sync resets, + # the reset value, and whether the field actually has a reset + try: + rst_signal = obj.get_property("resetsignal") + rst_name = rst_signal.inst_name + rst_async = rst_signal.get_property("async") + rst_type = "asynchronous" if rst_async else "synchronous" + + # Active low or active high? + if rst_signal.get_property("activelow"): + rst_edge = "negedge" + rst_negl = "!" + rst_active = "active_low" + else: + rst_edge = "posedge" + rst_negl = "" + rst_active = "active_high" + + print(obj.get_property('reset')) + # Value of reset? + rst_value = '\'x' if obj.get_property("reset") == None else obj.get_property('reset') + except: + rst_async = False + rst_name = None + rst_negl = None + rst_edge = None + rst_value = "'x" + rst_active = "-" + rst_type = "-" + + # Get certain properties + hw_access = obj.get_property('hw') + sw_access = obj.get_property('sw') + precedence = obj.get_property('precedence') + + # Add comment with summary on field's properties + self.rtl.append( + Field.templ_dict['field_comment'].format( + name = obj.inst_name, + hw_access = str(hw_access)[11:], + sw_access = str(sw_access)[11:], + hw_precedence = '(precedence)' if precedence == PrecedenceType.hw else '', + sw_precedence = '(precedence)' if precedence == PrecedenceType.sw else '', + rst_active = rst_active, + rst_type = rst_type, + indent = self.indent(indent_lvl))) + + # Handle always_ff + sense_list = 'sense_list_rst' if rst_async else 'sense_list_no_rst' + + self.rtl.append( + Field.templ_dict[sense_list].format( + clk_name = "clk", + rst_edge = rst_edge, + rst_name = rst_name, + indent = self.indent(indent_lvl))) + + + # Calculate how many genvars shall be added + genvars = ['[{}]'.format(chr(97+i)) for i in range(dimensions)] + genvars_str = ''.join(genvars) + + # Add actual reset line + if rst_name: + indent_lvl += 1 + + self.rtl.append( + Field.templ_dict['rst_field_assign'].format( + field_name = obj.inst_name, + rst_name = rst_name, + rst_negl = rst_negl, + rst_value = rst_value, + genvars = genvars_str, + indent = self.indent(indent_lvl))) + + self.rtl.append("{}begin".format(self.indent(indent_lvl))) + + indent_lvl += 1 + + # Define hardware access (if applicable) + hw_access_rtl = [] + + if hw_access == AccessType.rw or hw_access == AccessType.w: + if obj.get_property('we') or obj.get_property('wel'): + hw_access_rtl.append( + Field.templ_dict['hw_access_we_wel'].format( + negl = '!' if obj.get_property('wel') else '', + reg_name = obj.parent.inst_name, + field_name = obj.inst_name, + genvars = genvars_str, + indent = self.indent(indent_lvl))) + + hw_access_rtl.append( + Field.templ_dict['hw_access_field'].format( + reg_name = obj.parent.inst_name, + field_name = obj.inst_name, + genvars = genvars_str, + indent = self.indent(indent_lvl))) + + # Define software access (if applicable) + sw_access_rtl = [] + + # TODO: if sw_access_enabled + sw_access_rtl.append( + Field.templ_dict['sw_access_field'].format( + reg_name = obj.parent.inst_name, + field_name = obj.inst_name, + genvars = genvars_str, + indent = self.indent(indent_lvl))) + + indent_lvl += 1 + + # If field spans multiple bytes, every byte shall have a seperate enable! + for i in range(self.bytes): + sw_access_rtl.append( + Field.templ_dict['sw_access_byte'].format( + reg_name = obj.parent.inst_name, + field_name = obj.inst_name, + genvars = genvars_str, + i = i, + indent = self.indent(indent_lvl))) + + sw_access_rtl.append("") + + indent_lvl -= 1 + + sw_access_rtl.append("{}end".format(self.indent(indent_lvl))) + + # Check if hardware has precedence (default `precedence = sw`) + if precedence == 'PrecedenceType.sw': + self.rtl = [*self.rtl, + *sw_access_rtl, + '{}else'.format(self.indent(indent_lvl)), + *hw_access_rtl] + else: + self.rtl = [*self.rtl, + *sw_access_rtl, + '{}else'.format(self.indent(indent_lvl)), + *hw_access_rtl] + + indent_lvl -= 1 + + self.rtl.append( + Field.templ_dict['end_field_ff'].format( + reg_name = obj.parent.inst_name, + field_name = obj.inst_name, + indent = self.indent(indent_lvl))) + + + + @staticmethod + def indent(level): + return TAB*level + + def get_rtl(self) -> str: + return '\n'.join(self.rtl) diff --git a/srdl2sv/components/register.py b/srdl2sv/components/register.py new file mode 100644 index 0000000..c6e2a9e --- /dev/null +++ b/srdl2sv/components/register.py @@ -0,0 +1,81 @@ +import yaml + +from systemrdl import RDLCompiler, RDLCompileError, RDLWalker, RDLListener, node +from systemrdl.node import FieldNode + +from components.field import Field + +TAB = " " + +class Register: + # Save YAML template as class variable + with open('srdl2sv/components/templates/regs.yaml', 'r') as file: + templ_dict = yaml.load(file, Loader=yaml.FullLoader) + + def __init__(self, obj: node.RootNode): + self.obj = obj + self.name = obj.inst_name + self.rtl = [] + + if obj.is_array: + sel_arr = 'array' + array_dimensions = obj.array_dimensions + else: + sel_arr = 'single' + array_dimensions = [1] + + depth = '[{}]'.format(']['.join(f"{i}" for i in array_dimensions)) + dimensions = len(array_dimensions) + indent_lvl = 0 + + # Create comment and provide user information about register he/she + # is looking at. + self.rtl.append( + Register.templ_dict['reg_comment'].format( + name = obj.inst_name, + dimensions = dimensions, + depth = depth)) + + # Create wires every register + self.rtl.append( + Register.templ_dict['rw_wire_declare'].format( + name = obj.inst_name, + depth = depth)) + + # Create generate block for register and add comment + self.rtl.append("generate") + for i in range(dimensions): + self.rtl.append( + Register.templ_dict['generate_for_start'].format( + iterator = chr(97+i), + limit = array_dimensions[i], + indent = self.indent(i))) + + indent_lvl = i + + indent_lvl += 1 + + # Create RTL for fields + # Fields should be in order in RTL,therefore, use list + self.fields = [] + + for field in obj.fields(): + field_obj = Field(field, indent_lvl, dimensions) + self.fields.append(field_obj) + + self.rtl += field_obj.rtl + + # End loops + for i in range(dimensions-1, -1, -1): + self.rtl.append( + Register.templ_dict['generate_for_end'].format( + dimension = chr(97+i), + indent = self.indent(i))) + + + @staticmethod + def indent(level): + return TAB*level + + def get_rtl(self) -> str: + return '\n'.join(self.rtl) diff --git a/srdl2sv/components/templates/__init__.py b/srdl2sv/components/templates/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/srdl2sv/components/templates/addrmap.sv b/srdl2sv/components/templates/addrmap.sv new file mode 100644 index 0000000..6702f69 --- /dev/null +++ b/srdl2sv/components/templates/addrmap.sv @@ -0,0 +1,9 @@ +module {addrmap_name} ( + {bus_io} + {io_list} +); + + {bus_widget} + + {registers} +endmodule diff --git a/srdl2sv/components/templates/fields.yaml b/srdl2sv/components/templates/fields.yaml new file mode 100644 index 0000000..dae102f --- /dev/null +++ b/srdl2sv/components/templates/fields.yaml @@ -0,0 +1,31 @@ +--- +sense_list_rst: |- + {indent}always_ff @(posedge {clk_name} or {rst_edge} {rst_name}) +sense_list_no_rst: |- + {indent}always_ff @(posedge {clk_name}) +rst_field_assign: |- + {indent}if ({rst_negl}{rst_name}) + {indent} {field_name}_q{genvars} <= {rst_value}; + {indent}else +sw_access_field: |- + {indent}if ({reg_name}_{field_name}_sw_wr{genvars}) + {indent}begin +sw_access_byte: |- + {indent}if (byte_enable[{i}]) + {indent} {reg_name}_{field_name}_q{genvars}[8*({i}+1)-1 -: 8] <= sw_wr_bus[8*({i}+1)-1 -: 8]; +hw_access_we_wel: |- + {indent}if ({negl}{reg_name}_{field_name}_hw_wr{genvars}) +hw_access_field: |- + {indent}begin + {indent} {reg_name}_{field_name}_q{genvars} <= {reg_name}_{field_name}_in{genvars}; + {indent}end +end_field_ff: |- + {indent}end // of {reg_name}_{field_name}'s always_ff +field_comment: |- + + {indent}//-----------------FIELD SUMMARY----------------- + {indent}// name : {name} + {indent}// access : hw = {hw_access} {hw_precedence} + {indent}// sw = {sw_access} {sw_precedence} + {indent}// reset : {rst_active} / {rst_type} + {indent}//----------------------------------------------- diff --git a/srdl2sv/components/templates/regs.yaml b/srdl2sv/components/templates/regs.yaml new file mode 100644 index 0000000..adf3801 --- /dev/null +++ b/srdl2sv/components/templates/regs.yaml @@ -0,0 +1,20 @@ +--- +rw_wire_declare: | + logic {name}_wr {depth}; + logic {name}_rd {depth}; +rw_wire_assign: | + assign {name}_bus_wr[i] = addr == {} && r_vld; + assign {name}_bus_wr[i] = addr == {} && r_vld; +reg_comment: |- + /******************************************************************* + ******************************************************************* + * REGISTER : {name} + * DIMENSION : {dimensions} + * DEPTHS (per dimension): {depth} + ******************************************************************* + *******************************************************************/ +generate_for_start: |- + {indent}for ({iterator} = 0; {iterator} < {limit}; {iterator}++) + {indent}begin +generate_for_end: |- + {indent}end // of for loop with iterator {dimension} diff --git a/srdl2sv/main.py b/srdl2sv/main.py new file mode 100755 index 0000000..d476e37 --- /dev/null +++ b/srdl2sv/main.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 + +import sys +import os +import re +import time + +from systemrdl import RDLCompiler, RDLCompileError, RDLWalker, RDLListener, node +from systemrdl.node import FieldNode + +from components.addrmap import AddrMap + + +if __name__ == "__main__": + # Take start timestamp + start = time.time() + + # Compile and elaborate files provided from the command line + input_files = sys.argv[1:] + rdlc = RDLCompiler() + + try: + for input_file in input_files: + rdlc.compile_file(input_file) + + root = rdlc.elaborate() + except RDLCompileError: + sys.exit(1) + + addrmap = AddrMap(rdlc, root.top) + + print("====================================================") + print("Elapsed time: {} seconds".format(time.time() - start)) + print("====================================================") + +