pyha/conversion/redbaron_transforms.py
import logging
import textwrap
from contextlib import suppress
from parse import parse
from redbaron import Node, EndlNode, DefNode, AssignmentNode, TupleNode, CommentNode, FloatNode, \
IntNode, UnitaryOperatorNode, GetitemNode, inspect, CallNode, AtomtrailersNode, CallArgumentNode, \
BinaryOperatorNode, ComplexNode, AssociativeParenthesisNode
from redbaron.base_nodes import LineProxyList
import pyha
from pyha import Complex
from pyha.common.core import SKIP_FUNCTIONS, Hardware, PyhaFunc
from pyha.common.fixed_point import Sfix
from pyha.common.util import get_iterable, tabber, formatter, is_constant, const_filter
from pyha.conversion.type_transforms import escape_reserved_vhdl, VHDLModule, init_vhdl_type, VHDLEnum, VHDLList, \
TypeAppendHack
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def file_header():
template = '-- generated by pyha {}'
return template.format(pyha.__version__)
class NodeVHDL:
def __init__(self, red_node, parent=None):
self.red_node = red_node
self.parent = parent
self.target = None
self.value = None
self.first = None
self.second = None
self.test = None
self.arguments = None
self.name = None
self.iterator = None
for x in red_node._dict_keys:
self.__dict__[x] = redbaron_node_to_vhdl_node(red_node.__dict__[x], caller=self)
for x in red_node._list_keys:
if 'format' not in x:
self.__dict__[x] = []
for xj in red_node.__dict__[x]:
# function is NOT simulated, dont convert it
if isinstance(xj, DefNode) and getattr(convert_obj, xj.name).calls == 0:
logger.warning(
f'Class "{red_node.name}" function "{xj.name}" was NOT called during simulation, not converting it!')
continue
self.__dict__[x].append(redbaron_node_to_vhdl_node(xj, caller=self))
for x in red_node._str_keys:
self.__dict__[x] = red_node.__dict__[x]
def __str__(self):
return str(self.red_node)
class NameNodeVHDL(NodeVHDL):
def __str__(self):
return escape_reserved_vhdl(self.red_node.value)
class AtomtrailersNodeVHDL(NodeVHDL):
def is_function_call(self):
return any(isinstance(x, CallNodeVHDL) for x in self.value)
def __str__(self):
ret = ''
for i, x in enumerate(self.value):
# add '.' infront if NameNode
new = '.{}' if isinstance(x, NameNodeVHDL) and i != 0 else '{}'
if x.value == 'len' and isinstance(x.red_node.next, CallNode):
continue
if isinstance(x.red_node, CallNode) and x.red_node.previous.value == 'len':
new = f"{str(x)[1:-1]}'length"
ret += new.format(x)
return ret
class TupleNodeVHDL(NodeVHDL):
def __iter__(self):
return iter(self.value)
def __str__(self):
return ','.join(str(x) for x in self.value)
class AssignmentNodeVHDL(NodeVHDL):
def __str__(self):
return f'{self.target} := {self.value};'
class ReturnNodeVHDL(NodeVHDL):
def __str__(self):
ret = []
for i, value in enumerate(get_iterable(self.value)):
line = f'ret_{i} := {value}'
if line[-1] != ';':
line += ';'
ret.append(line)
ret += ['return;']
return '\n'.join(ret)
class ComparisonNodeVHDL(NodeVHDL):
def __str__(self):
return f'{self.first} {self.value} {self.second}'
class BinaryOperatorNodeVHDL(ComparisonNodeVHDL):
def __str__(self):
# test if we are dealing with array appending ([a] + b)
if self.value == '+':
if isinstance(self.first, ListNodeVHDL) or isinstance(self.second, ListNodeVHDL):
return f'{self.first} & {self.second}'
elif self.value == '//':
return f'integer({self.first} / {self.second})'
elif self.value == '>>':
self.value = 'sra'
elif self.value == '<<':
self.value = 'sla'
elif self.value == '&':
self.value = 'and'
elif self.value == '|':
self.value = 'or'
elif self.value == '^':
self.value = 'xor'
elif self.value == '%':
self.value = 'mod'
return f'{self.first} {self.value} {self.second}'
class BooleanOperatorNodeVHDL(ComparisonNodeVHDL):
pass
class AssociativeParenthesisNodeVHDL(NodeVHDL):
def __str__(self):
return f'({self.value})'
class ComparisonOperatorNodeVHDL(NodeVHDL):
def __str__(self):
if self.first == '==':
return '='
elif self.first == '!=':
return '/='
else:
return super().__str__()
class IfelseblockNodeVHDL(NodeVHDL):
def __str__(self):
body = '\n'.join(str(x) for x in self.value)
return body + '\nend if;'
class IfNodeVHDL(NodeVHDL):
def __str__(self):
body = '\n'.join(tabber(str(x)) for x in self.value)
return f'if {self.test} then\n{body}'
class ElseNodeVHDL(NodeVHDL):
def __str__(self):
body = '\n'.join(tabber(str(x)) for x in self.value)
return f'else\n{body}'
class ElifNodeVHDL(NodeVHDL):
def __str__(self):
body = '\n'.join(tabber(str(x)) for x in self.value)
return f'elsif {self.test} then\n{body}'
class DefNodeVHDL(NodeVHDL):
def __init__(self, red_node, parent=None):
super().__init__(red_node, parent)
# todo: remove me after refactorings
try:
self.data = getattr(convert_obj, self.name)
except AttributeError:
self.data = None
self.name = escape_reserved_vhdl(self.name)
# collect multiline comment
self.multiline_comment = ''
if isinstance(self.value[0], StringNodeVHDL):
self.multiline_comment = str(self.value[0])
del self.value[0]
# remove last line,if it is \n
if isinstance(self.value[-1], EndlNodeVHDL):
del self.value[-1]
self.arguments = self.build_arguments()
self.variables = self.build_variables()
def build_arguments(self):
# function arguments
argnames = inspect.getfullargspec(self.data.func).args[1:] # skip the first 'self'
argvals = list(self.data.get_arg_types())
args = [init_vhdl_type(name, val, val) for name, val in zip(argnames, argvals)]
args = ['self:in self_t; self_next:inout self_t; constant self_const: self_t_const'] + [
f'{x._pyha_name()}: {x._pyha_type()}' for x in args]
# function returns -> need to add as 'out' arguments in VHDL
rets = []
if self.data.get_output_types() is not None:
if isinstance(self.data.get_output_types(), tuple): # multiple returns
rets = [init_vhdl_type(f'ret_{i}', val, val)
for i, val in enumerate(get_iterable(self.data.get_output_types()))]
else:
rets = [init_vhdl_type(f'ret_{0}', self.data.get_output_types(), self.data.get_output_types())]
rets = [f'{x._pyha_name()}:out {x._pyha_type()}' for x in rets]
return '; '.join(args + rets)
def build_variables(self):
argnames = inspect.getfullargspec(self.data.func).args
variables = [init_vhdl_type(name, val, val)
for name, val in self.data.get_local_types().items()
if name not in argnames]
variables = [f'variable {x._pyha_name()}: {x._pyha_type()};' for x in variables if x is not None]
return '\n'.join(variables)
def build_body(self):
return '\n'.join(str(x) for x in self.value)
def build_function(self, prototype_only=False):
template = textwrap.dedent("""\
procedure {NAME}{ARGUMENTS} is
{MULTILINE_COMMENT}
{VARIABLES}
begin
{BODY}
end procedure;""")
args = f'({self.arguments})' if len(self.arguments) > 2 else ''
sockets = {'NAME': self.name,
'MULTILINE_COMMENT': self.multiline_comment,
'ARGUMENTS': args,
'VARIABLES': tabber(self.variables) if self.variables else '',
'BODY': tabber(self.build_body())}
if prototype_only:
return template.format(**sockets).splitlines()[0][:-3] + ';'
return template.format(**sockets)
def __str__(self):
return self.build_function()
class DefArgumentNodeVHDL(NodeVHDL):
# this node is not used. arguments are inferred from datamodel!
pass
class PassNodeVHDL(NodeVHDL):
def __str__(self):
return ''
class CallNodeVHDL(NodeVHDL):
def __str__(self):
base = '(' + ', '.join(str(x) for x in self.value) + ')'
# find if this call is part of assignment node or AssociativeParenthesisNode
p = self.red_node.parent
is_assign = False
is_assoc = False
while p is not None:
if type(p) == AssignmentNode:
is_assign = True
break
if type(p) == AssociativeParenthesisNode:
is_assoc = True
break
p = p.parent
if not is_assign and not is_assoc and isinstance(self.red_node.next_recursive, (EndlNode, CommentNode)):
if not isinstance(self.red_node.parent.parent, CallArgumentNode): # dont add ; for last argument
base += ';'
return base
class CallArgumentNodeVHDL(NodeVHDL):
def __str__(self):
# transform keyword arguments, = to =>
if self.target is not None:
return f'{self.target}=>{self.value}'
return str(self.value)
class IntNodeVHDL(NodeVHDL):
pass
class FloatNodeVHDL(NodeVHDL):
pass
class UnitaryOperatorNodeVHDL(NodeVHDL):
def __str__(self):
if self.value == '-':
return f'{self.value}{self.target}' # eg. -1
else:
return f'{self.value} {self.target}' # eg. not self.val
class AssertNodeVHDL(NodeVHDL):
def __str__(self):
return '--' + super().__str__()
class PrintNodeVHDL(NodeVHDL):
def __str__(self):
if isinstance(self.red_node.value[0], TupleNode):
raise Exception(f'{self.red_node} -> print only supported with one Sfix argument!')
return f"report to_string{self.value[0]};"
class ListNodeVHDL(NodeVHDL):
def __str__(self):
if len(self.value) == 1:
return str(self.value[0]) # [a] -> a
else:
ret = f'({", ".join(str(x) for x in self.value)})'
return ret
class EndlNodeVHDL(NodeVHDL):
def __str__(self):
if isinstance(self.red_node.previous_rendered, CommentNode):
return '--' + str(self.red_node.previous_rendered)[1:]
return ''
class HexaNodeVHDL(NodeVHDL):
def __str__(self):
return f'16#{self.value[2:]}#'
class CommentNodeVHDL(NodeVHDL):
def __str__(self):
return '--' + self.value[1:]
class StringNodeVHDL(NodeVHDL):
""" Multiline comments come here """
def __str__(self):
if self.value[:3] == '"""' and self.value[-3:] == '"""':
r = [x.strip() for x in self.value[3:-3].splitlines()]
r = '\n-- '.join(x for x in r if x != '')
return '-- ' + r
return self.value[1:]
# this is mostly array indexing
class GetitemNodeVHDL(NodeVHDL):
# turn python [] indexing to () indexing
def get_index_target(self):
ret = ''
for x in self.parent.value:
if x is self:
break
ret += '.' + str(x)
return ret[1:]
def is_negative_indexing(self, obj):
try:
return isinstance(obj, UnitaryOperatorNodeVHDL) and int(str(obj)) < 0
except ValueError:
return False
def __str__(self):
if self.is_negative_indexing(self.value):
target = self.get_index_target()
return f"({target}'length{self.value})"
return f'({self.value})'
class SliceNodeVHDL(GetitemNodeVHDL):
def get_index_target(self):
return '.'.join(str(x) for x in self.parent.parent.value[:-1])
# Example: [0:5] -> (0 to 4)
# x[0:-1] -> x(0 to x'high-1)
def __str__(self):
if self.upper is None:
upper = f"{self.get_index_target()}'high"
else:
# vhdl includes upper limit, subtract one to get same behaviour as in python
upper = f'({self.upper})-1'
if self.is_negative_indexing(self.upper):
target = self.get_index_target()
upper = f"{target}'high{self.upper}"
lower = 0 if self.lower is None else self.lower
return f'{lower} to {upper}'
class ForNodeVHDL(NodeVHDL):
def __str__(self):
template = textwrap.dedent("""\
for {ITERATOR} in {RANGE} loop
{BODY}
end loop;""")
sockets = {'ITERATOR': str(self.iterator)}
sockets['RANGE'] = self.range_to_vhdl(str(self.target))
sockets['BODY'] = '\n'.join(tabber(str(x)) for x in self.value)
return template.format(**sockets)
def range_to_vhdl(self, pyrange):
# this for was transforemed by 'redbaron_pyfor_to_vhdl'
if str(self.iterator) == '\\_i_\\':
return f"{pyrange}'range"
range_len_pattern = parse("\\range\\({}'length)", pyrange)
if range_len_pattern is not None and ',' not in range_len_pattern[0]:
return range_len_pattern[0] + "'range"
else:
range_pattern = parse('\\range\\({});', pyrange)
if range_pattern is None:
range_pattern = parse('\\range\\({})', pyrange)
if range_pattern is not None:
two_args = parse('{},{}', range_pattern[0])
if two_args is not None:
# todo: handle many more cases
len = parse("{}'length", two_args[1].strip())
if len is not None:
return f"{two_args[0].strip()} to ({len[0]}'length) - 1"
len = parse("{}'length{}", two_args[1].strip())
if len is not None:
return f"{two_args[0].strip()} to ({len[0]}'length{len[1]}) - 1"
return f'{two_args[0].strip()} to ({two_args[1].strip()}) - 1'
else:
len = parse("{}'length{}", range_pattern[0])
if len is not None:
return f"0 to ({len[0]}'length{len[1]}) - 1"
return f'0 to ({range_pattern[0]}) - 1'
# at this point range was not:
# range(len(x))
# range(x)
# range(x, y)
# assume
assert 0
class ClassNodeVHDL(NodeVHDL):
def __init__(self, red_node, parent=None):
super().__init__(red_node, parent)
# todo: remove me after refactorings
try:
self.data = VHDLModule('-', convert_obj)
except AttributeError:
self.data = None
# collect multiline comment
self.multiline_comment = ''
if len(self.value) and isinstance(self.value[0], StringNodeVHDL):
self.multiline_comment = str(self.value[0])
del self.value[0]
def get_function(self, name):
f = [x for x in self.value if str(x.name) == name]
assert len(f)
return f[0]
def build_imports(self):
template = textwrap.dedent("""\
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.fixed_float_types.all;
use ieee.fixed_pkg.all;
use ieee.math_real.all;
library work;
use work.complex_pkg.all;
use work.PyhaUtil.all;
use work.Typedefs.all;
use work.all;
{IMPORTS}""")
# add all converted classes to imports
# look: https://github.com/tgingold/ghdl/issues/209
from pyha.conversion.conversion import RecursiveConverter
imports = [f'use work.{x}.all;' for x in RecursiveConverter.converted_modules.keys()]
return template.format(IMPORTS=formatter(imports))
def build_constructor(self, prototype_only=False):
template = textwrap.dedent("""\
function {NAME}{ARGS} return self_t is
-- constructor
variable self: self_t;
begin
{DATA}
return self;
end function;""")
data = [x._pyha_constructor() for x in self.data.elems]
args = '; '.join([x._pyha_constructor_arg() for x in self.data.elems if x._pyha_constructor_arg() != ''])
if args != '':
args = f'({args[:-2]})' if args[-2:] == '; ' else f'({args})'
ret = template.format(NAME=escape_reserved_vhdl(self.name), ARGS=args, DATA=formatter(data))
if prototype_only:
return ret.splitlines()[0][:-3] + ';'
return ret
def build_data_structs(self):
template = textwrap.dedent("""\
type self_t is record
{DATA}
end record;""")
data = [x._pyha_definition() for x in self.data.elems if not is_constant(x._name)]
if not data:
data = ['dummy: integer;']
return template.format(DATA=formatter(data))
def build_constants(self):
template = textwrap.dedent("""\
type self_t_const is record
{DATA}
end record;""")
data = [x._pyha_definition() for x in self.data.elems if const_filter(x)]
if not data:
data = ['DUMMY: integer;']
return template.format(DATA=formatter(data))
def build_typedefs(self):
# self typedefs
typedefs = [x._pyha_typedef() for x in self.data.elems if x._pyha_typedef() is not None]
# local vars
for function in self.value:
if not isinstance(function, DefNodeVHDL):
continue
variables = [init_vhdl_type(name, val, val) for name, val in function.data.get_local_types().items()]
typedefs += [x._pyha_typedef() for x in variables if x is not None and x._pyha_typedef() is not None]
typedefs = list(dict.fromkeys(typedefs)) # get rid of duplicates
return typedefs
def build_package_header(self):
template = textwrap.dedent("""\
{MULTILINE_COMMENT}
package {NAME} is
{SELF_T}
{SELF_ARRAY_TYPEDEF}
{CONST_SELF_T}
{CONST_SELF_ARRAY_TYPEDEF}
{FUNC_HEADERS}
end package;""")
sockets = {}
sockets['MULTILINE_COMMENT'] = self.multiline_comment
sockets['NAME'] = self.data._pyha_module_name()
# data-structure without constants ie. registers
sockets['SELF_T'] = tabber(self.build_data_structs())
sockets[
'SELF_ARRAY_TYPEDEF'] = f' type {self.data._pyha_arr_type_name()} is array (natural range <>) of {self.data._pyha_type()};'
# only constants
with TypeAppendHack('_const'):
sockets['CONST_SELF_T'] = tabber(self.build_constants())
sockets[
'CONST_SELF_ARRAY_TYPEDEF'] = f' type {self.data._pyha_arr_type_name()} is array (natural range <>) of {self.data._pyha_type()};'
proto = '\n'.join(x.build_function(prototype_only=True) for x in self.value if isinstance(x, DefNodeVHDL))
proto += '\n' + self.build_constructor(prototype_only=True) + '\n'
sockets['FUNC_HEADERS'] = tabber(proto)
return template.format(**sockets)
def build_package_body(self):
template = textwrap.dedent("""\
package body {NAME} is
{USER_FUNCTIONS}
{CONSTRUCTOR}
end package body;""")
sockets = {}
sockets['NAME'] = self.data._pyha_module_name()
sockets['CONSTRUCTOR'] = tabber(self.build_constructor())
# sockets['INIT_SELF'] = tabber(self.build_init())
# sockets['CONSTANT_SELF'] = tabber(self.build_reset_constants())
# sockets['RESET_SELF'] = tabber(self.build_reset())
# sockets['UPDATE_SELF'] = tabber(self.build_update_registers())
sockets['USER_FUNCTIONS'] = '\n\n'.join(tabber(str(x)) for x in self.value if isinstance(x, DefNodeVHDL))
return template.format(**sockets)
def __str__(self):
template = textwrap.dedent("""\
{FILE_HEADER}
{IMPORTS}
{PACKAGE_HEADER}
{PACKAGE_BODY}
""")
sockets = {}
sockets['FILE_HEADER'] = file_header()
sockets['IMPORTS'] = self.build_imports()
sockets['PACKAGE_HEADER'] = self.build_package_header()
sockets['PACKAGE_BODY'] = self.build_package_body()
return template.format(**sockets)
def redbaron_node_to_vhdl_node(red: Node, caller):
""" Convert RedBaron class to conversion class
For example: red:NameNode returns NameNodeVHDL class
"""
import sys
red_type = red.__class__.__name__
try:
cls = getattr(sys.modules[__name__], red_type + 'VHDL')
except AttributeError:
if red_type == 'NoneType':
return None
raise
return cls(red_node=red, parent=caller)
convert_obj = None
def set_convert_obj(obj):
global convert_obj
convert_obj = obj
def convert(red: Node, obj=None):
set_convert_obj(obj)
# delete all non convertable functions from redbaron AST
# coding style is akward because of some redbaron bugs...
while True:
f = red.find('def', name=lambda x: x in SKIP_FUNCTIONS or x[:2] == '__' or x[:5] == '_pyha')
if not f:
break
f.parent.remove(f)
# delete functions that were not simulated
# problem was that some transforms may still parse (e.g find all assignments in the design) these and run into trouble as no type info exists
if obj is not None:
for k, v in obj.__dict__.items():
if isinstance(v, PyhaFunc):
if not v.calls:
f = red.find('def', name=lambda x: x == k)
if not f:
continue
logger.warning(f'Not converting function {k}, was not called in simulation!')
f.parent.remove(f)
# run RedBaron based conversions before parsing
transform_preprocessor(red)
transform_resize_arguments(red)
transform_remove_copy(red)
transform_for(red)
if obj is not None:
transform_dynamic_lists(red)
transform_unroll_local_constructor(red)
transform_call(red)
transform_multiple_assignment(red)
transform_lazy_operand(red)
transform_int_cast(red)
if obj is not None:
transform_enum(red)
transform_registers(red)
transform_fixed_indexing_result_to_bool(red)
transform_auto_resize(red)
transform_complex_real_imag(red)
transform_constants(red)
conv = redbaron_node_to_vhdl_node(red, caller=None) # converts all nodes
return conv
#################### FUNCTIONS THAT MODIFY REDBARON AST #############
#####################################################################
#####################################################################
#####################################################################
#####################################################################
#####################################################################
def super_getattr(obj, attr, is_local=False):
for part in attr.split('.'):
if not is_local:
if part == 'self' or part == 'self_next':
continue
if part.find('[') != -1: # is array indexing
try:
index = int(part[part.find('[') + 1: part.find(']')])
except ValueError: # cant convert to int :(
index = 0
part = part[:part.find('[')]
if isinstance(getattr(obj, part), Sfix): # indexing of sfix bits..
return 'FIXED_INDEXING_HACK'
obj = getattr(obj, part)[index] # just take first array element, because the index may be variable
elif part.find(']') != -1:
# this can happen if array index includes '.' so arr.split makes false split. example: self.a[self.b]
continue
else:
try:
obj = getattr(obj, part)
except AttributeError:
# obj might be dynamic array that has transformed indexing eg. a[0] -> a_0
try:
index = int(part[part.find('_') + 1:])
name = part[:part.find('_')]
obj = getattr(obj, name)[index]
except:
raise AttributeError
return obj
def get_object(node):
""" Parse rebaron AtomTrailers node into Python object (taken from ongoing conversion object)
Works for object and local scope """
if len(node) > 1 and (node[0].value == 'self' or node[0].value == 'self_next'):
var_t = super_getattr(convert_obj, str(node))
else:
# get the SOURCE function (where call is going on) from datamodel
def_parent = node.parent
while not isinstance(def_parent, DefNode):
def_parent = def_parent.parent
source_func_name = f'self.{def_parent.name}'
source_func_obj = super_getattr(convert_obj, str(source_func_name))
func_locals = source_func_obj.get_local_types()
class Struct:
def __init__(self, **entries):
self.__dict__.update(entries)
struct = Struct(**func_locals)
var_t = super_getattr(struct, str(node), is_local=True)
return var_t
def transform_unroll_local_constructor(red_node):
assigns = red_node.find_all('assign')
for node in assigns:
call = node.value.call
if call is None: # has no function call
continue
call_index = call.previous.index_on_parent
if call_index != 0: # input is something complicated..we only consider simple calls like 'Complex(..)'
continue
obj = get_object(node.target)
if not isinstance(obj, Hardware):
continue
call_name = str(node.value[0])
res_type = obj.__class__.__name__
if res_type != call_name: # not a call to constructor
continue
correct_indentation = node.indentation
new = 'if True:\n' # replace needs a BLOCK, so this is a dummy IF
target = str(node.target)
for i, (k, v) in enumerate(obj.__dict__.items()):
if k.startswith('_pyha'):
continue
new += f'{correct_indentation}\t{target}.{k} = {call[i].value}\n'
node.replace(new)
def transform_constants(red_node):
nodes = red_node.find_all('atomtrailers')
for node in nodes:
const = any([is_constant(x.dumps()) for x in node])
if const and node[0].dumps() == 'self':
node[0].replace('self_const')
def transform_preprocessor(red_node):
nodes = red_node.find_all('comment', value=lambda x: x == '# CONVERSION PREPROCESSOR replace next line with:')
for x in nodes:
new = str(x.parent[x.index_on_parent + 1].value)[2:]
x.parent[x.index_on_parent + 2].replace(new)
# x.replace(f'{x.target} = {x.target} {x.operator} {x.value}')
def transform_resize_arguments(red_node):
""" Replace 'wrap' -> fixed_wrap
'saturate' -> fixed_saturate
'truncate' -> fixed_truncate
'round' -> fixed_round
Force default fixed_wrap and fixed_truncate.
"""
nodes = red_node.find_all('call')
for call in nodes:
call_index = call.previous.index_on_parent
if call_index != 0: # not just resize().. maybe self.resize() etc...
continue
if call.previous.value not in ['resize', 'Sfix']:
continue
args = call.find_all('call_argument')
overflow_kword_found = False
round_kword_found = False
for arg in args:
if str(arg.value) == "'wrap'":
arg.value = 'fixed_wrap'
arg.target = 'overflow_style'
overflow_kword_found = True
if str(arg.value) == "'saturate'":
arg.value = 'fixed_saturate'
arg.target = 'overflow_style'
overflow_kword_found = True
if str(arg.value) == "'truncate'":
arg.value = 'fixed_truncate'
arg.target = 'round_style'
round_kword_found = True
if str(arg.value) == "'round'":
arg.value = 'fixed_round'
arg.target = 'round_style'
round_kword_found = True
# force defaults if not set
if not overflow_kword_found:
call.append(f'overflow_style=fixed_wrap')
if not round_kword_found:
call.append(f'round_style=fixed_truncate')
def transform_int_cast(red_node):
""" Convert int() to to_integer(round_syle=fixed_truncate, overflow_style=fixed_wrap) for VHDL. """
nodes = red_node.find_all('call')
for call in nodes:
call_index = call.previous.index_on_parent
if call_index != 0: # not just copy().. maybe self.copy() etc...
continue
if call.previous.value == 'int':
call.previous.value = 'to_integer'
call.append('round_style=fixed_truncate')
call.append('overflow_style=fixed_wrap')
def transform_remove_copy(red_node):
""" Remove copy() and deepcopy() calls, in VHDL everything is deepcopy by default
"""
nodes = red_node.find_all('call')
for call in nodes:
call_index = call.previous.index_on_parent
if call_index != 0: # not just copy().. maybe self.copy() etc...
continue
if call.previous.value not in ['copy', 'deepcopy']:
continue
call.parent.replace(call.value.dumps())
def transform_complex_real_imag(red_node):
nodes = red_node.find_all('atomtrailers')
for node in nodes:
if str(node[-1]) == 'real':
del node[-1]
node.replace(f'get_real({node.dumps()})')
elif str(node[-1]) == 'imag':
del node[-1]
node.replace(f'get_imag({node.dumps()})')
def transform_multiple_assignment(red_node):
""" Multi target assigns to single target:
a, b, c = 1, 2, 3 ->
a = 1
b = 2
c = 3
"""
nodes = red_node.find_all('assign', target=lambda x: isinstance(x, TupleNode))
for x in nodes:
for i, (target, value) in enumerate(zip(x.target, x.value)):
if i == len(x.target) - 1:
x.replace(f'{target} = {value}')
else:
x.parent.insert(x.index_on_parent, f'{target} = {value}')
def transform_lazy_operand(red_node):
""" *=, += .. etc:
a *= b ->
a = a * b
"""
nodes = red_node.find_all('assign', operator=lambda x: x != '')
for x in nodes:
x.replace(f'{x.target} = {x.target} {x.operator} {x.value}')
def transform_auto_resize(red_node):
""" Auto resize on Sfix assignments
Examples (depend on initial Sfix type):
self.sfix_reg = a -> self.sfix_reg = resize(a, 5, -29, fixed_wrap, fixed_round)
self.sfix_list[0] = a -> self.sfix_list[0] = resize(a, 0, 0, fixed_saturate, fixed_round)
"""
""" Resize stuff should happen on Sfix registers only, filter others out """
""" Wrap all subjects to autosfix inside resize() according to initial type """
""" When assignment target is sfix indexing ie. sfix[2], converts value to 'to_std_logic(value)' """
nodes = red_node.find_all('assign')
for node in nodes:
var_t = get_object(node.target)
if var_t == 'FIXED_INDEXING_HACK':
node.value = f'to_std_logic({node.value})'
elif isinstance(var_t, (Sfix)):
if isinstance(node.value, (FloatNode, IntNode)) \
or (isinstance(node.value, UnitaryOperatorNode) and isinstance(node.value.target, (
FloatNode, IntNode))): # second term to pass marked(-) nodes, like -1. -0.34 etc
if var_t.signed:
node.value = f'Sfix({node.value}, {var_t.left}, {var_t.right})'
else:
node.value = f'Ufix({node.value}, {var_t.left-1}, {var_t.right})'
else:
if var_t.signed:
node.value = f'resize({node.value}, {var_t.left}, {var_t.right}, fixed_{var_t.overflow_style}, fixed_{var_t.round_style})'
else:
node.value = f'resize({node.value}, {var_t.left-1}, {var_t.right}, fixed_{var_t.overflow_style}, fixed_{var_t.round_style})'
elif isinstance(var_t, (Complex)):
if isinstance(node.value, (BinaryOperatorNode)) \
and (isinstance(node.value.second, BinaryOperatorNode) and isinstance(node.value.second.second, ComplexNode)): # handle mul case: 1+1*1j
node.value = f'Complex({node.value.first}, {node.value.value}{node.value.second.first}, {var_t.left}, {var_t.right})'
elif isinstance(node.value, (BinaryOperatorNode)) and isinstance(node.value.second,
ComplexNode): # normal case: 1+1j
node.value = f'Complex({node.value.first}, {node.value.value}{node.value.second.value[:-1]}, {var_t.left}, {var_t.right})'
elif isinstance(node.value, (FloatNode)):
node.value = f'Complex({node.value}, {var_t.left}, {var_t.right})'
else:
node.value = f'resize({node.value}, {var_t.left}, {var_t.right}, fixed_{var_t.overflow_style}, fixed_{var_t.round_style})'
def transform_fixed_indexing_result_to_bool(red_node):
""" VHDL indexing of fixed point value returns 'std_logic' type, this casts such assignments to bool() """
nodes = red_node.find_all('atomtrailers')
for node in nodes:
try:
if node.parent.target == node: # this node is assignment target ie. NODE = value
continue
except:
pass
try:
var_t = get_object(node)
except AttributeError: # can happen when node is Sfix()
continue
if var_t == 'FIXED_INDEXING_HACK':
node.value = f'bool({node})'
def transform_registers(red_node):
def add_next(x):
if len(x) > 1 and str(x[0].value) == 'self':
x[0].replace('self_next')
assigns = red_node.find_all('assign')
for node in assigns:
if isinstance(node.target, TupleNode):
for mn in node.target:
add_next(mn)
else:
add_next(node.target)
def transform_call(red_node):
"""
Converts Python style function calls to VHDL style:
self.d(a) -> d(self, a)
If function owner is not exactly 'self' then 'type' is prepended.
self.next.moving_average.main(x) -> type.main(self.next.moving_average, x)
self.d(a) -> d(self, a)
self.next.d(a) -> d(self.next, a)
local.d() -> type.d(local)
self.local.d() -> type.d(self.local)
If return then:
b = self.a(arg) ->
variable pyha_ret_0: type;
a(self, arg, pyha_ret_0);
b := pyha_ret_0;
Handling call inside call is limited to depth 1.
"""
def find_line_node(red_obj):
line_node = red_obj
while True:
if type(line_node.next) == EndlNode:
break
if hasattr(line_node.parent, 'value') and type(line_node.parent.value) == LineProxyList:
if not (hasattr(line_node.parent, 'test') and (
line_node.parent.test == atom # if WE are the if condition, skip
or line_node.parent.test == atom.parent)): # if WE are the if condition (part of condition)
break
line_node = line_node.parent
return line_node
is_hack = False
# make sure each created variable is unique by appending this number and incrementing
tmp_var_count = 0
# loop over all atomtrailers, call is always a member of this
atomtrailers = red_node.find_all('atomtrailers')
for i, atom in enumerate(atomtrailers):
if is_hack: # when parsed out of order call
atom = atomtrailers[i - 1]
call = atom.call
is_hack = False
else:
call = atom.call # this actually points to the stuff between ()
if call is None: # this atomtrailer has no function call
continue
wat = call.call
if wat is not None: # one of the arguments is a call -> process it first (i expect it is next in the list)
call_index = wat.previous.index_on_parent
if call_index == 0: # input is something like x() -> len(), Sfix() ....
pass
else:
try:
atom = atomtrailers[i + 1]
call = atom.call
is_hack = True
except:
continue # no idea what is going on here...
if call is None: # this atomtrailer has no function call
continue
call_index = call.previous.index_on_parent
if call_index == 0: # input is something like x() -> len(), Sfix() ....
continue
# get the TARGET function object from datamodel
target_func_name = atom.copy()
del target_func_name[call_index + 1:]
try:
target_func_obj = super_getattr(convert_obj, str(target_func_name))
except: # happend for: (self.conjugate(complex_in) * complex_in).real
continue
if not target_func_obj.calls:
# function is not simulated...
line_node = find_line_node(atom)
line_node.replace(f'# comment out because not called in simulation: {line_node.dumps()}')
continue
prefix = atom.copy()
del prefix[call_index:]
del atom[:call_index]
tmp = prefix.copy()
if isinstance(tmp[0], AtomtrailersNode):
# this branch happens because of 'for transform'
tmp[0][0] = 'self_const'
call.insert(0, tmp)
else:
tmp[0] = 'self_const'
call.insert(0, tmp)
tmp = prefix.copy()
if isinstance(tmp[0], AtomtrailersNode):
tmp[0][0] = 'self_next'
call.insert(0, tmp)
else:
tmp[0] = 'self_next'
call.insert(0, tmp)
tmp = prefix.copy()
if isinstance(tmp[0], AtomtrailersNode):
tmp[0][0] = 'self'
call.insert(0, tmp)
else:
tmp[0] = 'self'
call.insert(0, tmp)
# get the SOURCE (where call is going on) function object from datamodel
def_parent = atom
while not isinstance(def_parent, DefNode):
def_parent = def_parent.parent
# def_parent = atom.parent_find('def')
source_func_name = f'self.{def_parent.name}'
source_func_obj = super_getattr(convert_obj, str(source_func_name))
# if call is not to local class function
# self.moving_average.main(x) -> MODULE_NAME.main(self.moving_average, x)
if str(prefix) != 'self':
var = super_getattr(convert_obj, str(prefix))
var = init_vhdl_type('-', var, var)
atom.insert(0, var._pyha_module_name())
if target_func_obj.get_output_types() is None:
continue # function is not returning stuff -> this is simple
else:
# add return variables to function locals, so that they will be converted to VHDL variables
ret_vars = []
for x in get_iterable(target_func_obj.get_output_types()):
name = f'pyha_ret_{tmp_var_count}'
ret_vars.append(name)
source_func_obj.add_local_type(name, x)
tmp_var_count += 1
# add return variable to arguments
call.append(name)
# call.value[-1].target = f'ret_{j}'
# need to add new source line before the CURRENT line..search for the node with linenodes
line_node = find_line_node(atom)
# add function call BEFORE the CURRENT line
if line_node != atom: # equality means that value is not assigned to anything
line_node.parent.insert(line_node.index_on_parent, atom.copy())
atom.replace(','.join(ret_vars))
def transform_for(red_node):
def modify_for(red_node):
# if for range contains call to 'range' -> skip
with suppress(Exception):
if red_node.target('call')[0].previous.value == 'range':
return
ite = red_node.iterator
red_node(ite.__class__.__name__, value=ite.value) \
.map(lambda x: x.replace(f'{red_node.target}[_i_]'))
red_node.iterator = '_i_'
fors = red_node.find_all('for')
for x in fors:
modify_for(x)
def transform_enum(red_node):
"""
Converts 'EnumType.ENUMVALUE' to integer value , see #154
"""
data = VHDLModule('-', convert_obj)
enums = [x for x in data.elems if isinstance(x, VHDLEnum)]
for x in enums:
type_name = x._pyha_type()
red_names = red_node.find_all('atomtrailers', value=lambda x: x[0].value == type_name)
for i, node in enumerate(red_names):
enum_obj = type(x.current)[str(node[1])]
red_names[i].replace(str(enum_obj.value))
def transform_dynamic_lists(red_node):
data = VHDLModule('-', convert_obj)
dynamic_lists = [x for x in data.elems if isinstance(x, VHDLList) and not x.elements_compatible_typed]
for x in dynamic_lists:
name = x._name
red_names = red_node.find_all('atomtrailers')
for node in red_names:
for i, part in enumerate(node):
if str(part) == name and isinstance(part.next, GetitemNode):
try:
index = int(str(part.next.value))
part.replace(f'{name}_{index}')
del node[i + 1]
except ValueError:
line_node = node
while True:
if type(line_node.next) == EndlNode:
break
if hasattr(line_node.parent, 'value') and type(line_node.parent.value) == LineProxyList:
if not (hasattr(line_node.parent, 'test') and (
line_node.parent.test == node # if WE are the if condition, skip
or line_node.parent.test == node.parent)): # if WE are the if condition (part of condition)
break
line_node = line_node.parent
index = str(part.next.value)
node[i + 1].replace(' ') # del node[i+1], crashes redbaron
correct_indentation = line_node.indentation
new = 'if True:\n' # replace needs a BLOCK, so this is a dummy IF
for i in range(len(x.elems)):
part.replace(f'{name}_{i}')
head = 'if' if i == 0 else 'elif'
new += f'{correct_indentation}\t{head} {index} == {i}:\n{correct_indentation}\t\t{line_node}\n'
line_node.replace(new)