From e75d5240187f3a64c26dfd4200bd4f176f56be56 Mon Sep 17 00:00:00 2001 From: John Wright Date: Thu, 13 Jun 2019 14:07:17 -0700 Subject: [PATCH 1/7] WIP --- src/hammer-vlsi/hammer_utils/spice_utils.py | 81 +++++++++++++++++++ src/hammer-vlsi/hammer_utils/verilog_utils.py | 7 +- 2 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 src/hammer-vlsi/hammer_utils/spice_utils.py diff --git a/src/hammer-vlsi/hammer_utils/spice_utils.py b/src/hammer-vlsi/hammer_utils/spice_utils.py new file mode 100644 index 000000000..e665a2b6e --- /dev/null +++ b/src/hammer-vlsi/hammer_utils/spice_utils.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# spice_utils.py +# Misc Verilog utilities +# +# See LICENSE for licence details. + +import re +from typing import Enum, Tuple, Optional, List + +# Some convenience types +class SpiceModule(str): + pass + +class SpiceWithoutMultilines(str): + pass + +ModuleTree = Dict[SpiceModule, List[SpiceModule]] + +class SpiceUtils: + +# # A convenience type for a data structure that is a mapping of a SPICE file name to +# # a list of top cells or None (for an auto-detect mode) +# SourceTopTuple = Tuple[str, Optional[List[str]]] +# +# @staticmethod +# def _str_to_source_top_tuple(source: Union[str, SourceTopTuple]) -> SourceTopTuple: +# if isinstance(source, str): +# return (str, None) +# else: +# return source + + @staticmethod + def uniquify_spice(sources: List[str]): + """ + Uniquify the provided SPICE sources. TODO finish doc + + :param sources: + """ + #sources_tuple = [SpiceUtils._str_to_source_top_tuple(x) for x in sources] + + sources_no_multiline = + + + @staticmethod + def module_tree(s: SpiceWithoutMultilines) -> ModuleTree: + """ + Parse a SPICE file and return a dictionary that contains all found modules pointing to lists of their submodules. + The SPICE file must not contain multiline statements. + + :param s: A SPICE file without any multiline statements + :return: A dictionary whose keys are all found modules and whose values are the list of submodules + """ + pass + + @staticmethod + def reverse_module_tree(m: ModuleTree) -> ModuleTree: + """ + Reverse a module tree dictionary so that the values are now the list of direct parent modules instead of submodules. + + :param m: A dictionary whose keys are SpiceModule strings and whose values are a list of submodules + :return: A dictionary with the same keys whose values are a list of the modules' direct parents + """ + pass + + @staticmethod + def remove_multilines(s: str) -> SpiceWithoutMultilines: + """ + Remove all multiline statements from the given SPICE source. + + :param s: The SPICE source + :return: SPICE source without multiline statements + """ + # Multilines in spice start with a + character + multiline_pattern = r"(\r?\n\+\s*)" + + return SpiceWithoutMultilines(re.sub(multiline_pattern, " ", s, flags=re.DOTALL)) + + + diff --git a/src/hammer-vlsi/hammer_utils/verilog_utils.py b/src/hammer-vlsi/hammer_utils/verilog_utils.py index 2167a23c5..17c6444a6 100644 --- a/src/hammer-vlsi/hammer_utils/verilog_utils.py +++ b/src/hammer-vlsi/hammer_utils/verilog_utils.py @@ -8,14 +8,11 @@ import re -__all__ = ['VerilogUtils'] - - class VerilogUtils: @staticmethod def remove_comments(v: str) -> str: """ - Remove comments from the given Verilog file. + Remove comments from the given Verilog source. :param v: Verilog source code :return: Source code without comments @@ -46,7 +43,7 @@ def contains_module(v: str, module: str) -> bool: @staticmethod def remove_module(v: str, module: str) -> str: """ - Remove the given module from the given Verilog source file, if it exists. + Remove the given module from the given Verilog source, if it exists. :param v: Verilog source code :param module: Module to remove From d15a3e1dabbdd2e4e2acb80d43d6c0cb9d337a5f Mon Sep 17 00:00:00 2001 From: John Wright Date: Thu, 13 Jun 2019 23:17:12 -0700 Subject: [PATCH 2/7] passes type checker --- src/hammer-vlsi/hammer_utils/spice_utils.py | 123 ++++++++++++++------ 1 file changed, 89 insertions(+), 34 deletions(-) diff --git a/src/hammer-vlsi/hammer_utils/spice_utils.py b/src/hammer-vlsi/hammer_utils/spice_utils.py index e665a2b6e..59decf6d6 100644 --- a/src/hammer-vlsi/hammer_utils/spice_utils.py +++ b/src/hammer-vlsi/hammer_utils/spice_utils.py @@ -7,44 +7,74 @@ # See LICENSE for licence details. import re -from typing import Enum, Tuple, Optional, List - -# Some convenience types -class SpiceModule(str): - pass +from typing import Tuple, Optional, List, Set, Dict +from enum import Enum +# A convenience class to enforce that we run the multiline removal before other steps class SpiceWithoutMultilines(str): pass -ModuleTree = Dict[SpiceModule, List[SpiceModule]] - class SpiceUtils: -# # A convenience type for a data structure that is a mapping of a SPICE file name to -# # a list of top cells or None (for an auto-detect mode) -# SourceTopTuple = Tuple[str, Optional[List[str]]] -# -# @staticmethod -# def _str_to_source_top_tuple(source: Union[str, SourceTopTuple]) -> SourceTopTuple: -# if isinstance(source, str): -# return (str, None) -# else: -# return source + # Multilines in spice start with a + character + multiline_pattern = re.compile("(\r?\n\+\s*)", flags=re.DOTALL) + module_def_pattern = re.compile("^(\.subckt\s+)(?P[^\s]+)(\s+.*)$", flags=re.IGNORECASE|re.MULTILINE) + module_inst_pattern = re.compile("^(x.*\s+)(?P[^\s]+)(\s*)$", flags=re.IGNORECASE|re.MULTILINE) + end_module_pattern = re.compile("^\.ends.*$", flags=re.IGNORECASE|re.MULTILINE) + tokens = [ + ('SUBCKT', SpiceUtils.module_def_pattern), + ('ENDS', SpiceUtils.end_module_pattern), + ('INST', SpiceUtils.module_inst_pattern) + ] + token_regex = re.compile('|'.join('(?P<{}>{})'.format(t[0], t[1]) for t in SpiceUtils.tokens)) @staticmethod - def uniquify_spice(sources: List[str]): + def uniquify_spice(sources: List[str]) -> List[SpiceWithoutMultilines]: """ - Uniquify the provided SPICE sources. TODO finish doc + Uniquify the provided SPICE sources. If a module name exists in multiple files, each duplicate module will be + renamed. If the original name of a duplicated module is used in a file where it is not defined, an + exception is thrown as it is impossible to know which renamed module to use. For example: - :param sources: - """ - #sources_tuple = [SpiceUtils._str_to_source_top_tuple(x) for x in sources] + - file1.sp defines B and A, which instantiates B + - file2.sp defines B and C, which instantiates B - sources_no_multiline = + B would be renamed to B_0 in file1.sp + B would be renamed to B_1 in file2.sp + + However, if file3.sp defines D, which instantiates B, an exception would be raised because we don't know if we + should chose B_0 or B_1. + + :param sources: A list of the contents of SPICE source files (not the filenames) + :return: A list of SPICE sources with unique modules + """ + sources_no_multiline = [SpiceUtils.remove_multilines(s) for s in sources] + module_trees = [SpiceUtils.parse_module_tree(s) for s in sources_no_multiline] + found_modules = set() # type: Set[str] + duplicates = set() # type: Set[str] + for tree in module_trees: + for module in tree: + if module in found_modules: + duplicates.add(module) + found_modules.add(module) + + def replace_source(source: SpiceWithoutMultilines) -> SpiceWithoutMultilines: + replacements = {} # type: Dict[str, str] + for old in duplicates: + i = 0 + new = old + while new in found_modules: + new = "{d}_{i}".format(d=old, i=i) + i = i + 1 + found_modules.add(new) + replacements[old] = new + + return SpiceUtils.replace_modules(source, replacements) + + return [replace_source(s) for s in sources_no_multiline] @staticmethod - def module_tree(s: SpiceWithoutMultilines) -> ModuleTree: + def parse_module_tree(s: SpiceWithoutMultilines) -> Dict[str, Set[str]]: """ Parse a SPICE file and return a dictionary that contains all found modules pointing to lists of their submodules. The SPICE file must not contain multiline statements. @@ -52,17 +82,45 @@ def module_tree(s: SpiceWithoutMultilines) -> ModuleTree: :param s: A SPICE file without any multiline statements :return: A dictionary whose keys are all found modules and whose values are the list of submodules """ - pass + in_module = False + module_name = "" + tree = {} # type: Dict[str, Set[str]] + for m in SpiceUtils.token_regex.finditer(s): + kind = m.lastgroup + if kind == 'SUBCKT': + module_name = m.group("name") + in_module = True + if module_name in tree: + raise ValueError("Multiple SPICE subckt definitions for \"{}\" in the same file".format(module_name)) + tree[module_name] = set() + elif kind == 'ENDS': + in_module = False + elif kind == 'INST': + if not in_module: + raise ValueError("Malformed SPICE source while parsing: \"{}\"".format(m.group())) + tree[module_name].add(m.group("name")) + else: + assert False, "Should not get here" + + return tree @staticmethod - def reverse_module_tree(m: ModuleTree) -> ModuleTree: + def replace_modules(source: SpiceWithoutMultilines, mapping: Dict[str, str]) -> SpiceWithoutMultilines: """ - Reverse a module tree dictionary so that the values are now the list of direct parent modules instead of submodules. + Replace module names in a provided SPICE file by the provided mapping. - :param m: A dictionary whose keys are SpiceModule strings and whose values are a list of submodules - :return: A dictionary with the same keys whose values are a list of the modules' direct parents + :param source: The input SPICE source with no multilines + :param mapping: A dictionary of old module names mapped to new module names + :return: SPICE source with the module names replaced """ - pass + # Not giving m a type because its type is different in different python3 versions :( + def repl_fn(m) -> str: + if m.group(2) in mapping: + return m.group(1) + mapping[m.group(2)] + m.group(3) + else: + return m.group(0) + + return SpiceWithoutMultilines(SpiceUtils.module_inst_pattern.sub(repl_fn, SpiceUtils.module_def_pattern.sub(repl_fn, source))) @staticmethod def remove_multilines(s: str) -> SpiceWithoutMultilines: @@ -72,10 +130,7 @@ def remove_multilines(s: str) -> SpiceWithoutMultilines: :param s: The SPICE source :return: SPICE source without multiline statements """ - # Multilines in spice start with a + character - multiline_pattern = r"(\r?\n\+\s*)" - - return SpiceWithoutMultilines(re.sub(multiline_pattern, " ", s, flags=re.DOTALL)) + return SpiceWithoutMultilines(SpiceUtils.multiline_pattern.sub(" ", s)) From e6eddba83f0599ce8ad596bcafd3ba23638e6c1f Mon Sep 17 00:00:00 2001 From: John Wright Date: Fri, 14 Jun 2019 00:46:02 -0700 Subject: [PATCH 3/7] Add tests, pass --- src/hammer-vlsi/hammer_utils/__init__.py | 1 + src/hammer-vlsi/hammer_utils/spice_utils.py | 46 ++-- src/hammer-vlsi/spice_utils_test.py | 228 ++++++++++++++++++++ src/test/unittests.sh | 1 + 4 files changed, 255 insertions(+), 21 deletions(-) create mode 100644 src/hammer-vlsi/spice_utils_test.py diff --git a/src/hammer-vlsi/hammer_utils/__init__.py b/src/hammer-vlsi/hammer_utils/__init__.py index 7835e15dc..241b965e7 100644 --- a/src/hammer-vlsi/hammer_utils/__init__.py +++ b/src/hammer-vlsi/hammer_utils/__init__.py @@ -15,6 +15,7 @@ from decimal import Decimal from .verilog_utils import * +from .spice_utils import * from .lef_utils import * diff --git a/src/hammer-vlsi/hammer_utils/spice_utils.py b/src/hammer-vlsi/hammer_utils/spice_utils.py index 59decf6d6..3f7c66a9c 100644 --- a/src/hammer-vlsi/hammer_utils/spice_utils.py +++ b/src/hammer-vlsi/hammer_utils/spice_utils.py @@ -10,26 +10,22 @@ from typing import Tuple, Optional, List, Set, Dict from enum import Enum -# A convenience class to enforce that we run the multiline removal before other steps -class SpiceWithoutMultilines(str): - pass - class SpiceUtils: # Multilines in spice start with a + character - multiline_pattern = re.compile("(\r?\n\+\s*)", flags=re.DOTALL) - module_def_pattern = re.compile("^(\.subckt\s+)(?P[^\s]+)(\s+.*)$", flags=re.IGNORECASE|re.MULTILINE) - module_inst_pattern = re.compile("^(x.*\s+)(?P[^\s]+)(\s*)$", flags=re.IGNORECASE|re.MULTILINE) + multiline_pattern = re.compile("(?:\s*\n\+)+\s*", flags=re.DOTALL) + module_def_pattern = re.compile("^(\.subckt\s+)(?P[^\s]+)(\s+.*)$", flags=re.IGNORECASE|re.MULTILINE) + module_inst_pattern = re.compile("^(x.*?\s)(?P[^\s]+)(\s*)$", flags=re.IGNORECASE|re.MULTILINE) end_module_pattern = re.compile("^\.ends.*$", flags=re.IGNORECASE|re.MULTILINE) tokens = [ - ('SUBCKT', SpiceUtils.module_def_pattern), - ('ENDS', SpiceUtils.end_module_pattern), - ('INST', SpiceUtils.module_inst_pattern) + ('SUBCKT', module_def_pattern.pattern), + ('ENDS', end_module_pattern.pattern), + ('INST', module_inst_pattern.pattern) ] - token_regex = re.compile('|'.join('(?P<{}>{})'.format(t[0], t[1]) for t in SpiceUtils.tokens)) + token_regex = re.compile('|'.join('(?P<%s>%s)' % t for t in tokens), flags=re.IGNORECASE|re.MULTILINE) @staticmethod - def uniquify_spice(sources: List[str]) -> List[SpiceWithoutMultilines]: + def uniquify_spice(sources: List[str]) -> List[str]: """ Uniquify the provided SPICE sources. If a module name exists in multiple files, each duplicate module will be renamed. If the original name of a duplicated module is used in a file where it is not defined, an @@ -51,13 +47,19 @@ def uniquify_spice(sources: List[str]) -> List[SpiceWithoutMultilines]: module_trees = [SpiceUtils.parse_module_tree(s) for s in sources_no_multiline] found_modules = set() # type: Set[str] duplicates = set() # type: Set[str] + extmods = set() # type: Set[str] for tree in module_trees: + extmods.update(set(item for sublist in tree.values() for item in sublist if item not in set(tree.keys()))) for module in tree: if module in found_modules: duplicates.add(module) found_modules.add(module) - def replace_source(source: SpiceWithoutMultilines) -> SpiceWithoutMultilines: + invalid_extmods = extmods.intersection(duplicates) + if len(invalid_extmods) != 0: + raise ValueError("Unable to resolve master for duplicate SPICE module name: {}".format(",".join(invalid_extmods))) + + def replace_source(source: str) -> str: replacements = {} # type: Dict[str, str] for old in duplicates: i = 0 @@ -74,7 +76,7 @@ def replace_source(source: SpiceWithoutMultilines) -> SpiceWithoutMultilines: @staticmethod - def parse_module_tree(s: SpiceWithoutMultilines) -> Dict[str, Set[str]]: + def parse_module_tree(s: str) -> Dict[str, Set[str]]: """ Parse a SPICE file and return a dictionary that contains all found modules pointing to lists of their submodules. The SPICE file must not contain multiline statements. @@ -88,7 +90,7 @@ def parse_module_tree(s: SpiceWithoutMultilines) -> Dict[str, Set[str]]: for m in SpiceUtils.token_regex.finditer(s): kind = m.lastgroup if kind == 'SUBCKT': - module_name = m.group("name") + module_name = m.group("dname") in_module = True if module_name in tree: raise ValueError("Multiple SPICE subckt definitions for \"{}\" in the same file".format(module_name)) @@ -98,18 +100,20 @@ def parse_module_tree(s: SpiceWithoutMultilines) -> Dict[str, Set[str]]: elif kind == 'INST': if not in_module: raise ValueError("Malformed SPICE source while parsing: \"{}\"".format(m.group())) - tree[module_name].add(m.group("name")) + tree[module_name].add(m.group("mname")) else: assert False, "Should not get here" return tree @staticmethod - def replace_modules(source: SpiceWithoutMultilines, mapping: Dict[str, str]) -> SpiceWithoutMultilines: + def replace_modules(source: str, mapping: Dict[str, str]) -> str: """ Replace module names in a provided SPICE file by the provided mapping. + The SPICE file must not contain multiline statements. + - :param source: The input SPICE source with no multilines + :param source: The input SPICE source without any multiline statements :param mapping: A dictionary of old module names mapped to new module names :return: SPICE source with the module names replaced """ @@ -120,17 +124,17 @@ def repl_fn(m) -> str: else: return m.group(0) - return SpiceWithoutMultilines(SpiceUtils.module_inst_pattern.sub(repl_fn, SpiceUtils.module_def_pattern.sub(repl_fn, source))) + return SpiceUtils.module_inst_pattern.sub(repl_fn, SpiceUtils.module_def_pattern.sub(repl_fn, source)) @staticmethod - def remove_multilines(s: str) -> SpiceWithoutMultilines: + def remove_multilines(s: str) -> str: """ Remove all multiline statements from the given SPICE source. :param s: The SPICE source :return: SPICE source without multiline statements """ - return SpiceWithoutMultilines(SpiceUtils.multiline_pattern.sub(" ", s)) + return SpiceUtils.multiline_pattern.sub(" ", s) diff --git a/src/hammer-vlsi/spice_utils_test.py b/src/hammer-vlsi/spice_utils_test.py new file mode 100644 index 000000000..e7abb82b8 --- /dev/null +++ b/src/hammer-vlsi/spice_utils_test.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# SPICE-related tests for hammer-vlsi. +# +# See LICENSE for licence details. + +from inspect import cleandoc +from typing import Iterable, Tuple, Any + +from hammer_utils import SpiceUtils + +import unittest + + +class SpiceUtilsTest(unittest.TestCase): + + multiline = cleandoc(""" + .SUBCKT foo a b c + r0 a b 10 + c0 a b 10.1 + r1 a c 200 + r2 b c 1 + xinst a b + + c bar + .ENDS + .subckt bar d e + + f + r0 d e 1 + r1 e + + f + + 100 + r2 d f + + + + 10 + .ends""") + + no_multiline = cleandoc(""" + .SUBCKT foo a b c + r0 a b 10 + c0 a b 10.1 + r1 a c 200 + r2 b c 1 + xinst a b c bar + .ENDS + .subckt bar d e f + r0 d e 1 + r1 e f 100 + r2 d f 10 + .ends""") + + def test_multiline(self) -> None: + self.assertEqual(SpiceUtils.remove_multilines(self.multiline), self.no_multiline) + + def test_replace_modules(self) -> None: + no_multiline_replaced = cleandoc(""" + .SUBCKT foo a b c + r0 a b 10 + c0 a b 10.1 + r1 a c 200 + r2 b c 1 + xinst a b c baz + .ENDS + .subckt baz d e f + r0 d e 1 + r1 e f 100 + r2 d f 10 + .ends""") + self.assertEqual(SpiceUtils.replace_modules(self.no_multiline, {"bar": "baz"}), no_multiline_replaced) + + hierarchy = [] # type: List[str] + hierarchy_uniq = [] # type: List[str] + + # 0 + hierarchy.append(cleandoc(""" + .subckt top clock in out vdd vss + x0 clock in mid vdd vss middle0 + x1 clock mid out vdd vss middle1 + .ends + .subckt middle0 clock in out vdd vss + m1 out mid vdd vdd pmos + m2 out mid vss vss nmos + xinst clock in mid vdd vss leaf0 + .ends + .subckt middle1 clock in out vdd vss + m1 out mid0 vdd vdd pmos + m2 out mid0 vss vss nmos + xinst clock mid1 mid0 vdd vss leaf1 + xinst clock in mid1 vdd vss leaf1 + .ends + .subckt leaf0 clock in out vdd vss + m1 out in vdd vdd pmos + m2 out in vss vss nmos + r1 clock out 100 + .ends + .subckt leaf1 clock in out vdd vss + m1 out in vdd vdd pmos + m2 out in vss vss nmos + r1 clock out 50 + .ends + """)) + + # 0 + hierarchy_uniq.append(cleandoc(""" + .subckt top clock in out vdd vss + x0 clock in mid vdd vss middle0_0 + x1 clock mid out vdd vss middle1_0 + .ends + .subckt middle0_0 clock in out vdd vss + m1 out mid vdd vdd pmos + m2 out mid vss vss nmos + xinst clock in mid vdd vss leaf0_0 + .ends + .subckt middle1_0 clock in out vdd vss + m1 out mid0 vdd vdd pmos + m2 out mid0 vss vss nmos + xinst clock mid1 mid0 vdd vss leaf1 + xinst clock in mid1 vdd vss leaf1 + .ends + .subckt leaf0_0 clock in out vdd vss + m1 out in vdd vdd pmos + m2 out in vss vss nmos + r1 clock out 100 + .ends + .subckt leaf1 clock in out vdd vss + m1 out in vdd vdd pmos + m2 out in vss vss nmos + r1 clock out 50 + .ends + """)) + + # 1 + hierarchy.append(cleandoc(""" + .subckt top1 clock in out vdd vss + x0 clock in mid vdd vss middle0 + x1 clock mid out vdd vss middle1 + .ends + .subckt middle0 clock in out vdd vss + m1 out mid vdd vdd pmos + m2 out mid vss vss nmos + xinst clock in mid vdd vss leaf0 + .ends + .subckt middle1 clock in out vdd vss + m1 out mid0 vdd vdd pmos + m2 out mid0 vss vss nmos + xinst clock mid1 mid0 vdd vss leaf1 + xinst clock in mid1 vdd vss leaf1 + .ends + .subckt leaf0 clock in out vdd vss + m1 out in vdd vdd pmos + m2 out in vss vss nmos + r1 clock out 100 + .ends + """)) + + # 1 + hierarchy_uniq.append(cleandoc(""" + .subckt top1 clock in out vdd vss + x0 clock in mid vdd vss middle0_1 + x1 clock mid out vdd vss middle1_1 + .ends + .subckt middle0_1 clock in out vdd vss + m1 out mid vdd vdd pmos + m2 out mid vss vss nmos + xinst clock in mid vdd vss leaf0_1 + .ends + .subckt middle1_1 clock in out vdd vss + m1 out mid0 vdd vdd pmos + m2 out mid0 vss vss nmos + xinst clock mid1 mid0 vdd vss leaf1 + xinst clock in mid1 vdd vss leaf1 + .ends + .subckt leaf0_1 clock in out vdd vss + m1 out in vdd vdd pmos + m2 out in vss vss nmos + r1 clock out 100 + .ends + """)) + + # 2 + hierarchy.append(cleandoc(""" + .subckt top3 clock in out vdd vss + x0 clock in mid vdd vss leaf0 + .ends + .subckt leaf0 clock in out vdd vss + m1 out in vdd vdd pmos + m2 out in vss vss nmos + r1 clock out 50 + .ends""")) + + # 2 + hierarchy_uniq.append(cleandoc(""" + .subckt top3 clock in out vdd vss + x0 clock in mid vdd vss leaf0_2 + .ends + .subckt leaf0_2 clock in out vdd vss + m1 out in vdd vdd pmos + m2 out in vss vss nmos + r1 clock out 50 + .ends""")) + + # 3 + hierarchy.append(cleandoc(""" + .subckt top4 clock in out vdd vss + x0 clock in mid vdd vss leaf0 + .ends + """)) + + def test_module_tree(self) -> None: + tree = SpiceUtils.parse_module_tree(self.hierarchy[0]) + check = {} # type: Dict[str, Set[str]] + check['top'] = {'middle0', 'middle1'} + check['middle0'] = {'leaf0'} + check['middle1'] = {'leaf1'} + check['leaf0'] = set() + check['leaf1'] = set() + self.assertEqual(tree, check) + + def test_uniq(self) -> None: + self.assertEqual(SpiceUtils.uniquify_spice(self.hierarchy[0:3]), self.hierarchy_uniq) + + with self.assertRaises(ValueError): + # This should assert because it won't know how to handle leaf0 + SpiceUtils.uniquify_spice(self.hierarchy) + + +if __name__ == '__main__': + unittest.main() diff --git a/src/test/unittests.sh b/src/test/unittests.sh index 7f2862b52..5f82db2de 100755 --- a/src/test/unittests.sh +++ b/src/test/unittests.sh @@ -12,6 +12,7 @@ python3 ../hammer-vlsi/cli_driver_test.py python3 ../hammer-vlsi/utils_test.py python3 ../hammer-vlsi/units_test.py python3 ../hammer-vlsi/verilog_utils_test.py +python3 ../hammer-vlsi/spice_utils_test.py python3 ../hammer-vlsi/lef_utils_test.py python3 ../hammer_config_test/test.py From f1478a6ed25a03d1d3a1dcbe6caf266ee2bf3705 Mon Sep 17 00:00:00 2001 From: John Wright Date: Fri, 14 Jun 2019 01:22:10 -0700 Subject: [PATCH 4/7] Update src/hammer-vlsi/hammer_utils/spice_utils.py Co-Authored-By: edwardcwang --- src/hammer-vlsi/hammer_utils/spice_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hammer-vlsi/hammer_utils/spice_utils.py b/src/hammer-vlsi/hammer_utils/spice_utils.py index 3f7c66a9c..9180c9234 100644 --- a/src/hammer-vlsi/hammer_utils/spice_utils.py +++ b/src/hammer-vlsi/hammer_utils/spice_utils.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # # spice_utils.py -# Misc Verilog utilities +# Misc SPICE utilities # # See LICENSE for licence details. From 88868028eb3afd87d4ce80d6363f954fdfd97d94 Mon Sep 17 00:00:00 2001 From: John Wright Date: Fri, 14 Jun 2019 01:26:23 -0700 Subject: [PATCH 5/7] Review feedback --- src/hammer-vlsi/hammer_utils/spice_utils.py | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/hammer-vlsi/hammer_utils/spice_utils.py b/src/hammer-vlsi/hammer_utils/spice_utils.py index 3f7c66a9c..2adcc61f2 100644 --- a/src/hammer-vlsi/hammer_utils/spice_utils.py +++ b/src/hammer-vlsi/hammer_utils/spice_utils.py @@ -13,16 +13,16 @@ class SpiceUtils: # Multilines in spice start with a + character - multiline_pattern = re.compile("(?:\s*\n\+)+\s*", flags=re.DOTALL) - module_def_pattern = re.compile("^(\.subckt\s+)(?P[^\s]+)(\s+.*)$", flags=re.IGNORECASE|re.MULTILINE) - module_inst_pattern = re.compile("^(x.*?\s)(?P[^\s]+)(\s*)$", flags=re.IGNORECASE|re.MULTILINE) - end_module_pattern = re.compile("^\.ends.*$", flags=re.IGNORECASE|re.MULTILINE) - tokens = [ - ('SUBCKT', module_def_pattern.pattern), - ('ENDS', end_module_pattern.pattern), - ('INST', module_inst_pattern.pattern) + MultilinePattern = re.compile("(?:\s*\n\+)+\s*", flags=re.DOTALL) + ModuleDefPattern = re.compile("^(\.subckt\s+)(?P[^\s]+)(\s+.*)$", flags=re.IGNORECASE|re.MULTILINE) + ModuleInstPattern = re.compile("^(x.*?\s)(?P[^\s]+)(\s*)$", flags=re.IGNORECASE|re.MULTILINE) + EndModulePattern = re.compile("^\.ends.*$", flags=re.IGNORECASE|re.MULTILINE) + Tokens = [ + ('SUBCKT', ModuleDefPattern.pattern), + ('ENDS', EndModulePattern.pattern), + ('INST', ModuleInstPattern.pattern) ] - token_regex = re.compile('|'.join('(?P<%s>%s)' % t for t in tokens), flags=re.IGNORECASE|re.MULTILINE) + TokenRegex = re.compile('|'.join('(?P<%s>%s)' % t for t in Tokens), flags=re.IGNORECASE|re.MULTILINE) @staticmethod def uniquify_spice(sources: List[str]) -> List[str]: @@ -87,7 +87,7 @@ def parse_module_tree(s: str) -> Dict[str, Set[str]]: in_module = False module_name = "" tree = {} # type: Dict[str, Set[str]] - for m in SpiceUtils.token_regex.finditer(s): + for m in SpiceUtils.TokenRegex.finditer(s): kind = m.lastgroup if kind == 'SUBCKT': module_name = m.group("dname") @@ -124,7 +124,7 @@ def repl_fn(m) -> str: else: return m.group(0) - return SpiceUtils.module_inst_pattern.sub(repl_fn, SpiceUtils.module_def_pattern.sub(repl_fn, source)) + return SpiceUtils.ModuleInstPattern.sub(repl_fn, SpiceUtils.ModuleDefPattern.sub(repl_fn, source)) @staticmethod def remove_multilines(s: str) -> str: @@ -134,7 +134,7 @@ def remove_multilines(s: str) -> str: :param s: The SPICE source :return: SPICE source without multiline statements """ - return SpiceUtils.multiline_pattern.sub(" ", s) + return SpiceUtils.MultilinePattern.sub(" ", s) From 73bce8e6368fe63d34ad79be831a0769bcb01a3d Mon Sep 17 00:00:00 2001 From: John Wright Date: Fri, 14 Jun 2019 01:27:11 -0700 Subject: [PATCH 6/7] missed some types --- src/hammer-vlsi/spice_utils_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hammer-vlsi/spice_utils_test.py b/src/hammer-vlsi/spice_utils_test.py index e7abb82b8..48b4aded5 100644 --- a/src/hammer-vlsi/spice_utils_test.py +++ b/src/hammer-vlsi/spice_utils_test.py @@ -6,7 +6,7 @@ # See LICENSE for licence details. from inspect import cleandoc -from typing import Iterable, Tuple, Any +from typing import Iterable, Tuple, Any, Dict, List from hammer_utils import SpiceUtils From 6583c525ac8cf7177e54d640c705bcba82714cbc Mon Sep 17 00:00:00 2001 From: John Wright Date: Fri, 14 Jun 2019 01:32:06 -0700 Subject: [PATCH 7/7] Eventually I'll get better at this. --- src/hammer-vlsi/spice_utils_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hammer-vlsi/spice_utils_test.py b/src/hammer-vlsi/spice_utils_test.py index 48b4aded5..284ac210b 100644 --- a/src/hammer-vlsi/spice_utils_test.py +++ b/src/hammer-vlsi/spice_utils_test.py @@ -6,7 +6,7 @@ # See LICENSE for licence details. from inspect import cleandoc -from typing import Iterable, Tuple, Any, Dict, List +from typing import Iterable, Tuple, Any, Dict, List, Set from hammer_utils import SpiceUtils