# Copyright 2024 Volvo Car Corporation # Licensed under Apache 2.0. # -*- coding: utf-8 -*- """Module containing classes for model out-port interfaces.""" import os from operator import itemgetter import math import powertrain_build.build_defs as bd from powertrain_build import signal from powertrain_build.a2l import A2l from powertrain_build.problem_logger import ProblemLogger from powertrain_build.types import byte_size, get_bitmask class DummySpm(ProblemLogger): """Generate c-files which defines missing outport variables in the model out-port interface. The models declare all in-ports as 'external' and powertrain-build will then generate any missing outports in the correct #if/#endif guards here. One c-file per outport origin model should be generated. * Generate c-code from matlab/TL script with #if/#endif guards. - Pro: Is generated at the same time as other code and placed in model src folder. - Pro: Generic code. Will be cached together with TL-generated code. - Con: m-script * Generate c-code from python with only needed variables. No preprocessor directives. - Pro: Python - Pro: Simpler c-file with only used variables. - Con: Not generic! Not cached? """ __asil_level_map = { 'A': (bd.CVC_DISP_ASIL_A_START, bd.CVC_DISP_ASIL_A_END), 'B': (bd.CVC_DISP_ASIL_B_START, bd.CVC_DISP_ASIL_B_END), 'C': (bd.CVC_DISP_ASIL_C_START, bd.CVC_DISP_ASIL_C_END), 'D': (bd.CVC_DISP_ASIL_D_START, bd.CVC_DISP_ASIL_D_END), 'QM': (bd.CVC_DISP_START, bd.CVC_DISP_END)} def __init__(self, missing_outports, prj_cfg, feature_cfg, unit_cfg, user_defined_types, basename): """Constructor. Args: missing_outports (list): undefined outports based on unit config variables. prj_cfg (BuildProjConfig): Build project class holding where files should be stored. feature_cfg (FeatureConfig): Feature configs from SPM_Codeswitch_Setup. unit_cfg (UnitConfigs): Class holding all unit interfaces. user_defined_types (UserDefinedTypes): Class holding user defined data types. basename (str): the basename of the outvar, used for .c and .a2l creation. See :doc:`Unit config ` for information on the 'outport' dict. """ super().__init__() self._prj_cfg = prj_cfg self._feature_cfg = feature_cfg self._unit_cfg = unit_cfg self._enumerations = user_defined_types.get_enumerations() self._common_header_files = user_defined_types.common_header_files self._name = basename self.use_volatile_globals = prj_cfg.get_use_volatile_globals() self._missing_outports = self._restruct_input_data(missing_outports) def _get_byte_size(self, data_type): """Get byte size of a data type. Enumeration byte sizes are derived from the underlying data type. Args: data_type (str): Data type. Returns: byte_size(powertrain_build.types.byte_size): Return result of powertrain_build.types.byte_size. """ if data_type in self._enumerations: return byte_size(self._enumerations[data_type]['underlying_data_type']) return byte_size(data_type) def _restruct_input_data(self, outports): """Restructure all the input variables per data-type.""" outports.sort(key=itemgetter('name')) outports.sort(key=lambda var: self._get_byte_size(var['type']), reverse=True) new_outports = {} for outport in outports: integrity_level = outport.get('integrity_level', 'QM') if integrity_level == 'QM': outport['cvc_type'] = 'CVC_DISP' else: outport['cvc_type'] = f'ASIL_{integrity_level}/CVC_DISP_ASIL_{integrity_level}' if integrity_level in new_outports: new_outports[integrity_level].append(outport) else: new_outports.update({integrity_level: [outport]}) return new_outports def _a2l_dict(self, outports): """Return a dict defining all parameters for a2l-generation.""" res = { 'vars': {}, 'function': 'VcDummy_spm' } for outport in [port for sublist in outports.values() for port in sublist]: var = outport['name'] if outport['type'] in self._enumerations: data_type = self._enumerations[outport['type']]['underlying_data_type'] else: data_type = outport['type'] resv = res['vars'] resv.setdefault(var, {})['a2l_data'] = { 'bitmask': get_bitmask(data_type), 'description': outport.get('description', ''), 'lsb': '2^{}'.format(int(math.log2(outport.get('lsb', 1))) if outport.get('lsb') not in ['-', ''] else 0), 'max': outport.get('max'), 'min': outport.get('min'), 'offset': -(outport.get('offset', 0) if outport.get('offset') not in ['-', ''] else 0), 'unit': outport['unit'], 'x_axis': None, 'y_axis': None} resv[var]['function'] = ['VcEc'] resv[var]['var'] = {'cvc_type': outport['cvc_type'], 'type': data_type, 'var': var} resv[var]['array'] = outport.get('width', 1) res.update({'vars': resv}) return res def generate_code_files(self, dst_dir): """Generate code and header files. Args: dst_dir (str): Path to destination directory. """ h_file_path = os.path.join(dst_dir, f'{self._name}.h') self._generate_h_file(h_file_path, self._missing_outports) c_file_path = os.path.join(dst_dir, f'{self._name}.c') self._generate_c_file(c_file_path, self._missing_outports) def generate_a2l_files(self, dst_dir): """Generate A2L files. Args: dst_dir (str): Path to destination directory. """ filename = f"{os.path.join(dst_dir, self._name)}.a2l" a2l_dict = self._a2l_dict(self._missing_outports) a2l = A2l(a2l_dict, self._prj_cfg) a2l.gen_a2l(filename) def _generate_h_file(self, file_path, outports): """Generate header file. Args: file_path (str): File path to generate. """ file_name = os.path.basename(file_path).split('.')[0] with open(file_path, 'w', encoding="utf-8") as fh: fh.write(f'#ifndef {file_name.upper()}_H\n') fh.write(f'#define {file_name.upper()}_H\n') fh.write(self._unit_cfg.base_types_headers) fh.write('#include "VcCodeSwDefines.h"\n') for common_header_file in self._common_header_files: fh.write(f'#include "{common_header_file}"\n') for integrity_level in outports.keys(): disp_start = bd.PREDECL_ASIL_LEVEL_MAP[integrity_level]['DISP']['START'] disp_end = bd.PREDECL_ASIL_LEVEL_MAP[integrity_level]['DISP']['END'] fh.write('\n') fh.write(f'#include "{disp_start}"\n') for outport in outports[integrity_level]: if outport['class'] not in signal.INPORT_CLASSES: self.warning(f'inport {outport["name"]} class {outport["class"]} is not an inport class') array = '' width = outport['width'] if not isinstance(width, list): width = [width] if len(width) != 1 or width[0] != 1: for w in width: if w > 1: if not isinstance(w, int): self.critical(f'{outport["name"]} widths must be integers. Got "{type(w)}"') array += f'[{w}]' elif w < 0: self.critical(f'{outport["name"]} widths can not be negative. Got "{w}"') if self.use_volatile_globals: fh.write(f"extern volatile {outport['type']} {outport['name']}{array};\n") else: fh.write(f"extern {outport['type']} {outport['name']}{array};\n") fh.write(f'#include "{disp_end}"\n') fh.write(f'#endif /* {file_name.upper()}_H */\n') def _generate_c_file(self, file_path, outports): """Generate C-file for inports that are missing outports except for supplier ports.""" file_name = os.path.basename(file_path).split('.')[0] base_header = f'#include "{file_name}.h"\n' with open(file_path, 'w', encoding="utf-8") as fh_c: fh_c.write(base_header) for integrity_level in outports.keys(): disp_start = bd.CVC_ASIL_LEVEL_MAP[integrity_level]['DISP']['START'] disp_end = bd.CVC_ASIL_LEVEL_MAP[integrity_level]['DISP']['END'] fh_c.write('\n') fh_c.write(f'#include "{disp_start}"\n') for outport in outports[integrity_level]: if outport['class'] not in signal.INPORT_CLASSES: self.warning(f'inport {outport["name"]} class {outport["class"]} is not an inport class') width = outport['width'] if outport['type'] in self._enumerations: if self._enumerations[outport['type']]['default_value'] is not None: init_value = self._enumerations[outport['type']]['default_value'] else: self.warning('Initializing enumeration %s to "zero".', outport['type']) init_value = [ k for k, v in self._enumerations[outport['type']]['members'].items() if v == 0 ][0] if width != 1: self.critical(f'{outport["name"]} enumeration width must be 1. Got "{width}"') fh_c.write(f"{outport['type']} {outport['name']} = {init_value};\n") else: if not isinstance(width, list): width = [width] if len(width) == 1 and width[0] == 1: array = ' = 0' else: array = '' for w in width: if w > 1: if not isinstance(w, int): msg = f'{outport["name"]} widths must be integers. Got "{type(w)}"' self.critical(msg) array += f'[{w}]' elif w < 0: self.critical(f'{outport["name"]} widths can not be negative. Got "{w}"') if self.use_volatile_globals: fh_c.write(f'volatile {outport["type"]} {outport["name"]}{array};\n') else: fh_c.write(f'{outport["type"]} {outport["name"]}{array};\n') fh_c.write(f'#include "{disp_end}"\n') def generate_files(self, dst_dir): """Generate the files for defining all missing input variables.""" self.generate_code_files(dst_dir) self.generate_a2l_files(dst_dir)