65c1d746a7
We don't transfer git history since it may contain proprietary data that we cannot have in an open sources version. Change-Id: I9586124c1720db69a76b9390e208e9f0ba3b86d4
253 lines
12 KiB
Python
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)
|