From b52262d419b0aa96c2aaf8e55b08ab019c689f99 Mon Sep 17 00:00:00 2001 From: Kerry Wang <43691721+WorldofKerry@users.noreply.github.com> Date: Sun, 22 Oct 2023 18:01:04 -0600 Subject: [PATCH] Reduced hardware usage with nested for loops (#182) - Improved errors --- pyproject.toml | 2 +- python2verilog/api/verilogify.py | 10 +-- python2verilog/exceptions/__init__.py | 21 +++--- python2verilog/frontend/function.py | 92 ++++++++++++--------------- python2verilog/ir/context.py | 9 +-- python2verilog/optimizer/helpers.py | 1 - python2verilog/utils/generics.py | 1 + tests/integration/functions.py | 18 ++++++ tests/integration/test_multi.py | 2 + 9 files changed, 86 insertions(+), 70 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 273b2d1f..a6637949 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python2verilog" -version = "0.4.1" +version = "0.4.2" authors = [{ name = "Kerry Wang", email = "kerrywang369@gmail.com" }] description = "Converts a subset of python generator functions into synthesizable sequential SystemVerilog" readme = "README.md" diff --git a/python2verilog/api/verilogify.py b/python2verilog/api/verilogify.py index f4c6ae5f..c7c3a06e 100644 --- a/python2verilog/api/verilogify.py +++ b/python2verilog/api/verilogify.py @@ -95,9 +95,7 @@ def generator_wrapper(*args, **kwargs): if not context.input_types: context.input_types = [type(arg) for arg in args] for val in context.input_types: - assert ( - val == int - ), f"Unexpected {val} as a input type {list(map(type, args))}" + assert val == int, f"Unexpected {val} as a input type" else: context.check_input_types(args) @@ -110,7 +108,9 @@ def tuplefy(either: Union[int, tuple[int]]) -> tuple[int]: elif isinstance(either, tuple): ret = either else: - raise StaticTypingError(f"Unexpected yielded value {either}") + raise StaticTypingError( + f"Unexpected yielded value `{either}` from `{func.__name__}`" + ) return ret # Always get output one-ahead of what func user sees @@ -186,7 +186,7 @@ def tuplefy(either: Union[int, tuple[int]]) -> tuple[int]: assert guard(value, int) except Exception as e: raise StaticTypingError( - "Expected `int` type inputs and outputs" + f"Expected `int` type inputs and outputs for `{func.__name__}`" ) from e return ret diff --git a/python2verilog/exceptions/__init__.py b/python2verilog/exceptions/__init__.py index cac7cf4d..341506ff 100644 --- a/python2verilog/exceptions/__init__.py +++ b/python2verilog/exceptions/__init__.py @@ -17,18 +17,20 @@ class UnsupportedSyntaxError(Exception): Python syntax was not within the supported subset """ - def __init__(self, *args: object) -> None: + def __init__(self, msg: object) -> None: super().__init__( - "Python syntax was not within the supported subset", - *args, + msg, ) @classmethod - def from_pyast(cls, node: ast.AST): + def from_pyast(cls, node: ast.AST, name: str): """ Based on AST error """ - inst = cls(f"Unsupported Python syntax {ast.dump(node)}") + inst = cls( + f"Unsupported Python syntax `{ast.unparse(node)}` found in function " + f"`{name}` as {ast.dump(node)}" + ) return inst @@ -49,10 +51,13 @@ class TypeInferenceError(Exception): Type inferrence failed, either use the function in code or provide type hints """ - def __init__(self, *args: object) -> None: + def __init__(self, name: str) -> None: + """ + :param name: function name + """ msg = ( - "Input/output type inferrence failed, " + f"Input/output type inferrence failed for function `{name}`, " "either use the function in Python code or provide type hints", ) - super().__init__(" ".join(map(str, (*msg, *args)))) + super().__init__(msg) diff --git a/python2verilog/frontend/function.py b/python2verilog/frontend/function.py index 760a03dc..6016ba2c 100644 --- a/python2verilog/frontend/function.py +++ b/python2verilog/frontend/function.py @@ -184,7 +184,7 @@ def _parse_stmt( assert guard(dummy.child, ir.Edge) return dummy, [dummy.child] - raise TypeError(f"Unexpected statement {pyast.dump(stmt)}") + raise UnsupportedSyntaxError.from_pyast(stmt, self.__context.name) def _parse_return(self, ret: pyast.Return, prefix: str) -> ParseResult: """ @@ -195,12 +195,15 @@ def _parse_return(self, ret: pyast.Return, prefix: str) -> ParseResult: return done, [] assert not self.__context.is_generator - if isinstance(ret.value, pyast.Tuple): - stmts = [self._parse_expression(c) for c in ret.value.elts] - elif isinstance(ret.value, pyast.expr): - stmts = [self._parse_expression(ret.value)] - else: - raise TypeError(f"Expected tuple {type(ret.value)} {pyast.dump(ret)}") + try: + if isinstance(ret.value, pyast.Tuple): + stmts = [self._parse_expression(c) for c in ret.value.elts] + elif isinstance(ret.value, pyast.expr): + stmts = [self._parse_expression(ret.value)] + else: + raise UnsupportedSyntaxError.from_pyast(ret, self.__context.name) + except Exception as e: + raise UnsupportedSyntaxError.from_pyast(ret, self.__context.name) from e self.__context.validate() head, tail = self._weave_nonclocked_edges( @@ -256,7 +259,7 @@ def _parse_assign_to_call(self, assign: pyast.Assign, prefix: str) -> ParseResul target = assign.targets[0] assert isinstance(target, pyast.Name) - def get_func_call_names(caller_cxt: ir.Context): + def get_func_call_names(): """ :return: target_name, func_name """ @@ -269,21 +272,9 @@ def get_func_call_names(caller_cxt: ir.Context): assert guard(func, pyast.Name) func_name = func.id - if target_name in map( - lambda x: x.py_name, - ( - *caller_cxt.local_vars, - *caller_cxt.input_vars, - *caller_cxt.output_vars, - ), - ): - raise StaticTypingError( - f"{target_name} changed type from another type to generator instance" - ) - return target_name, func_name - target_name, func_name = get_func_call_names(self.__context) + target_name, func_name = get_func_call_names() # Get context of generator function being called callee_cxt = self.__context.namespace[func_name] @@ -299,7 +290,7 @@ def get_func_call_names(caller_cxt: ir.Context): call_args=assign.value.args, callee_cxt=callee_cxt, targets=assign.targets, - target_name=target_name, + _target_name=target_name, prefix=prefix, ) @@ -308,7 +299,7 @@ def _parse_func_call( call_args: list[pyast.expr], targets: list[pyast.expr], callee_cxt: ir.Context, - target_name: str, + _target_name: str, prefix: str, ) -> ParseResult: """ @@ -317,7 +308,7 @@ def _parse_func_call( Implemented as an inline (no external unit). """ callee_cxt, body_head, prev_tails = Function( - callee_cxt, prefix=f"{prefix}_{target_name}_" + callee_cxt, prefix=f"{prefix}_" ).parse_inline() arguments = list(map(self._parse_expression, call_args)) @@ -474,7 +465,7 @@ def _parse_for_gen_call(self, stmt: pyast.For, prefix: str) -> ParseResult: assert guard(stmt.iter, pyast.Call) gen_cxt = self.__context.namespace[self._get_func_call_name(stmt.iter)] - mangled_name = f"{prefix}_offset{stmt.col_offset}" # consider nested for loops + mangled_name = f"nested{stmt.col_offset}" # consider nested for loops call_head, call_tails = self._parse_gen_call( call_args=stmt.iter.args, @@ -849,30 +840,27 @@ def _parse_expression(self, expr: pyast.expr) -> ir.Expression: """ (e.g. constant, name, subscript, etc., those that return a value) """ - try: - if isinstance(expr, pyast.Constant): - return ir.Int(expr.value) - if isinstance(expr, pyast.Name): - return self.__context.make_var(expr.id) - if isinstance(expr, pyast.Subscript): - return self._parse_subscript(expr) - if isinstance(expr, pyast.BinOp): - return self._parse_binop(expr) - if isinstance(expr, pyast.UnaryOp): - if isinstance(expr.op, pyast.USub): - return ir.UnaryOp("-", self._parse_expression(expr.operand)) - if isinstance(expr, pyast.Compare): - return self._parse_compare(expr) - if isinstance(expr, pyast.BoolOp): - if isinstance(expr.op, pyast.And): - return ir.UBinOp( - self._parse_expression(expr.values[0]), - "&&", - self._parse_expression(expr.values[1]), - ) - except Exception as e: - raise UnsupportedSyntaxError.from_pyast(expr) from e - raise UnsupportedSyntaxError.from_pyast(expr) + if isinstance(expr, pyast.Constant): + return ir.Int(expr.value) + if isinstance(expr, pyast.Name): + return self.__context.make_var(expr.id) + if isinstance(expr, pyast.Subscript): + return self._parse_subscript(expr) + if isinstance(expr, pyast.BinOp): + return self._parse_binop(expr) + if isinstance(expr, pyast.UnaryOp): + if isinstance(expr.op, pyast.USub): + return ir.UnaryOp("-", self._parse_expression(expr.operand)) + if isinstance(expr, pyast.Compare): + return self._parse_compare(expr) + if isinstance(expr, pyast.BoolOp): + if isinstance(expr.op, pyast.And): + return ir.UBinOp( + self._parse_expression(expr.values[0]), + "&&", + self._parse_expression(expr.values[1]), + ) + raise UnsupportedSyntaxError.from_pyast(expr, self.__context.name) def _parse_subscript(self, node: pyast.Subscript) -> ir.Expression: """ @@ -907,7 +895,9 @@ def _parse_compare(self, node: pyast.Compare) -> ir.UBinOp: elif isinstance(node.ops[0], pyast.Eq): operator = "===" else: - raise UnsupportedSyntaxError(f"Unknown operator {pyast.dump(node.ops[0])}") + raise UnsupportedSyntaxError( + f"Unsupported operator {pyast.dump(node.ops[0])}" + ) return ir.UBinOp( left=self._parse_expression(node.left), oper=operator, @@ -942,4 +932,4 @@ def _parse_binop(self, expr: pyast.BinOp) -> ir.Expression: left = self._parse_expression(expr.left) right = self._parse_expression(expr.right) return ir.Mod(left, right) - raise UnsupportedSyntaxError(f"Unexpected binop type {pyast.dump(expr.op)}") + raise UnsupportedSyntaxError(f"Unsupported binop `{pyast.dump(expr.op)}") diff --git a/python2verilog/ir/context.py b/python2verilog/ir/context.py index 1e66fcd2..16c35431 100644 --- a/python2verilog/ir/context.py +++ b/python2verilog/ir/context.py @@ -120,7 +120,7 @@ def input_mapper(arg: ast.arg) -> type[Any]: try: self.input_types = list(map(input_mapper, input_args)) except Exception as e: - raise TypeInferenceError() from e + raise TypeInferenceError(self.name) from e def _use_output_type_hints(self): """ @@ -150,7 +150,7 @@ def output_mapper(arg: ast.Name) -> type[Any]: try: self.output_types = list(map(output_mapper, output_args)) except Exception as e: - raise TypeInferenceError(f"in function `{self.name}`") from e + raise TypeInferenceError(self.name) from e self.default_output_vars() def validate(self): @@ -296,7 +296,8 @@ def add_special_local_var(self, var: Var): """ if var.py_name in self.generator_instances: raise StaticTypingError( - f"{var.py_name} changed type from generator instance to another type" + f"Variable `{var.py_name}` changed type from generator" + f" instance to another type in {self.name}" ) if var not in (*self._local_vars, *self.input_vars, *self.output_vars): self._local_vars.append(typed_strict(var, Var)) @@ -367,7 +368,7 @@ def create_generator_instance(self, name: str) -> Instance: ) ) - signals = ProtocolSignals(prefix=f"{self.prefix}{name}_{self.name}__") + signals = ProtocolSignals(prefix=f"{self.prefix}{self.name}_{name}__") return Instance( self.name, diff --git a/python2verilog/optimizer/helpers.py b/python2verilog/optimizer/helpers.py index 63d55d74..87628096 100644 --- a/python2verilog/optimizer/helpers.py +++ b/python2verilog/optimizer/helpers.py @@ -20,7 +20,6 @@ def backwards_replace( """ expr = copy.deepcopy(expr) if isinstance(expr, ir.Var): - # if not isinstance(expr, ir.ExclusiveVar): for key in mapping: if key.to_string() == expr.to_string(): return mapping[key] diff --git a/python2verilog/utils/generics.py b/python2verilog/utils/generics.py index f85b6afc..81f9d010 100644 --- a/python2verilog/utils/generics.py +++ b/python2verilog/utils/generics.py @@ -37,5 +37,6 @@ class GenericReprAndStr(GenericRepr): Implements a generic __repr__ and __str__ based on self.__dict__ """ + @reprlib.recursive_repr() def __str__(self): return f"{self.__class__.__name__}\n{pretty_dict(self.__dict__)}" diff --git a/tests/integration/functions.py b/tests/integration/functions.py index 91d50dac..5a99d8a3 100644 --- a/tests/integration/functions.py +++ b/tests/integration/functions.py @@ -360,3 +360,21 @@ def fib_product(n): for num in fib(n): prod = multiplier(num, num) yield prod + + +def multi_funcs(a, b): + """ + Testing multiple function calls and tested function calls + """ + temp = multiplier(a, b) + yield temp + temp = multiplier(a + 10, b) + yield temp + for i in p2vrange(0, 2, 1): + yield i + for i in p2vrange(0, 2, 1): + yield i + for i in p2vrange(0, 2, 1): + yield i + for i in p2vrange(0, 2, 1): + yield i diff --git a/tests/integration/test_multi.py b/tests/integration/test_multi.py index 6633cb09..0178db4f 100644 --- a/tests/integration/test_multi.py +++ b/tests/integration/test_multi.py @@ -13,6 +13,8 @@ from .utils import name_func PARAMETERS = [ + ([multi_funcs, multiplier, p2vrange], [(13, 17)]), + ([fib_product, multiplier, fib, p2vrange], [10, 20]), ([fib_product, multiplier, fib, p2vrange], [10, 20]), ([fib, p2vrange], range(10, 31, 10)), ([quad_multiply, multiplier_generator], [(3, 7), (31, 43)]),