diff --git a/docs/target_link/enumerations.md b/docs/target_link/enumerations.md index f4cd70e..330b2aa 100644 --- a/docs/target_link/enumerations.md +++ b/docs/target_link/enumerations.md @@ -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? diff --git a/powertrain_build/matlab_scripts/CodeGen/Generate_A2L.m b/powertrain_build/matlab_scripts/CodeGen/Generate_A2L.m index 9624521..dbb93f6 100644 --- a/powertrain_build/matlab_scripts/CodeGen/Generate_A2L.m +++ b/powertrain_build/matlab_scripts/CodeGen/Generate_A2L.m @@ -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); diff --git a/powertrain_build/user_defined_types.py b/powertrain_build/user_defined_types.py index 09a4dd1..c0ada5b 100644 --- a/powertrain_build/user_defined_types.py +++ b/powertrain_build/user_defined_types.py @@ -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): diff --git a/tests/powertrain_build/test_user_defined_types.py b/tests/powertrain_build/test_user_defined_types.py index 866750f..76e3c71 100644 --- a/tests/powertrain_build/test_user_defined_types.py +++ b/tests/powertrain_build/test_user_defined_types.py @@ -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,