From 164f196d04ad8ceba2c751d3708292a7a47fe24b Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 29 Mar 2025 18:22:18 -0300 Subject: [PATCH 01/14] Remove HdlDetect --- hdltools/hdl_detect.py | 72 ------------------------------------------ tests/test_detect.py | 44 -------------------------- 2 files changed, 116 deletions(-) delete mode 100644 hdltools/hdl_detect.py delete mode 100644 tests/test_detect.py diff --git a/hdltools/hdl_detect.py b/hdltools/hdl_detect.py deleted file mode 100644 index 6fa7819..0000000 --- a/hdltools/hdl_detect.py +++ /dev/null @@ -1,72 +0,0 @@ -# -# Copyright (C) 2025 HDLtools Project -# -# SPDX-License-Identifier: GPL-3.0-or-later -# - -""" -Module for detecting hardware description languages (HDL). - -This module provides the `HdlDetect` class, which can detect whether a given -code is written in VHDL, Verilog, or SystemVerilog. It uses specific keywords -and patterns to identify the language based on the code provided. -""" - -import argparse -import re - - -class HdlDetect: - """ - Class to detect hardware description language (HDL) from a given code. - """ - - def __init__(self, code=''): - self.lang = None - self.detect(code) - - def detect(self, code): - """Detect the HDL from the provided code.""" - self.code = code - self.lang = None - - if 'entity' in self.code and 'module' not in self.code: - self.lang = 'vhdl' - elif 'entity' not in self.code and 'module' in self.code: - self.lang = 'vlog' - keywords = r'\blogic\b|\balways_ff\b|\balways_comb\b' - if re.search(keywords, self.code): - self.lang = 'slog' - - def is_vhdl(self): - """Returns True if the code is VHDL.""" - return self.lang == 'vhdl' - - def is_vlog(self): - """Returns True if the code is Verilog.""" - return self.lang == 'vlog' - - def is_slog(self): - """Returns True if the code is SystemVerilog.""" - return self.lang == 'slog' - - def is_xlog(self): - """Returns True if the code is Verilog or SystemVerilog.""" - return self.lang in ('vlog', 'slog') - - def get_lang(self): - """Returns the detected language.""" - return self.lang - - def __str__(self): - return self.lang - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument('hdlfile') - args = parser.parse_args() - with open(args.hdlfile, 'r', encoding='utf-8') as fobj: - text = fobj.read() - obj = HdlDetect(text) - print(obj.get_lang()) diff --git a/tests/test_detect.py b/tests/test_detect.py deleted file mode 100644 index 5c75d7b..0000000 --- a/tests/test_detect.py +++ /dev/null @@ -1,44 +0,0 @@ -import pytest -from hdltools.hdl_detect import HdlDetect - - -code = { - 'vhdl': """ -library IEEE; -use IEEE.STD_LOGIC_1164.ALL; - -entity Example is - Port ( - data_i : in STD_LOGIC; - data_o : out STD_LOGIC - ); -end Example; - -architecture Behavioral of Example is -begin - data_o <= data_i; -end Behavioral; -""", - 'vlog': """ -module Example ( - input data_i, - output data_o -); - assign data_o = data_i; -endmodule -""", - 'slog': """ -module Example ( - input logic data_i, - output logic data_o -); - assign data_o = data_i; -endmodule -""" -} - - -@pytest.mark.parametrize("lang", ['vhdl', 'vlog', 'slog']) -def test_detect(lang): - obj = HdlDetect(code[lang]) - assert obj.get_lang() == lang From 56b1b63409cd42bec7c828daaf0d92c8efd59067 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 29 Mar 2025 18:23:42 -0300 Subject: [PATCH 02/14] Rewritten GenFile as HDLWriter --- hdltools/gen_file.py | 34 ---------------------------------- hdltools/hdl_writer.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 34 deletions(-) delete mode 100644 hdltools/gen_file.py create mode 100644 hdltools/hdl_writer.py diff --git a/hdltools/gen_file.py b/hdltools/gen_file.py deleted file mode 100644 index 0db5011..0000000 --- a/hdltools/gen_file.py +++ /dev/null @@ -1,34 +0,0 @@ -# -# Copyright (C) 2025 HDLtools Project -# -# SPDX-License-Identifier: GPL-3.0-or-later -# - -""" -Generates files based on templates. -""" - -from pathlib import Path -from jinja2 import Environment, FileSystemLoader - - -class GenFile: - """Generates files.""" - - def __init__(self): - tdir = Path(__file__).parent.joinpath('templates') - self.env = Environment(loader=FileSystemLoader(str(tdir))) - self.content = 'Nothing yet' - - def render(self, name, context): - """Render the specified template.""" - template = self.env.get_template(f'{name}.jinja') - self.content = template.render(context) - - def write(self, output): - """Write content into a file.""" - with open(output, 'w', encoding='utf-8') as fobj: - fobj.write(self.content) - - def __str__(self): - return self.content diff --git a/hdltools/hdl_writer.py b/hdltools/hdl_writer.py new file mode 100644 index 0000000..b6388e2 --- /dev/null +++ b/hdltools/hdl_writer.py @@ -0,0 +1,35 @@ +# +# Copyright (C) 2025 HDLtools Project +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +""" +Generates and writes the output HDL code based on templates. +""" + +from pathlib import Path +from jinja2 import Environment, FileSystemLoader + + +class HDLWriter: + """Generates and writes the output HDL code.""" + + def __init__(self): + tdir = Path(__file__).parent.joinpath('templates') + self.env = Environment(loader=FileSystemLoader(str(tdir))) + self.code = '' + + def render(self, tempname, context): + """Render the specified template.""" + template = self.env.get_template(f'{tempname}.jinja') + self.code = template.render(context) + + def write_file(self, path): + """Writes the generated HDL code to the specified file.""" + with open(path, 'w', encoding='utf-8') as fobj: + fobj.write(self.code) + + def get_code(self): + """Get the generated code.""" + return self.code From fb24d94179c721d090dbdbc35df3e294cf5ace8a Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 29 Mar 2025 18:24:11 -0300 Subject: [PATCH 03/14] Rewritten HdlSanitize as HDLReader --- hdltools/hdl_reader.py | 37 +++++++++++++++++++++++++++++++++ hdltools/hdl_sanitize.py | 45 ---------------------------------------- 2 files changed, 37 insertions(+), 45 deletions(-) create mode 100644 hdltools/hdl_reader.py delete mode 100644 hdltools/hdl_sanitize.py diff --git a/hdltools/hdl_reader.py b/hdltools/hdl_reader.py new file mode 100644 index 0000000..c9cc8e0 --- /dev/null +++ b/hdltools/hdl_reader.py @@ -0,0 +1,37 @@ +# +# Copyright (C) 2025 HDLtools Project +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +""" +Reads and sanitizes the input HDL code by removing comments, extra whitespaces +and newlines. +""" + +import re + + +class HDLReader: + """Reads and sanitizes the input HDL code.""" + + def __init__(self, code=''): + self.code = code + + def read_file(self, path): + """Reads the HDL code from file.""" + with open(path, 'r', encoding='utf-8') as fobj: + self.code = fobj.read() + + def set_code(self, code): + """Directly sets the HDL code.""" + self.code = code + + def get_code(self, is_vhdl=False): + """Retrieves the sanitized HDL code.""" + if is_vhdl: + text = re.sub(r'--[^\n]*', '', self.code) + else: + text = re.sub(r'//[^\n]*', '', self.code) + text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL) + return re.sub(r'\s+', ' ', text).strip() diff --git a/hdltools/hdl_sanitize.py b/hdltools/hdl_sanitize.py deleted file mode 100644 index 88bb4e5..0000000 --- a/hdltools/hdl_sanitize.py +++ /dev/null @@ -1,45 +0,0 @@ -# -# Copyright (C) 2025 HDLtools Project -# -# SPDX-License-Identifier: GPL-3.0-or-later -# - -""" -Sanitizes HDL source code by removing comments and extra whitespaces. -""" - -import argparse -import re - - -class HdlSanitize: - """Removes comments and extra whitespace in HDL (VHDL/Verilog) files.""" - - def __init__(self, fpath, is_vhdl=False): - try: - with open(fpath, 'r', encoding='utf-8') as fobj: - ftext = fobj.read() - except (OSError, IOError) as error: - self.code = f'Error opening {fpath}: {error}' - return - if is_vhdl: - ftext = re.sub(r'--.*', '', ftext) - else: - ftext = re.sub(r'//.*', '', ftext) - ftext = re.sub(r'/\*.*?\*/', '', ftext, flags=re.DOTALL) - self.code = re.sub(r'\s+', ' ', ftext).strip() - - def get_code(self): - """Get the sanitized HDL code.""" - return self.code - - def __str__(self): - return self.code - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument('hdlfile') - args = parser.parse_args() - code = HdlSanitize(args.hdlfile) - print(code) From 40e5a57be1bfd65f02ebb4db91e3568e28b6efc0 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 29 Mar 2025 18:24:42 -0300 Subject: [PATCH 04/14] Rename mod_parse as mod_parser, and applied small changes --- hdltools/{mod_parse.py => mod_parser.py} | 30 +++++++----------------- 1 file changed, 8 insertions(+), 22 deletions(-) rename hdltools/{mod_parse.py => mod_parser.py} (86%) diff --git a/hdltools/mod_parse.py b/hdltools/mod_parser.py similarity index 86% rename from hdltools/mod_parse.py rename to hdltools/mod_parser.py index 43d4e54..5dbd4ac 100644 --- a/hdltools/mod_parse.py +++ b/hdltools/mod_parser.py @@ -8,23 +8,20 @@ Parses SystemVerilog modules to extract information about parameters and ports. """ -import argparse -import json import re -from hdltools.hdl_sanitize import HdlSanitize - - -class ModParse: +class ModParser: """Extract information about parameters and ports from modules.""" - def __init__(self, fpath): + def __init__(self, code=''): self.modules = {} - hdl = HdlSanitize(fpath) - self.code = hdl.get_code() - self._parse() + self.code = code + + def set_code(self, code): + """Sets the HDL code.""" + self.code = code - def _parse(self): + def parse(self): pattern = ( r'module\s+' r'(\w+)\s*' # name @@ -105,14 +102,3 @@ def get_module(self, module=None): if module in self.modules: return self.modules[module] return None - - def __str__(self): - return json.dumps(self.modules, indent=2) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument('svfile') - args = parser.parse_args() - modules = ModParse(args.svfile) - print(modules) From 88a33580c84f3b7cb747e7958bd85354110647b9 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 29 Mar 2025 20:30:05 -0300 Subject: [PATCH 05/14] Add Function cli_parser --- hdltools/cli_parser.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 hdltools/cli_parser.py diff --git a/hdltools/cli_parser.py b/hdltools/cli_parser.py new file mode 100644 index 0000000..1fd53d2 --- /dev/null +++ b/hdltools/cli_parser.py @@ -0,0 +1,24 @@ +# +# Copyright (C) 2025 HDLtools Project +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +"""This file implements the command-line parser.""" + +import argparse + + +def cli_parser(app): + """Parse command-line arguments based on the selected application.""" + parser = argparse.ArgumentParser() + if app == 'comp': + parser.add_argument('--top1') + parser.add_argument('--top2') + parser.add_argument('files', nargs=2) + else: + parser.add_argument('--top') + parser.add_argument('--suffix', default=f'_{app}') + parser.add_argument('--output') + parser.add_argument('file') + return parser.parse_args() From acf2617197df169fd408e5dfcca9dd57f6eb1de3 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 29 Mar 2025 21:36:26 -0300 Subject: [PATCH 06/14] Re-implemented apps after last changes --- hdltools/apps/genstub.py | 46 ++++++++++--------- hdltools/apps/genwrap.py | 46 ++++++++++--------- hdltools/apps/modcomp.py | 84 +++++++++++++++++++++++++++-------- hdltools/hdl_writer.py | 2 +- hdltools/mod_parser.py | 1 + hdltools/templates/comp.jinja | 25 +++++++++++ 6 files changed, 143 insertions(+), 61 deletions(-) create mode 100644 hdltools/templates/comp.jinja diff --git a/hdltools/apps/genstub.py b/hdltools/apps/genstub.py index a0d561c..ba9ffc8 100644 --- a/hdltools/apps/genstub.py +++ b/hdltools/apps/genstub.py @@ -6,41 +6,45 @@ """This script generates a stub for the specified module.""" -import argparse import logging import sys -from hdltools.mod_parse import ModParse -from hdltools.gen_file import GenFile - +from hdltools.cli_parser import cli_parser +from hdltools.hdl_reader import HDLReader +from hdltools.mod_parser import ModParser +from hdltools.hdl_writer import HDLWriter logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") -parser = argparse.ArgumentParser() -parser.add_argument('--top') -parser.add_argument('--suffix', default='_stub') -parser.add_argument('--output') -parser.add_argument('file') -args = parser.parse_args() +args = cli_parser('stub') + +hdl_reader = HDLReader() +try: + hdl_reader.read_file(args.file) +except: + logging.error('file not found') + sys.exit(1) +hdl_code = hdl_reader.get_code() -modules = ModParse(args.file) -names = modules.get_names() +mod_parser = ModParser(hdl_code) +mod_parser.parse() -if not len(names) or (args.top and args.top not in names): +module_names = mod_parser.get_names() +if not len(module_names) or (args.top and args.top not in module_names): logging.error('module not found') sys.exit(1) if not args.top: - args.top = names[0] + args.top = module_names[0] -module = modules.get_module(args.top) -module['name'] = args.top -module['suffix'] = args.suffix +module_info = mod_parser.get_module(args.top) +module_info['name'] = args.top +module_info['suffix'] = args.suffix -top = GenFile() -top.render('stub', module) +hdl_writer = HDLWriter() +hdl_writer.render('stub', module_info) if not args.output: - print(top) + print(hdl_writer.get_code()) else: - top.write(args.output) + hdl_writer.write_file(args.output) diff --git a/hdltools/apps/genwrap.py b/hdltools/apps/genwrap.py index 2663893..4d41528 100644 --- a/hdltools/apps/genwrap.py +++ b/hdltools/apps/genwrap.py @@ -6,41 +6,45 @@ """This script generates a wrapper for the specified module.""" -import argparse import logging import sys -from hdltools.mod_parse import ModParse -from hdltools.gen_file import GenFile - +from hdltools.cli_parser import cli_parser +from hdltools.hdl_reader import HDLReader +from hdltools.mod_parser import ModParser +from hdltools.hdl_writer import HDLWriter logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") -parser = argparse.ArgumentParser() -parser.add_argument('--top') -parser.add_argument('--suffix', default='_wrap') -parser.add_argument('--output') -parser.add_argument('file') -args = parser.parse_args() +args = cli_parser('wrap') + +hdl_reader = HDLReader() +try: + hdl_reader.read_file(args.file) +except: + logging.error('file not found') + sys.exit(1) +hdl_code = hdl_reader.get_code() -modules = ModParse(args.file) -names = modules.get_names() +mod_parser = ModParser(hdl_code) +mod_parser.parse() -if not len(names) or (args.top and args.top not in names): +module_names = mod_parser.get_names() +if not len(module_names) or (args.top and args.top not in module_names): logging.error('module not found') sys.exit(1) if not args.top: - args.top = names[0] + args.top = module_names[0] -module = modules.get_module(args.top) -module['name'] = args.top -module['suffix'] = args.suffix +module_info = mod_parser.get_module(args.top) +module_info['name'] = args.top +module_info['suffix'] = args.suffix -top = GenFile() -top.render('wrap', module) +hdl_writer = HDLWriter() +hdl_writer.render('wrap', module_info) if not args.output: - print(top) + print(hdl_writer.get_code()) else: - top.write(args.output) + hdl_writer.write_file(args.output) diff --git a/hdltools/apps/modcomp.py b/hdltools/apps/modcomp.py index 9406a43..419c3ca 100644 --- a/hdltools/apps/modcomp.py +++ b/hdltools/apps/modcomp.py @@ -6,41 +6,89 @@ """This script compares two Verilog modules.""" -import argparse import difflib -import json import logging import sys -from hdltools.mod_parse import ModParse - +from hdltools.cli_parser import cli_parser +from hdltools.hdl_reader import HDLReader +from hdltools.mod_parser import ModParser +from hdltools.hdl_writer import HDLWriter logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") -parser = argparse.ArgumentParser() -parser.add_argument('--top1') -parser.add_argument('--top2') -parser.add_argument('files', nargs=2) -args = parser.parse_args() +args = cli_parser('comp') + +# +# Module 1 +# + +hdl_reader = HDLReader() +try: + hdl_reader.read_file(args.files[0]) +except: + logging.error('first file not found') + sys.exit(1) +hdl_code = hdl_reader.get_code() + +mod_parser = ModParser(hdl_code) +mod_parser.parse() -modules = ModParse(args.files[0]) -module1 = modules.get_module(args.top1) -if not module1: +module_names = mod_parser.get_names() +if not len(module_names) or (args.top1 and args.top1 not in module_names): logging.error('first module not found') sys.exit(1) -modules = ModParse(args.files[1]) -module2 = modules.get_module(args.top2) -if not module2: +if not args.top1: + args.top1 = module_names[0] + +module_info = mod_parser.get_module(args.top1) +module_info['name'] = args.top1 + +hdl_writer = HDLWriter() +hdl_writer.render('comp', module_info) + +module1 = hdl_writer.get_code() + +# +# Module 2 +# + +hdl_reader = HDLReader() +try: + hdl_reader.read_file(args.files[1]) +except: + logging.error('second file not found') + sys.exit(1) +hdl_code = hdl_reader.get_code() + +mod_parser = ModParser(hdl_code) +mod_parser.parse() + +module_names = mod_parser.get_names() +if not len(module_names) or (args.top2 and args.top2 not in module_names): logging.error('second module not found') sys.exit(1) +if not args.top2: + args.top2 = module_names[0] + +module_info = mod_parser.get_module(args.top2) +module_info['name'] = args.top2 + +hdl_writer = HDLWriter() +hdl_writer.render('comp', module_info) + +module2 = hdl_writer.get_code() + +# +# Diff +# + if module1 == module2: logging.info('modules are equal') else: logging.error('modules have differences') - text1 = json.dumps(module1, indent=2, sort_keys=True).splitlines() - text2 = json.dumps(module2, indent=2, sort_keys=True).splitlines() - diff = difflib.unified_diff(text1, text2, lineterm='') + diff = list(difflib.ndiff(module1.splitlines(), module2.splitlines())) print("\n".join(diff)) sys.exit(1) diff --git a/hdltools/hdl_writer.py b/hdltools/hdl_writer.py index b6388e2..57e0427 100644 --- a/hdltools/hdl_writer.py +++ b/hdltools/hdl_writer.py @@ -31,5 +31,5 @@ def write_file(self, path): fobj.write(self.code) def get_code(self): - """Get the generated code.""" + """Returns the generated code.""" return self.code diff --git a/hdltools/mod_parser.py b/hdltools/mod_parser.py index 5dbd4ac..5c39c73 100644 --- a/hdltools/mod_parser.py +++ b/hdltools/mod_parser.py @@ -22,6 +22,7 @@ def set_code(self, code): self.code = code def parse(self): + """Parse the code.""" pattern = ( r'module\s+' r'(\w+)\s*' # name diff --git a/hdltools/templates/comp.jinja b/hdltools/templates/comp.jinja new file mode 100644 index 0000000..e28fc45 --- /dev/null +++ b/hdltools/templates/comp.jinja @@ -0,0 +1,25 @@ +{%- set all_ports = [] %} +{%- set port_names = [] %} +{%- for direction, signals in ports.items() %} + {%- for name, props in signals.items() %} + {%- set dir = direction.rstrip('s') %} + {%- set type = props.type | default('') %} + {%- set packed = props.packed | default('') %} + {%- set unpacked = props.unpacked | default('') %} + {%- set port_str = dir ~ " " ~ type ~ " " ~ packed ~ " " ~ name ~ " " ~ unpacked %} + {%- set _ = all_ports.append(port_str) %} + {%- set _ = port_names.append(name) %} + {%- endfor %} +{%- endfor -%} +module {{ name }} {% if params -%}#( + {%- for name, props in params.items() -%} + {%- set type = props.type | default('') %} + {%- set packed = props.packed | default('') %} + parameter {{ type }} {{ packed }} {{ name }} = {{ props.value }}{% if not loop.last %},{% endif %} + {%- endfor %} +){% endif %}( +{%- for port in all_ports %} + {{ port }}{% if not loop.last %},{% endif %} +{%- endfor %} +); +endmodule From 192f1e6a5b1c5677b5f009932eac41c88695c63e Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 4 Apr 2025 00:02:34 -0300 Subject: [PATCH 07/14] Fix pylint complaints --- hdltools/apps/genstub.py | 4 ++-- hdltools/apps/genwrap.py | 4 ++-- hdltools/apps/modcomp.py | 8 ++++---- hdltools/mod_parser.py | 1 + 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/hdltools/apps/genstub.py b/hdltools/apps/genstub.py index ba9ffc8..54e66cf 100644 --- a/hdltools/apps/genstub.py +++ b/hdltools/apps/genstub.py @@ -21,8 +21,8 @@ hdl_reader = HDLReader() try: hdl_reader.read_file(args.file) -except: - logging.error('file not found') +except (OSError, UnicodeDecodeError) as e: + logging.error(f'{e}') sys.exit(1) hdl_code = hdl_reader.get_code() diff --git a/hdltools/apps/genwrap.py b/hdltools/apps/genwrap.py index 4d41528..275ee3b 100644 --- a/hdltools/apps/genwrap.py +++ b/hdltools/apps/genwrap.py @@ -21,8 +21,8 @@ hdl_reader = HDLReader() try: hdl_reader.read_file(args.file) -except: - logging.error('file not found') +except (OSError, UnicodeDecodeError) as e: + logging.error(f'{e}') sys.exit(1) hdl_code = hdl_reader.get_code() diff --git a/hdltools/apps/modcomp.py b/hdltools/apps/modcomp.py index 419c3ca..d13b724 100644 --- a/hdltools/apps/modcomp.py +++ b/hdltools/apps/modcomp.py @@ -26,8 +26,8 @@ hdl_reader = HDLReader() try: hdl_reader.read_file(args.files[0]) -except: - logging.error('first file not found') +except (OSError, UnicodeDecodeError) as e: + logging.error(f'{e}') sys.exit(1) hdl_code = hdl_reader.get_code() @@ -57,8 +57,8 @@ hdl_reader = HDLReader() try: hdl_reader.read_file(args.files[1]) -except: - logging.error('second file not found') +except (OSError, UnicodeDecodeError) as e: + logging.error(f'{e}') sys.exit(1) hdl_code = hdl_reader.get_code() diff --git a/hdltools/mod_parser.py b/hdltools/mod_parser.py index 5c39c73..431a08c 100644 --- a/hdltools/mod_parser.py +++ b/hdltools/mod_parser.py @@ -10,6 +10,7 @@ import re + class ModParser: """Extract information about parameters and ports from modules.""" From 1d9fbccfe3071ec140d7db413d182a1f1e4d9906 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 4 Apr 2025 00:12:26 -0300 Subject: [PATCH 08/14] Add method to detect if code is VHDL or not --- hdltools/hdl_reader.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/hdltools/hdl_reader.py b/hdltools/hdl_reader.py index c9cc8e0..700549e 100644 --- a/hdltools/hdl_reader.py +++ b/hdltools/hdl_reader.py @@ -27,9 +27,13 @@ def set_code(self, code): """Directly sets the HDL code.""" self.code = code - def get_code(self, is_vhdl=False): + def is_vhdl(self): + """Return True if the code seems to be VHDL.""" + return 'endmodule' not in self.code.lower() + + def get_code(self): """Retrieves the sanitized HDL code.""" - if is_vhdl: + if self.is_vhdl(): text = re.sub(r'--[^\n]*', '', self.code) else: text = re.sub(r'//[^\n]*', '', self.code) From 02b3a759e0c3ee2b95ceea6633bd6394ea790da2 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 5 Apr 2025 23:54:45 -0300 Subject: [PATCH 09/14] Add Class HDLController --- hdltools/hdl_controller.py | 63 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 hdltools/hdl_controller.py diff --git a/hdltools/hdl_controller.py b/hdltools/hdl_controller.py new file mode 100644 index 0000000..ec02dcf --- /dev/null +++ b/hdltools/hdl_controller.py @@ -0,0 +1,63 @@ +# +# Copyright (C) 2025 HDLtools Project +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +""" +This module defines the HDLController class that orchestrates the entire flow +from reading and sanitizing HDL code, parsing modules, and generating output +HDL code using Jinja2 templates. +""" + +import sys + +from hdltools.hdl_reader import HDLReader +from hdltools.mod_parser import ModParser +from hdltools.hdl_writer import HDLWriter + + +class HDLController: + """A central controller to orchestrate the HDL processing tasks.""" + + def __init__(self, template): + self.reader = HDLReader() + self.parser = ModParser() + self.writer = HDLWriter() + self.template = template + + def generate(self, filepath, top=None, suffix=None): + """Generates HDL code from the input file.""" + + try: + self.reader.read_file(filepath) + except (OSError, UnicodeDecodeError): + self._error(f'file not found ({filepath})') + + raw_code = self.reader.get_code() + self.parser.set_code(raw_code) + self.parser.parse() + + modules = self.parser.get_names() + if not modules: + self._error('module not found') + if top and top not in modules: + self._error(f'top not found ({top})') + if not top: + top = modules[0] + + context = self.parser.get_module(top) + context['name'] = top + context['suffix'] = suffix + self.writer.render(self.template, context) + + return self.writer.get_code() + + def write(self, filepath): + """Writes the generated HDL code to the specified file.""" + self.writer.write_file(filepath) + + @staticmethod + def _error(message, ecode=1): + print(f'ERROR: {message}') + sys.exit(ecode) From e8d27718c1b1e2cd4d2a5a9da644106a1c3f1c4b Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 5 Apr 2025 23:56:29 -0300 Subject: [PATCH 10/14] Fix the wrong accumulation of parsed results --- hdltools/mod_parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/hdltools/mod_parser.py b/hdltools/mod_parser.py index 431a08c..f9f984d 100644 --- a/hdltools/mod_parser.py +++ b/hdltools/mod_parser.py @@ -24,6 +24,7 @@ def set_code(self, code): def parse(self): """Parse the code.""" + self.modules = {} pattern = ( r'module\s+' r'(\w+)\s*' # name From cbe3152ee5921306fa88ad4386256e743b09dd76 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 5 Apr 2025 23:57:03 -0300 Subject: [PATCH 11/14] apps: simplified employing HDLController --- hdltools/apps/genstub.py | 43 +++----------------- hdltools/apps/genwrap.py | 41 +++---------------- hdltools/apps/modcomp.py | 85 +++++----------------------------------- 3 files changed, 20 insertions(+), 149 deletions(-) diff --git a/hdltools/apps/genstub.py b/hdltools/apps/genstub.py index 54e66cf..ee389e1 100644 --- a/hdltools/apps/genstub.py +++ b/hdltools/apps/genstub.py @@ -6,45 +6,14 @@ """This script generates a stub for the specified module.""" -import logging -import sys - from hdltools.cli_parser import cli_parser -from hdltools.hdl_reader import HDLReader -from hdltools.mod_parser import ModParser -from hdltools.hdl_writer import HDLWriter - -logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") - -args = cli_parser('stub') - -hdl_reader = HDLReader() -try: - hdl_reader.read_file(args.file) -except (OSError, UnicodeDecodeError) as e: - logging.error(f'{e}') - sys.exit(1) -hdl_code = hdl_reader.get_code() - -mod_parser = ModParser(hdl_code) -mod_parser.parse() - -module_names = mod_parser.get_names() -if not len(module_names) or (args.top and args.top not in module_names): - logging.error('module not found') - sys.exit(1) - -if not args.top: - args.top = module_names[0] - -module_info = mod_parser.get_module(args.top) -module_info['name'] = args.top -module_info['suffix'] = args.suffix +from hdltools.hdl_controller import HDLController -hdl_writer = HDLWriter() -hdl_writer.render('stub', module_info) +args = cli_parser('wrap') +ctrl = HDLController('wrap') +code = ctrl.generate(args.file, args.top, args.suffix) if not args.output: - print(hdl_writer.get_code()) + print(code) else: - hdl_writer.write_file(args.output) + ctrl.write(args.output) diff --git a/hdltools/apps/genwrap.py b/hdltools/apps/genwrap.py index 275ee3b..2a08673 100644 --- a/hdltools/apps/genwrap.py +++ b/hdltools/apps/genwrap.py @@ -6,45 +6,14 @@ """This script generates a wrapper for the specified module.""" -import logging -import sys - from hdltools.cli_parser import cli_parser -from hdltools.hdl_reader import HDLReader -from hdltools.mod_parser import ModParser -from hdltools.hdl_writer import HDLWriter - -logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") +from hdltools.hdl_controller import HDLController args = cli_parser('wrap') - -hdl_reader = HDLReader() -try: - hdl_reader.read_file(args.file) -except (OSError, UnicodeDecodeError) as e: - logging.error(f'{e}') - sys.exit(1) -hdl_code = hdl_reader.get_code() - -mod_parser = ModParser(hdl_code) -mod_parser.parse() - -module_names = mod_parser.get_names() -if not len(module_names) or (args.top and args.top not in module_names): - logging.error('module not found') - sys.exit(1) - -if not args.top: - args.top = module_names[0] - -module_info = mod_parser.get_module(args.top) -module_info['name'] = args.top -module_info['suffix'] = args.suffix - -hdl_writer = HDLWriter() -hdl_writer.render('wrap', module_info) +ctrl = HDLController('wrap') +code = ctrl.generate(args.file, args.top, args.suffix) if not args.output: - print(hdl_writer.get_code()) + print(code) else: - hdl_writer.write_file(args.output) + ctrl.write(args.output) diff --git a/hdltools/apps/modcomp.py b/hdltools/apps/modcomp.py index d13b724..9035060 100644 --- a/hdltools/apps/modcomp.py +++ b/hdltools/apps/modcomp.py @@ -7,88 +7,21 @@ """This script compares two Verilog modules.""" import difflib -import logging import sys from hdltools.cli_parser import cli_parser -from hdltools.hdl_reader import HDLReader -from hdltools.mod_parser import ModParser -from hdltools.hdl_writer import HDLWriter +from hdltools.hdl_controller import HDLController -logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") args = cli_parser('comp') +ctrl = HDLController('comp') +mod1 = ctrl.generate(args.files[0], args.top1) +mod2 = ctrl.generate(args.files[1], args.top2) -# -# Module 1 -# - -hdl_reader = HDLReader() -try: - hdl_reader.read_file(args.files[0]) -except (OSError, UnicodeDecodeError) as e: - logging.error(f'{e}') - sys.exit(1) -hdl_code = hdl_reader.get_code() - -mod_parser = ModParser(hdl_code) -mod_parser.parse() - -module_names = mod_parser.get_names() -if not len(module_names) or (args.top1 and args.top1 not in module_names): - logging.error('first module not found') - sys.exit(1) - -if not args.top1: - args.top1 = module_names[0] - -module_info = mod_parser.get_module(args.top1) -module_info['name'] = args.top1 - -hdl_writer = HDLWriter() -hdl_writer.render('comp', module_info) - -module1 = hdl_writer.get_code() - -# -# Module 2 -# - -hdl_reader = HDLReader() -try: - hdl_reader.read_file(args.files[1]) -except (OSError, UnicodeDecodeError) as e: - logging.error(f'{e}') - sys.exit(1) -hdl_code = hdl_reader.get_code() - -mod_parser = ModParser(hdl_code) -mod_parser.parse() - -module_names = mod_parser.get_names() -if not len(module_names) or (args.top2 and args.top2 not in module_names): - logging.error('second module not found') - sys.exit(1) - -if not args.top2: - args.top2 = module_names[0] - -module_info = mod_parser.get_module(args.top2) -module_info['name'] = args.top2 - -hdl_writer = HDLWriter() -hdl_writer.render('comp', module_info) - -module2 = hdl_writer.get_code() - -# -# Diff -# - -if module1 == module2: - logging.info('modules are equal') +if mod1 == mod2: + print('INFO: modules are equal') else: - logging.error('modules have differences') - diff = list(difflib.ndiff(module1.splitlines(), module2.splitlines())) - print("\n".join(diff)) + print('ERROR: modules are different\n') + diff = list(difflib.ndiff(mod1.splitlines(), mod2.splitlines())) + print('\n'.join(diff)) sys.exit(1) From 61c2e5c0c0da2e8865897aea82dae8313155d3d0 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 6 Apr 2025 09:24:42 -0300 Subject: [PATCH 12/14] Move logic from genwrap and genstub to hdl_controller --- hdltools/apps/genstub.py | 11 ++++------- hdltools/apps/genwrap.py | 9 +++------ hdltools/hdl_controller.py | 7 +++++-- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/hdltools/apps/genstub.py b/hdltools/apps/genstub.py index ee389e1..b3df125 100644 --- a/hdltools/apps/genstub.py +++ b/hdltools/apps/genstub.py @@ -9,11 +9,8 @@ from hdltools.cli_parser import cli_parser from hdltools.hdl_controller import HDLController -args = cli_parser('wrap') -ctrl = HDLController('wrap') -code = ctrl.generate(args.file, args.top, args.suffix) -if not args.output: - print(code) -else: - ctrl.write(args.output) +args = cli_parser('stub') +ctrl = HDLController('stub') +ctrl.generate(args.file, args.top, args.suffix) +ctrl.write(args.output) diff --git a/hdltools/apps/genwrap.py b/hdltools/apps/genwrap.py index 2a08673..a586e52 100644 --- a/hdltools/apps/genwrap.py +++ b/hdltools/apps/genwrap.py @@ -9,11 +9,8 @@ from hdltools.cli_parser import cli_parser from hdltools.hdl_controller import HDLController + args = cli_parser('wrap') ctrl = HDLController('wrap') -code = ctrl.generate(args.file, args.top, args.suffix) - -if not args.output: - print(code) -else: - ctrl.write(args.output) +ctrl.generate(args.file, args.top, args.suffix) +ctrl.write(args.output) diff --git a/hdltools/hdl_controller.py b/hdltools/hdl_controller.py index ec02dcf..c7099b5 100644 --- a/hdltools/hdl_controller.py +++ b/hdltools/hdl_controller.py @@ -54,8 +54,11 @@ def generate(self, filepath, top=None, suffix=None): return self.writer.get_code() def write(self, filepath): - """Writes the generated HDL code to the specified file.""" - self.writer.write_file(filepath) + """Writes the generated HDL code.""" + if not filepath: + print(self.writer.get_code()) + else: + self.writer.write_file(filepath) @staticmethod def _error(message, ecode=1): From cdec0cd735ec6e97161840253231c603fc0743ff Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 6 Apr 2025 22:11:27 -0300 Subject: [PATCH 13/14] Tests renamed and updated --- tests/{test_parse.py => test_parser.py} | 22 ++++++++++++++++------ tests/{test_sanitize.py => test_reader.py} | 10 ++++++---- 2 files changed, 22 insertions(+), 10 deletions(-) rename tests/{test_parse.py => test_parser.py} (83%) rename tests/{test_sanitize.py => test_reader.py} (59%) diff --git a/tests/test_parse.py b/tests/test_parser.py similarity index 83% rename from tests/test_parse.py rename to tests/test_parser.py index 4c0cbcd..9e1cc6e 100644 --- a/tests/test_parse.py +++ b/tests/test_parser.py @@ -1,24 +1,33 @@ from pathlib import Path -from hdltools.mod_parse import ModParse +from hdltools.hdl_reader import HDLReader +from hdltools.mod_parser import ModParser -vfile = Path(__file__).parent.resolve() / 'hdl/modules.sv' + +def get_vlog(): + filepath = Path(__file__).parent.resolve() / 'hdl' / 'modules.sv' + reader = HDLReader() + reader.read_file(filepath) + return reader.get_code() def test_modules(): - vobj = ModParse(vfile) + vobj = ModParser(get_vlog()) + vobj.parse() modules = vobj.get_modules() assert len(modules) == 3 def test_empty(): - vobj = ModParse(vfile) + vobj = ModParser(get_vlog()) + vobj.parse() module = vobj.get_module('mod_empty') assert module == {} def test_params(): - vobj = ModParse(vfile) + vobj = ModParser(get_vlog()) + vobj.parse() module = vobj.get_module('mod_param') assert 'params' in module params = {} @@ -34,7 +43,8 @@ def test_params(): def test_ports(): - vobj = ModParse(vfile) + vobj = ModParser(get_vlog()) + vobj.parse() module = vobj.get_module('mod_param') assert 'ports' in module ports = { diff --git a/tests/test_sanitize.py b/tests/test_reader.py similarity index 59% rename from tests/test_sanitize.py rename to tests/test_reader.py index 2942951..5fbd255 100644 --- a/tests/test_sanitize.py +++ b/tests/test_reader.py @@ -1,19 +1,21 @@ from pathlib import Path -from hdltools.hdl_sanitize import HdlSanitize +from hdltools.hdl_reader import HDLReader -vfile = Path(__file__).parent.resolve() / 'hdl/modules.sv' +vfile = Path(__file__).parent.resolve() / 'hdl' / 'modules.sv' def test_comments(): - vobj = HdlSanitize(vfile) + vobj = HDLReader() + vobj.read_file(vfile) vcode = vobj.get_code() comment_patterns = ['//', '/*', '*/', '--'] assert not any(pattern in vcode for pattern in comment_patterns) def test_spaces(): - vobj = HdlSanitize(vfile) + vobj = HDLReader() + vobj.read_file(vfile) vcode = vobj.get_code() assert '\n' not in vcode assert ' ' not in vcode From e0e4a4e8d643d0847272269d7d3ab190d5fb0b97 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 6 Apr 2025 22:41:03 -0300 Subject: [PATCH 14/14] Simplified tests using fixtures --- tests/test_parser.py | 26 +++++++++++--------------- tests/test_reader.py | 19 ++++++++++--------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/tests/test_parser.py b/tests/test_parser.py index 9e1cc6e..f4dad66 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,33 +1,31 @@ -from pathlib import Path +import pytest +from pathlib import Path from hdltools.hdl_reader import HDLReader from hdltools.mod_parser import ModParser -def get_vlog(): +@pytest.fixture +def vobj(): filepath = Path(__file__).parent.resolve() / 'hdl' / 'modules.sv' reader = HDLReader() reader.read_file(filepath) - return reader.get_code() + parser = ModParser(reader.get_code()) + parser.parse() + return parser -def test_modules(): - vobj = ModParser(get_vlog()) - vobj.parse() +def test_modules(vobj): modules = vobj.get_modules() assert len(modules) == 3 -def test_empty(): - vobj = ModParser(get_vlog()) - vobj.parse() +def test_empty(vobj): module = vobj.get_module('mod_empty') assert module == {} -def test_params(): - vobj = ModParser(get_vlog()) - vobj.parse() +def test_params(vobj): module = vobj.get_module('mod_param') assert 'params' in module params = {} @@ -42,9 +40,7 @@ def test_params(): assert module["params"] == params -def test_ports(): - vobj = ModParser(get_vlog()) - vobj.parse() +def test_ports(vobj): module = vobj.get_module('mod_param') assert 'ports' in module ports = { diff --git a/tests/test_reader.py b/tests/test_reader.py index 5fbd255..299bf0d 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -1,21 +1,22 @@ -from pathlib import Path +import pytest +from pathlib import Path from hdltools.hdl_reader import HDLReader -vfile = Path(__file__).parent.resolve() / 'hdl' / 'modules.sv' - -def test_comments(): +@pytest.fixture +def vcode(): + vfile = Path(__file__).parent.resolve() / 'hdl' / 'modules.sv' vobj = HDLReader() vobj.read_file(vfile) - vcode = vobj.get_code() + return vobj.get_code() + + +def test_comments(vcode): comment_patterns = ['//', '/*', '*/', '--'] assert not any(pattern in vcode for pattern in comment_patterns) -def test_spaces(): - vobj = HDLReader() - vobj.read_file(vfile) - vcode = vobj.get_code() +def test_spaces(vcode): assert '\n' not in vcode assert ' ' not in vcode