powertrain-build/pybuild/dummy_spm.py
Henrik Wahlqvist 65c1d746a7 Copy from Volvo Cars local project
We don't transfer git history since it may contain proprietary data that
we cannot have in an open sources version.

Change-Id: I9586124c1720db69a76b9390e208e9f0ba3b86d4
2024-05-29 08:03:54 +02:00

253 lines
12 KiB
Python

# 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 pybuild.build_defs as bd
from pybuild import signal
from pybuild.a2l import A2l
from pybuild.problem_logger import ProblemLogger
from pybuild.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 pybuild 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 <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(pybuild.types.byte_size): Return result of pybuild.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)