# Copyright 2024 Volvo Car Corporation # Licensed under Apache 2.0. # -*- coding: utf-8 -*- """Module containing classes for VCC - Supplier debug interface. TODO: Check if all IO parameters in SPMEMSInterfaceRequirements.xlsx defined as safety variables automatically have debug switches. """ import os import re from powertrain_build import build_defs from powertrain_build.types import byte_size_string, get_bitmask, a2l_range from powertrain_build.a2l import A2l from powertrain_build.problem_logger import ProblemLogger class ExtDbg(ProblemLogger): """Class for generating c-files. These declares all debug parameters in the VCC - Supplier interface. """ __data_type_size = {'Float32': '4', 'UInt32': '4', 'Int32': '4', 'UInt16': '2', 'Int16': '2', 'UInt8': '1', 'Int8': '1', 'Bool': '1'} def __init__(self, variable_dict, prj_cfg, unit_cfg, integrity_level=build_defs.CVC_ASIL_QM): """Constructor. Args: variable_dict (dict): dictionary with signal information Variable dict shall have the following format and is generated by the :doc:`CsvSignalInterfaces ` class:: { 'CAN-Input': { 'signal1': { 'IOType': 'd', 'description': 'Some description', 'init': 0, 'max': 1, 'min': 0, 'type': 'UInt8', 'unit': '-' }, 'signal2': { ... } }, 'CAN-Output': { 'signal3': { ... } }, 'xxx-Input': ..., 'xxx-Output': ... } The optional keyword arguments may override which code section directives to use. """ super().__init__() self.set_integrity_level(integrity_level) self._prj_cfg = prj_cfg self._unit_cfg = unit_cfg self._use_volatile_globals = prj_cfg.get_use_volatile_globals() self.dbg_dict = self._restruct_data(variable_dict) @staticmethod def _restruct_data(variable_dict): """Restructure input variables per data-type. This will be used for declaring the variables and generating the A2L-file. """ data = {'outputs': {}, 'inputs': {}} for inp in variable_dict.keys(): if re.match(r'.*Output$', inp) is not None: iotype = 'outputs' else: iotype = 'inputs' for var, var_data in variable_dict[inp].items(): data_type_size = byte_size_string(var_data['type']) data[iotype].setdefault(data_type_size, {})[var] = var_data return data def set_integrity_level(self, integrity_level): """Set integrity level of code generation. Args: integrity_level (str): integrity level of the unit from 'A' to 'D' or 'QM' """ self._disp_start = integrity_level['DISP']['START'] self._disp_end = integrity_level['DISP']['END'] self._cal_start = integrity_level['CAL']['START'] self._cal_end = integrity_level['CAL']['END'] self._code_start = integrity_level['CODE']['START'] self._code_end = integrity_level['CODE']['END'] def _a2l_dict(self, var_dict, function): """Generate dict defining parameters for a2l-generation.""" def _range(data): range_a2l = a2l_range(data['type']) if data['min'] == '-': a2l_min = range_a2l[0] else: a2l_min = data['min'] if data['max'] == '-': a2l_max = range_a2l[1] else: a2l_max = data['max'] return a2l_min, a2l_max res = {'vars': {}, 'function': function} for var, data in self._type_order_iterator(var_dict): resv = res['vars'] a2l_min, a2l_max = _range(data) a2l_data = { 'bitmask': get_bitmask(data['type']), 'description': data['description'], 'lsb': '2^0', 'max': a2l_max, 'min': a2l_min, 'offset': '0', 'unit': '', 'x_axis': None, 'y_axis': None } var_db = f'c{var[1:]}_db' var_sw = self._var_name_to_dbgsw_name(var) resv.setdefault(var_db, {})['a2l_data'] = a2l_data a2l_data = { 'bitmask': get_bitmask(data['type']), 'description': f'debug switch for {var_db} (1=bdsw act)', 'lsb': '2^0', 'max': '1', 'min': '0', 'offset': '0', 'unit': '', 'x_axis': None, 'y_axis': None } resv.setdefault(var_sw, {})['a2l_data'] = a2l_data resv[var_db]['array'] = [] resv[var_sw]['array'] = [] resv[var_db]['function'] = [function] resv[var_sw]['function'] = [function] resv[var_db]['var'] = {'cvc_type': 'CVC_CAL', 'type': data['type'], 'var': var} resv[var_sw]['var'] = {'cvc_type': 'CVC_CAL', 'type': 'Bool', 'var': var} return res @staticmethod def _var_name_to_dbgsw_name(name): """Convert a variable name to a debug switch name.""" # the below conversion would generate a correct name for the # debug switch, however the current build systemt generates one # like the currently returned name # return re.sub(r'\w(\w+?)_\w+?_(\w+)', r'c\1_B_\2_sw', name) return re.sub(r'\w(\w+)', r'c\1_sw', name) @staticmethod def _type_order_iterator(var_items): """Get iterator over all variables. In data type size order, and then in alphabetical order. """ for _, typ_data in var_items.items(): for var in sorted(typ_data.keys()): yield (var, typ_data[var]) def _gen_dbg_c_file(self, data, filename): """Generate debug c-files. These define all the debug labels for the supplier input and output signals. """ with open(filename, 'w', encoding="utf-8") as fh_c: fh_c.write(self._unit_cfg.base_types_headers) fh_c.write('#define CVC_DISP\n') # define extrern variable references fh_c.write(f'#include "{self._disp_start}"\n') for var, var_data in self._type_order_iterator(data): fh_c.write(f"extern CVC_DISP {var_data['type']} {var};\n") fh_c.write(f'#include "{self._disp_end}"\n\n') # define debug calibration constants fh_c.write(f'#include "{self._cal_start}"\n') fh_c.write('\n/* Debug values */\n\n') for var, var_data in self._type_order_iterator(data): initial_value = var_data['min'] if var_data['min'] != "-" and float(var_data['min']) > 0 else "0" if self._use_volatile_globals: fh_c.write(f"volatile {var_data['type']} c{var[1:]}_db = {initial_value};\n") else: fh_c.write(f"{var_data['type']} c{var[1:]}_db = {initial_value};\n") fh_c.write('\n/* Debug switches */\n\n') for var, var_data in self._type_order_iterator(data): sw_name = self._var_name_to_dbgsw_name(var) if self._use_volatile_globals: fh_c.write(f"volatile Bool {sw_name} = 0;\n") else: fh_c.write(f"Bool {sw_name} = 0;\n") fh_c.write(f'#include "{self._cal_end}"\n\n') # set the variable to the debug calibration constants fh_c.write('/***********************/\n') fh_c.write('/* debug functionality */\n') fh_c.write('/***********************/\n\n') _, fname_tmp = os.path.split(filename) func_name = fname_tmp.split('.')[-2] fh_c.write(f'#include "{self._code_start}"\n') fh_c.write(f'void {func_name}(void) {{\n') for var, var_data in self._type_order_iterator(data): sw_name = self._var_name_to_dbgsw_name(var) fh_c.write(f' if ({sw_name}) {{\n') fh_c.write(f' {var} = c{var[1:]}_db;\n }}\n') fh_c.write(f'}}\n#include "{self._code_end}"\n\n') self.info('Generated %s', filename) def gen_dbg_files(self, file_path_inputs, file_path_outputs): """Generate the c-files and A2L-files. These declares all the supplier interface debug parameters and functions. Args: file_path_inputs (str): path to the debug inputs c-file. file_path_outputs (str): path to the debug outputs c-file. """ _, file_name_inputs = os.path.split(file_path_inputs) _, file_name_outputs = os.path.split(file_path_outputs) if not self.dbg_dict['inputs']: self.info(f"Skipping {file_name_inputs} as there were no corresponding vars.") else: self._gen_dbg_c_file(self.dbg_dict['inputs'], file_path_inputs + '.c') a2l_dict_in = self._a2l_dict(self.dbg_dict['inputs'], file_name_inputs) a2l = A2l(a2l_dict_in, self._prj_cfg) a2l.gen_a2l(file_path_inputs + '.a2l') if not self.dbg_dict['outputs']: self.info(f"Skipping {file_name_outputs} as there were no corresponding vars.") else: self._gen_dbg_c_file(self.dbg_dict['outputs'], file_path_outputs + '.c') a2l_dict_out = self._a2l_dict(self.dbg_dict['outputs'], file_name_outputs) a2l = A2l(a2l_dict_out, self._prj_cfg) a2l.gen_a2l(file_path_outputs + '.a2l')