Try using data type from Simulink class for enumerations

Change-Id: I569d06f93faabcd96987b71140464d5149068262
This commit is contained in:
Henrik Wahlqvist 2025-04-02 16:34:12 +02:00
parent 6faabe161d
commit 475c4aaa47
4 changed files with 196 additions and 42 deletions

View File

@ -15,6 +15,23 @@ This makes them easy to find and keep unique.
Unique enumeration definitions are required.
The name of the enumeration must match the name of the file.
### Defining the Underlying Data Type
As of powertrain-build version 1.5.0, it is possible to specify the underlying data type.
There was already a concern regarding the underlying data type in different compilers,
for more informatin see [Pitfalls](#pitfalls).
The new version changes the default behaviour from calculating the smallest possible data type to
using the one specified in the Simulink class definition file.
This is done by parsing the `classdef SomeEnumName < Simulink.IntEnumType` line,
at the top of the Simulink enumeration definition file.
If calculating the underlying data type is desired,
one must add a new class function called _calculateUnderlyingDataType_ and set the return value to _true_.
### Simulink Enumeration Template
Matlab enumeration template:
```matlab
@ -40,6 +57,9 @@ classdef SomeEnumName < Simulink.IntEnumType
function retVal = addClassNameToEnumNames()
retVal = true;
end
function retVal = calculateUnderlyingDataType()
retVal = false;
end
end
end
```
@ -353,6 +373,12 @@ The path is set with the key "enumDefDir" in the _ProjectCfg.json_ file.
## Pitfalls
Calculating the underlying data type turned out to be a problem for some compilers,
hence the default behaviour was changed,
see [Defining the Underlying Data Type](#defining-the-underlying-data-type) for more information.
### History
When TargetLink generates the A2L-file, it must know the underlying data type to use.
The underlying data type is calculated based on the number of enumeration members and their values.
When powertrain-build parses the enumeration it does not know about the underlying data type.
@ -363,12 +389,6 @@ given an enumeration. The smallest underlying data type which can be chosen is a
Some compilers may perform optimization using only one byte. If the software compiles enumerations using one byte,
while the A2L-files states they are two bytes, what will happen when calibrating the software?
What is the underlying data type of enumerations entering an app?
CS software homepage (based on ARXML from a database?) seems to specify signed 8 bit integers.
Can this cause issues?
## Summary
* If one of the functions for calculating the underlying data type is changed, the other must be changed too.
* Potential software calibration issues, A2L versus compile sofware enumeration sizes.
* Actual data types of enumerations entering an app?

View File

@ -18,18 +18,37 @@ function Generate_A2L(CodegenFunction, A2lStylesheetName, model_ending)
ApplicationName = [CodegenFunction model_ending];
BuildName = ['Build' CodegenFunction model_ending];
dsdd_manage_build('Create',...
'Name', BuildName,...
'Application', ApplicationName,...
'TargetInfoDir', TargetInfoDir,...
'TargetConfigDir', TargetConfigDir);
simulinkToTargetLinkDataTypes = struct(...
'int', 'Int32',...
'int32', 'Int32',...
'uint16', 'UInt16',...
'int16', 'Int16',...
'uint8', 'UInt8',...
'int8', 'Int8'...
);
enumDataTypesObj = dsdd('CreateEnumDataTypes', ['/' ApplicationName '/' BuildName]);
enumNames = GetUsedEnumerations(CodegenFunction);
for idx = 1:length(enumNames)
enumName = enumNames{idx};
underlyingDataType = CalculateUnderlyingDataType(CodegenFunction, enumName);
enumMemberNames = enumeration(enumName);
if isempty(enumMemberNames)
error([...
'Cannot get enum name %s from model %s.',...
'Enum name should match the unit in applicable blocks, e.g. -,$EnumName.'...
], enumName, modelName);
end
if ismember('calculateUnderlyingDataType', methods(enumName)) && enumMemberNames(1).calculateUnderlyingDataType()
underlyingDataType = CalculateUnderlyingDataType(enumName, enumMemberNames);
else
simulinkDataType = Simulink.data.getEnumTypeInfo(enumName, 'StorageType');
underlyingDataType = simulinkToTargetLinkDataTypes.(simulinkDataType);
end
[hDDTypedef, ~, ~] = tlEnumDataType('CreateDDTypedef',...
'SLEnumType', enumName,...
'TypedefGroup', ['/' ApplicationName '/' BuildName '/EnumDataTypes'],...
@ -56,16 +75,32 @@ function Generate_A2L(CodegenFunction, A2lStylesheetName, model_ending)
% Delete created enums, these can cause problems for upcoming code generation calls on models
% using the same enumerations.
dsdd('Delete', enumDataTypesObj);
fprintf('\n\nGeneration of %s finished\n\n', [CodegenFunction '.a2l']);
end
function enumerations = GetUsedEnumerations(modelName)
function usedEnumerations = GetUsedEnumerations(modelName)
%GETUSEDENUMERATIONS Get all enumeration names used in a given model.
unsupportedByTLAPI = {'TL_DummyTrigger', 'TL_Enable', 'TL_Function', 'TL_SimFrame'};
tlBlocksTmp = find_system(modelName, 'FindAll', 'on', 'LookUnderMasks', 'All', 'RegExp', 'on', 'MaskType', 'TL_*');
maskTypesTmp = get_param(tlBlocksTmp, 'MaskType');
supportedTlBlocks = tlBlocksTmp(ismember(maskTypesTmp, unsupportedByTLAPI)==0);
supportedTlBlocksTmp = tlBlocksTmp(ismember(get_param(tlBlocksTmp, 'MaskType'), unsupportedByTLAPI)==0);
% Get enumerations from TL_BusInport blocks
busEnumerations = {};
busInportBlocks = supportedTlBlocksTmp(strcmp(get_param(supportedTlBlocksTmp, 'MaskType'), 'TL_BusInport'));
for idx = 1:length(busInportBlocks)
for idy = 1:tl_get(busInportBlocks(idx), 'numoutputs')
dataType = tl_get(busInportBlocks(idx), ['output(' num2str(idy) ').type']);
unitTmp = tl_get(busInportBlocks(idx), ['output(' num2str(idy) ').unit']);
if isempty(unitTmp)
continue
end
unit = erase(unitTmp, ['-', ',', '$']);
if strcmp(dataType, 'IMPLICIT_ENUM') && ~isempty(unit) && ~ismember(unit, busEnumerations)
busEnumerations{end+1} = unit;
end
end
end
% Get enumerations from standard TL blocks
supportedTlBlocks = supportedTlBlocksTmp(~strcmp(get_param(supportedTlBlocksTmp, 'MaskType'), 'TL_BusInport'));
[allDataTypes, invalidIndices, ~] = tl_get(supportedTlBlocks, 'output.type');
validTlBlocks = supportedTlBlocks(~invalidIndices);
validDataTypes = allDataTypes(~invalidIndices);
@ -80,18 +115,12 @@ function enumerations = GetUsedEnumerations(modelName)
else
enumerations = {};
end
usedEnumerations = unique([busEnumerations enumerations]);
end
function underlyingDataType = CalculateUnderlyingDataType(modelName, enumName)
function underlyingDataType = CalculateUnderlyingDataType(enumName, enumMemberNames)
%CALCULATEUNDERLYINGDATATYPE Calculate best fitting data type given a name of an enumeration.
% This is done by find min and max values, than choosing a fitting data type, as small as possible.
enumMemberNames = enumeration(enumName);
if isempty(enumMemberNames)
error([...
'Cannot get enum name %s from model %s.',...
'Enum name should match the unit in applicable blocks, e.g. -,$EnumName.'...
], enumName, modelName);
end
% int32 is super class of Simulink.IntEnumType
enumMemberValues = int32(enumMemberNames);
minValue = min(enumMemberValues);

View File

@ -32,6 +32,24 @@ class UserDefinedTypes(ProblemLogger):
'i8': 'int8_T'
}
}
SIMULINK_DATA_TYPES = { # Add more as needed
'target_link': {
'Simulink.IntEnumType': 'Int32',
'int32': 'Int32',
'uint16': 'UInt16',
'int16': 'Int16',
'uint8': 'UInt8',
'int8': 'Int8'
},
'embedded_coder': {
'Simulink.IntEnumType': 'int32_T',
'int32': 'int32_T',
'uint16': 'uint16_T',
'int16': 'int16_T',
'uint8': 'uint8_T',
'int8': 'int8_T'
}
}
def __init__(self, build_prj_config, unit_configs):
"""Class Initialization.
@ -302,7 +320,11 @@ class UserDefinedTypes(ProblemLogger):
for enum_name, enum_content in enums:
enum_members_tmp = re.findall(r'\s*(\w+) = ([-+]?\d+)', enum_content, flags=re.M)
enum_members = [(k, int(v)) for k, v in enum_members_tmp]
underlying_data_type = self._calculate_underlying_data_type(unit, enum_name, enum_members)
if enum_name in self.common_enums:
underlying_data_type = self.common_enums[enum_name]['underlying_data_type']
else:
self.warning('Calculating underlying data type for: %s', str(enum_name))
underlying_data_type = self._calculate_underlying_data_type(unit, enum_name, enum_members)
enum_dict = {
'underlying_data_type': underlying_data_type,
'members': {},
@ -375,7 +397,6 @@ class UserDefinedTypes(ProblemLogger):
Returns:
common_enums (dict): Dictionary containing all enums defined in the enum definition directory.
"""
common_enums = {}
if self._build_prj_cfg.get_enum_def_dir() is None:
@ -389,9 +410,10 @@ class UserDefinedTypes(ProblemLogger):
with enum_file.open(mode='r', encoding='ISO-8859-1') as fh:
enum_file_content = fh.read()
enum_name = enum_file.stem
members_part = re.search(
r'\s+enumeration\n'
r'(\s*\w+\(\d+\)(\s*%.*)?\n|\s*(%.*)?\n)+'
r'\s+enumeration\s*\n'
r'(\s*%.*|\s*\w+\s*\(\d+\)\s*(%.*)?\n)+'
r'\s+end',
enum_file_content,
flags=re.MULTILINE
@ -399,8 +421,10 @@ class UserDefinedTypes(ProblemLogger):
if not members_part:
self.warning('Cannot extract enumeration members from %s', str(enum_file))
continue
members = re.findall(r'(\w+)\((\d+)\)', members_part.group(0))
members = re.findall(r'(\w+)\s*\((\d+)\)', members_part.group(0))
member_dict = {enum_name + '_' + member[0]: int(member[1]) for member in members}
enum_members = [(member_name, member_value) for member_name, member_value in member_dict.items()]
get_default_value = re.search(
r'function ([A-Za-z0-9]+) = getDefaultValue\(\)[\s\n]*'
r'\1 = [A-Za-z0-9]+\.([A-Za-z0-9]+);[\s\n]*'
@ -410,16 +434,38 @@ class UserDefinedTypes(ProblemLogger):
)
if get_default_value is None:
self.warning('Cannot extract default enumeration values in: %s', str(enum_file))
return None
continue
default_value = get_default_value.groups()[1]
default_member = f'{enum_name}_{default_value}'
enum_members = [(member_name, member_value) for member_name, member_value in member_dict.items()]
underlying_data_type = self._calculate_underlying_data_type('target_link', enum_name, enum_members)
calculate_underlying_data_type = re.search(
r'function ([A-Za-z0-9]+) = calculateUnderlyingDataType\(\)[\s\n]*'
r'\1 = (true|false);[\s\n]*'
r'end',
enum_file_content,
flags=re.MULTILINE
)
get_storage_type = re.search(
r'classdef[\s]+[A-Za-z0-9]+[\s]*<[\s]*([A-Za-z0-9\.]+)[\s\n]*',
enum_file_content,
flags=re.MULTILINE
)
if get_storage_type is None:
self.warning('Cannot extract storage type in: %s', str(enum_file))
if calculate_underlying_data_type is None or calculate_underlying_data_type.groups()[1] == 'false':
self.warning('Calculating underlying data type for: %s', str(enum_file))
underlying_data_type = self._calculate_underlying_data_type('target_link', enum_name, enum_members)
else:
underlying_data_type = self._calculate_underlying_data_type('target_link', enum_name, enum_members)
else:
underlying_data_type = self.SIMULINK_DATA_TYPES['target_link'][get_storage_type.groups()[0]]
common_enums[enum_name] = {
"underlying_data_type": underlying_data_type,
"default_value": default_member,
"members": member_dict
}
return common_enums
def _get_enumerations(self):

View File

@ -167,7 +167,7 @@ class TestUserDefinedTypes(unittest.TestCase):
"""Set-up common data structures for all tests in the test case."""
self.build_cfg = MagicMock(spec_set=BuildProjConfig)
self.build_cfg.get_root_dir = MagicMock(return_value='.')
self.build_cfg.get_enum_def_dir = MagicMock(return_value='../../reference_files/enums/')
self.build_cfg.get_enum_def_dir = MagicMock(return_value=str(Path(REF_DIR, 'enums')))
self.build_cfg.get_code_generation_config = MagicMock(return_value=False)
self.unit_cfg = MagicMock(spec_set=UnitConfigs)
@ -191,7 +191,7 @@ class TestUserDefinedTypes(unittest.TestCase):
expected_enums = {
'VcTestModel': {
'EnumTest': {
'underlying_data_type': 'UInt8',
'underlying_data_type': 'Int32',
'members': {
'ENUMTEST_CLIMAOFF': 0,
'ENUMTEST_CLIMAHEATGTOHVACANDHVBATT': 1,
@ -229,7 +229,7 @@ class TestUserDefinedTypes(unittest.TestCase):
},
'VcTestModel1': {
'EnumTest': {
'underlying_data_type': 'UInt8',
'underlying_data_type': 'Int32',
'members': {
'ENUMTEST_CLIMAOFF': 0,
'ENUMTEST_CLIMAHEATGTOHVACANDHVBATT': 1,
@ -269,7 +269,7 @@ class TestUserDefinedTypes(unittest.TestCase):
}
self.assertDictEqual(expected_structs, udt.structs_per_unit)
self.assertDictEqual(expected_enums, udt.enums_per_unit)
self.assertDictEqual({'critical': 0, 'warning': 6}, udt.get_nbr_problems())
self.assertDictEqual({'critical': 0, 'warning': 9}, udt.get_nbr_problems())
def test_init_interface_enums(self):
"""Test initialization with interface enumerations."""
@ -290,7 +290,7 @@ class TestUserDefinedTypes(unittest.TestCase):
}
)
udt = UserDefinedTypes(self.build_cfg, self.unit_cfg)
self.assertDictEqual({'critical': 0, 'warning': 0}, udt.get_nbr_problems())
self.assertDictEqual({'critical': 0, 'warning': 2}, udt.get_nbr_problems())
result = udt.get_interface_data_types()
self.assertDictEqual({'enums': INTERFACE_ENUM}, result)
udt.clear_log()
@ -337,7 +337,7 @@ class TestUserDefinedTypes(unittest.TestCase):
expected_enums = {
'VcTestModel2': {
'EnumTest': {
'underlying_data_type': 'UInt8',
'underlying_data_type': 'Int32',
'members': {
'ENUMTEST_CLIMAOFF': 0,
'ENUMTEST_CLIMAHEATGTOHVACANDHVBATT': 1,
@ -369,7 +369,7 @@ class TestUserDefinedTypes(unittest.TestCase):
}
self.assertDictEqual(expected_enums, udt.enums_per_unit)
self.assertDictEqual(expected_structs, udt.structs_per_unit)
self.assertDictEqual({'critical': 2, 'warning': 4}, udt.get_nbr_problems())
self.assertDictEqual({'critical': 2, 'warning': 5}, udt.get_nbr_problems())
def test_init_multiply_defined_in_project(self):
"""Test initialization where two units define the same enumeration/struct differently.
@ -386,7 +386,7 @@ class TestUserDefinedTypes(unittest.TestCase):
expected_enums = {
'VcTestModel': {
'EnumTest': {
'underlying_data_type': 'UInt8',
'underlying_data_type': 'Int32',
'members': {
'ENUMTEST_CLIMAOFF': 0,
'ENUMTEST_CLIMAHEATGTOHVACANDHVBATT': 1,
@ -424,7 +424,7 @@ class TestUserDefinedTypes(unittest.TestCase):
},
'VcTestModel2': {
'EnumTest': {
'underlying_data_type': 'UInt8',
'underlying_data_type': 'Int32',
'members': {
'ENUMTEST_CLIMAOFF': 0,
'ENUMTEST_CLIMAHEATGTOHVACANDHVBATT': 1,
@ -480,7 +480,7 @@ class TestUserDefinedTypes(unittest.TestCase):
}
self.assertDictEqual(expected_structs, udt.structs_per_unit)
self.assertDictEqual(expected_enums, udt.enums_per_unit)
self.assertDictEqual({'critical': 4, 'warning': 7}, udt.get_nbr_problems())
self.assertDictEqual({'critical': 4, 'warning': 10}, udt.get_nbr_problems())
def test_calculate_underlying_data_type(self):
"""Test the _calculate_underlying_data_type method.
@ -897,7 +897,7 @@ class TestUserDefinedTypes(unittest.TestCase):
udt = UserDefinedTypes(self.build_cfg, self.unit_cfg)
expected = {
'EnumTest': {
'underlying_data_type': 'UInt8',
'underlying_data_type': 'Int32',
'members': {
'ENUMTEST_CLIMAOFF': 0,
'ENUMTEST_CLIMAHEATGTOHVACANDHVBATT': 1,
@ -937,7 +937,66 @@ class TestUserDefinedTypes(unittest.TestCase):
}
}
self.assertDictEqual(expected, udt.get_enumerations())
self.assertDictEqual({'critical': 4, 'warning': 7}, udt.get_nbr_problems())
self.assertDictEqual({'critical': 4, 'warning': 10}, udt.get_nbr_problems())
def test_get_enumerations_more_commons(self):
"""Test the get_enumerations method where two of the enums have an underlying data type."""
self.build_cfg.get_unit_src_dirs = MagicMock(
return_value={
'VcTestModel': str(Path(REF_DIR, 'SS1', 'test_model')),
'VcTestModel2': str(Path(REF_DIR, 'SS1', 'test_model2'))
}
)
common_enums = {
'EnumTest': {'underlying_data_type': 'Int8'},
'EnumTestTwo': {'underlying_data_type': 'UInt16'}
}
with patch.object(UserDefinedTypes, '_read_enums_from_definitions', return_value=common_enums):
udt = UserDefinedTypes(self.build_cfg, self.unit_cfg)
expected = {
'EnumTest': {
'underlying_data_type': 'Int8',
'members': {
'ENUMTEST_CLIMAOFF': 0,
'ENUMTEST_CLIMAHEATGTOHVACANDHVBATT': 1,
'ENUMTEST_CLIMAHEATGTOHVBATT': 2,
'ENUMTEST_CLIMAHEATGTOHVAC': 3,
'ENUMTEST_CLIMAFLOW': 4,
'ENUMTEST_DEGAS': 5,
'ENUMTEST_FAILSAFE': 6
},
'default_value': None,
'units': ['VcTestModel', 'VcTestModel2']
},
'EnumTestTwo': {
'underlying_data_type': 'UInt16',
'members': {
'ENUMTESTTWO_COOLGREQ': 0,
'ENUMTESTTWO_HEATGREQ': 1
},
'default_value': None,
'units': ['VcTestModel']
},
'EnumTestThree': {
'underlying_data_type': 'UInt8',
'members': {
'ENUMTESTTHREE_DTELECCOOLG': 0,
'ENUMTESTTHREE_DTELECPASCOOLG': 1,
'ENUMTESTTHREE_DTELECHEATG': 2,
'ENUMTESTTHREE_DTELECPASHEATG': 3,
'ENUMTESTTHREE_DTELECACTVHEATG': 4,
'ENUMTESTTHREE_DTELECPASACTVHEATG': 5,
'ENUMTESTTHREE_DTELECPAS': 6,
'ENUMTESTTHREE_DEGAS': 7,
'ENUMTESTTHREE_FAILSAFE': 8
},
'default_value': None,
'units': ['VcTestModel', 'VcTestModel2']
}
}
self.assertDictEqual(expected, udt.get_enumerations())
self.assertDictEqual({'critical': 4, 'warning': 9}, udt.get_nbr_problems())
def test_get_structs(self):
"""Test the get_structs method."""
@ -965,7 +1024,7 @@ class TestUserDefinedTypes(unittest.TestCase):
}
}
self.assertDictEqual(expected, udt.get_structs())
self.assertDictEqual({'critical': 4, 'warning': 7}, udt.get_nbr_problems())
self.assertDictEqual({'critical': 4, 'warning': 10}, udt.get_nbr_problems())
def test_get_default_enum_value(self):
"""Test the get_default_enum_value method."""
@ -1045,7 +1104,7 @@ class TestUserDefinedTypes(unittest.TestCase):
udt = UserDefinedTypes(self.build_cfg, self.unit_cfg)
expected = {
'EnumTest': {
'underlying_data_type': 'UInt8',
'underlying_data_type': 'Int32',
'members': {
'EnumTest_ClimaOff': 0,
'EnumTest_ClimaHeatgToHvacAndHvBatt': 1,