classdef (Hidden = true) PILBlockSfunction < rtw.pil.Sfunction
%PILBLOCKSFUNCTION creates an s-function for the CodeInfo-based SIL/PIL block
%
% This is an undocumented class. Its methods and properties are likely to
% change without warning from one release to the next.

% Copyright 2008-2020 The MathWorks, Inc.

properties (SetAccess = immutable, GetAccess = private)
IsNonVirtualBusOutput(1,:) logical;
end

methods
% constructor
function this = PILBlockSfunction(silPILWrapperUtils, ...
sfunSourcePath, ...
sfunOutDir, ...
varargin)

narginchk(3, 5);

% Get reference to MF0
mf0Contents = silPILWrapperUtils.MF0ModelInfo;

% Package information to pass to super class constructor.
lHasResetFunction = silPILWrapperUtils.getCodeInfoUtils.hasResetFunction();
if lHasResetFunction
%Only include Reset functions if there are outputs with
%RESET timing
lCodeInfoUtils = silPILWrapperUtils.getCodeInfoUtils;
lHasResetFunction = ~isempty(...
lCodeInfoUtils.getPortsForTimingProperties( ...
lCodeInfoUtils.getRates('RESET'), ...
[lCodeInfoUtils.getCodeInfo.Inports, lCodeInfoUtils.getCodeInfo.Outports]));
end
lExposesITPorts = false;

parser = inputParser;
parser.addParameter('TimingInterfaceFlags', ...
rtw.connectivity.TimingInterfaceFlags.fromMF0(mf0Contents, ...
lHasResetFunction, ...
lExposesITPorts), ...
@(x)isa(x, 'rtw.connectivity.TimingInterfaceFlags'));
parser.parse(varargin{:});

timingInterfaceFlags = parser.Results.TimingInterfaceFlags;

% call super class constructor
this@rtw.pil.Sfunction(silPILWrapperUtils, ...
sfunSourcePath, ...
sfunOutDir, ...
'TimingInterfaceFlags', timingInterfaceFlags);

% Bus outputs are non-virtual if there is no bus object or if
% BusOutputAsStruct is explicitly true.
if mf0Contents.origOutportBusType.Size == 0
% If empty, toArray turns the string sequence into an empty
% char array.
outportBusType = {};
else
outportBusType = mf0Contents.origOutportBusType.toArray;
end
outportBusOutputAsStruct = mf0Contents.origOutportBusOutputAsStruct.toArray;
this.IsNonVirtualBusOutput = ...
cellfun(@isempty, outportBusType) | ...
outportBusOutputAsStruct; % Array - can't use ||
end
end

methods (Access = 'protected')

function is_xrelblock_from_modelref = isXrelBlockFromModelRef(this)
% return true if the PILBlock is a cross-release block and its
% original component was a model reference code interface
silpil_interface = this.SILPILWrapperUtils.getSILPILInterface;
is_xrelblock_from_modelref = ...
silpil_interface.isXRelBlock && ~silpil_interface.isXRelBlockFromTopModelCode;
end

function is_xrelblock = isXrelBlock(this)
% return true if the PILBlock is a cross-release block
silpil_interface = this.SILPILWrapperUtils.getSILPILInterface;
is_xrelblock = ...
silpil_interface.isXRelBlock;
end

function is_xrelblock_reusable = isXrelBlockReusable(this)
% return true if the PILBlock is a cross-release block with a
% reusable code interface.
silpil_interface = this.SILPILWrapperUtils.getSILPILInterface;
is_xrelblock_reusable = ...
silpil_interface.isXRelBlockReusable;
end

% override super class method
function writeSectionInitializeSizesBodyHeader(this)
% call super class method
writeSectionInitializeSizesBodyHeader@rtw.pil.Sfunction(this);
% In-the-Loop s-function
this.writer.writeLine('ssSetRTWGeneratedSFcn(S, 4);');
if ~isempty(this.codeInfo.SymbolicDimensionDefinitions)
this.writer.writeLine('ssSetSymbolicDimsSupport(S, true);');
end
this.writer.newLine;
end

function writeSectionSetInputPortSampleTimeBody(this)
if ~this.codeInfoUtils.isExportFunctions
isInport = true;
this.writeSectionSetIOPortSampleTimeBody(isInport);
else
this.writeSectionSetExpFcnMdlSideIOPortSampleTimeBody
end
end

function writeSectionSetOutputPortSampleTimeBody(this)
isInport = false;
this.writeSectionSetIOPortSampleTimeBody(isInport);
end

% return the number of dialog parameters to register
function numSFcnParams = getNumSFcnParams(this)
% setup Workspace Params
indices = this.SILPILWrapperUtils.getSupportedTunableWorkspaceParams;
numSFcnParams = length(indices);
end

function writeSectionSetWorkWidthsBody(this)
% call super class method
writeSectionSetWorkWidthsBody@rtw.pil.Sfunction(this);

% register runtime params
this.writeSectionRegisterRuntimeParams;

% Validate DSM types
simulinkTypeUtilsInstance = rtw.connectivity.SimulinkTypeUtils;
dataInterfaces = this.codeInfoUtils.getGlobalDataStores;
numDI = length(dataInterfaces);
if numDI > 0
this.writer.wBlockStart('');
this.writer.wComment('Validate data types, dimensions and complexity of data store memories');
this.writer.writeLine('int expectedDataTypeID;');
this.writer.writeLine('int foundDataTypeID;');
this.writer.writeLine('SFcnDataStoreInfo *info;');
this.writer.newLine;

for kDI = 1:numDI
di = dataInterfaces(kDI);
cStorageType = rtw.connectivity.CodeInfoTypeUtils.getCStorageType(di.Type);
diIdx = num2str(kDI-1);
this.writer.wComment(sprintf('Validation: Data store ''%s''', di.GraphicalName));
this.writer.writeLine('ssGetDataStoreInfo(S, %s, &info);', diIdx);

% Validate data type
baseType = rtw.connectivity.CodeInfoTypeUtils.getBaseType(di.Type);
if baseType.isNumeric && baseType.isFixed
% Fixed point data types need to go by name -
% storageId gives there underlying integer
% implementation.
namedObject = baseType.Name;
else
[storageId, namedObject] = rtw.connectivity.CodeInfoTypeUtils.getCodeInfoStoredValueDTypeId(di.Type);
end

% Note: We use ssGetDataTypeIdAliasedThruTo to obtain
% the underlying non-alias type to validate against.
this.writer.writeLine('foundDataTypeID = ssGetDataTypeIdAliasedThruTo(S, info->dataTypeId);');
if isempty(namedObject)
assert(storageId ~= rtw.connectivity.CodeInfoTypeEnum.TypeChar, ...
'String data type not supported for Data Store Memories')

storageId = simulinkTypeUtilsInstance.builtinTypeToTypeId(rtw.connectivity.TypeUtils.codeInfoTypeIdToType(storageId));
this.writer.writeLine('expectedDataTypeID = %d;', storageId);
else
this.writer.writeLine('expectedDataTypeID = ssGetDataTypeIdAliasedThruTo(S, ssGetDataTypeId(S, "%s"));', namedObject);
end
this.writer.wBlockStart('if (foundDataTypeID != expectedDataTypeID)');

% We need to use the static version of the error
% handler here rather than this.writeError.
this.writer.writeLine('mxArray * prhs[ 3 ];');
this.writer.writeLine('prhs[0] = mxCreateString("PIL:pil:InconsistentDSMDataType");');
this.writer.writeLine('prhs[1] = mxCreateString("%s");', di.GraphicalName);
this.writer.writeLine('prhs[2] = mxCreateString("%s");', cStorageType.Name);
this.writer.writeLine('xilSimulinkUtilsStaticErrorHandle(3, prhs, S);');
this.writer.writeLine('return;');
this.writer.wBlockEnd;

% Validate dimensions
symbolicDims = rtw.connectivity.CodeInfoTypeUtils.getSymbolicDimensions(di.Type);
if all(strcmp(symbolicDims, '1'))
% Special case for scalar scenario - check that all
% dimensions present are unity
this.writer.wBlockStart('');
this.writer.writeLine('int locDim;');
this.writer.wBlockStart('for (locDim = 0; locDim < info->numDims; locDim++)');
this.writer.wBlockStart('if (info->dims[ locDim ] != 1)');
this.writer.writeLine('mxArray * prhs[ 5 ];');
this.writer.writeLine('prhs[0] = mxCreateString("PIL:pil:InconsistentDSMDimensions");');
this.writer.writeLine('prhs[1] = mxCreateString("%s");', di.GraphicalName);
this.writer.writeLine('prhs[2] = mxCreateDoubleScalar((double)(locDim+1));');
this.writer.writeLine('prhs[3] = mxCreateString("%s");', '1');
this.writer.writeLine('prhs[4] = mxCreateDoubleScalar((double)info->dims[ locDim ]);');
this.writer.writeLine('xilSimulinkUtilsStaticErrorHandle(5, prhs, S);');
this.writer.writeLine('return;');
this.writer.wBlockEnd;
this.writer.wBlockEnd;
this.writer.wBlockEnd;
else
% For non-scalar situations we expect an exact
% match in the number of dimensions and their
% respective sizes.
expectNumDims = numel(symbolicDims);
this.writer.wBlockStart('if (info->numDims != %d)', expectNumDims);
this.writer.writeLine('mxArray * prhs[ 4 ];');
this.writer.writeLine('prhs[0] = mxCreateString("PIL:pil:InconsistentDSMNumDimensions");');
this.writer.writeLine('prhs[1] = mxCreateString("%s");', di.GraphicalName);
this.writer.writeLine('prhs[2] = mxCreateString("%d");', expectNumDims);
this.writer.writeLine('prhs[3] = mxCreateDoubleScalar((double)info->numDims);');
this.writer.writeLine('xilSimulinkUtilsStaticErrorHandle(4, prhs, S);');
this.writer.writeLine('return;');
this.writer.wBlockEnd;

for kDim = 1:expectNumDims
this.writer.wBlockStart('if (info->dims[ %d ] != %s)', kDim-1, symbolicDims{kDim});
this.writer.writeLine('mxArray * prhs[ 5 ];');
this.writer.writeLine('prhs[0] = mxCreateString("PIL:pil:InconsistentDSMDimensions");');
this.writer.writeLine('prhs[1] = mxCreateString("%s");', di.GraphicalName);
this.writer.writeLine('prhs[2] = mxCreateString("%s");', sprintf('%d', kDim));
this.writer.writeLine('prhs[3] = mxCreateString("%s");', symbolicDims{kDim});
this.writer.writeLine('prhs[4] = mxCreateDoubleScalar((double)info->dims[ %d ]);', kDim-1);
this.writer.writeLine('xilSimulinkUtilsStaticErrorHandle(5, prhs, S);');
this.writer.writeLine('return;');
this.writer.wBlockEnd;
end
end

% Validate complexity
% info->complex
expectComplex = rtw.connectivity.CodeInfoTypeUtils.isComplex(di.Type);
if expectComplex
errMsgStrExpectComplex = 'complex';
else
errMsgStrExpectComplex = 'real';
end
this.writer.wBlockStart('if (info->complex != %d)', expectComplex);
this.writer.writeLine('mxArray * prhs[ %d ];', 3);
this.writer.writeLine('prhs[0] = mxCreateString("PIL:pil:InconsistentDSMComplexity");');
this.writer.writeLine('prhs[1] = mxCreateString("%s");', di.GraphicalName);
this.writer.writeLine('prhs[2] = mxCreateString("%s");', errMsgStrExpectComplex);
this.writer.writeLine('xilSimulinkUtilsStaticErrorHandle(3, prhs, S);');
this.writer.writeLine('return;');
this.writer.wBlockEnd;
end
this.writer.wBlockEnd;
this.writer.newLine;
end

end

function writeSectionProcessParametersHook(this)
inportIndices = [];
% only process params with tunable storage
paramIndices = this.SILPILWrapperUtils.getSupportedTunableWorkspaceParams();
outportIndices = [];
dsReadIndices = [];
dsWriteIndices = [];
sharedLocalDataStoreIndices = [];
loggingIndices = [];
inportSizesIndices = [];
outportSizesIndices = [];
serverFcnIndex = [];
% process params
%
% tid not used
tid = [];
this.writeSectionPILIOCommand('XIL_PROCESS_PARAMS_COMMAND', ...
inportIndices, ...
inportSizesIndices, ...
paramIndices, ...
dsReadIndices, ...
dsWriteIndices, ...
sharedLocalDataStoreIndices, ...
outportIndices, ...
outportSizesIndices, ...
loggingIndices, ...
serverFcnIndex, ...
tid);
end

function writeSectionGetParameterDataInterfacePtr(this, dataInterface, sfunIdx)
% initialize "dataInterfacePtr" for parameter data
di = dataInterface.getRTWDataInterface;
assert(this.codeInfoUtils.isWorkspaceParam(di), ...
'Parameter is not a workspace param.');
this.writer.writeLine(['dataInterfacePtr = (void *) ' ... ...
'ssGetRunTimeParamInfo(S, ' sfunIdx ')->data;']);
end

function writeSectionStartBody(this)

this.writeSectionReturnIfCodeGen;

if ~this.codeInfoUtils.isExportFunctions
% implement required solver error message based on the specification of
% singletasking / multitasking in the interface description
multitasking = this.codeInfoUtils.isMultiTasking;
multirate = this.codeInfoUtils.isMultiRate;
if multitasking
% assert that multitasking implies multirate In-the-Loop component
if ~multirate
rtw.pil.ProductInfo.error('pil', 'SingleRateMultiTasking');
end
% scheduling done by mdlOutputs for multitasking is
% completely independent of whether the In-the-Loop s-function tasking mode
% is singletasking or multitasking => no solver check
this.writer.writeLine('/* no solver check required for multitasking scheduling */');
else
% singletasking
if multirate
% scheduling done by mdlOutputs for singletasking
% assumes singletasking scheduling of the In-the-Loop s-function
% (I/O for all rates accessed on base rate sample hit)
%
% we must check the solver is set to singletasking
this.writer.writeCellLines({'if (ssGetSolverMode(S) != SOLVER_MODE_SINGLETASKING) {';
' ssSetErrorStatus(S, "The In-the-Loop component requires singletasking scheduling. "';
' "Please change the tasking mode in the ''Solver'' component of the configuration set.");';
' return;';
'}'});
else
% scheduling done by mdlOutputs for singlerate is
% completely independent of whether the In-the-Loop s-function tasking
% mode is singletasking or multitasking => no solver
% check
this.writer.writeLine('/* no solver check required for singlerate (singletasking) scheduling */');
end
end
end

% Check start time
this.writer.wLine('/* Check current start time is consistent with the generated code */');
startTime = rtw.connectivity.CodeInfoUtils.double2str( ...
this.SILPILWrapperUtils.MF0ModelInfo.configSettingsForConsistencyChecks.startTime);
this.writer.wBlockStart('if (ssGetTStart(S) != %s)', startTime);
errorReturn = 'return;';
this.writeError(errorReturn, 'PIL:pil:XILBlockStartTimeError', ...
sprintf('mxCreateString("%s")', startTime));
this.writer.wBlockEnd;
% call super class method
writeSectionStartBody@rtw.pil.Sfunction(this);
% export functions doesn't fully support constant sample times
% so we must issue the constant output command here, after
% initialization, rather than from mdlOutputs (CONSTANT_TID)
if this.codeInfoUtils.isExportFunctions && this.codeInfoUtils.hasConstOutput
this.writeSectionConstOutputCommand;
end
end

function isInitializeConditionsRequired = isInitializeConditionsRequired(this) %#ok<MANU>
isInitializeConditionsRequired = true;
end

function writeSectionInitializeConditionsBody(this)
if this.SILPILWrapperUtils.getSILPILInterface.isXRelBlock
% for any cross-release block
errId = 'PIL:xrelimport:UnsupportedResetStates';
else
% for any other SIL/PIL block
errId = 'PIL:pil:NoInitConditionsToInvoke';
end
this.writer.wBlockStart('if (!ssIsFirstInitCond(S))');
% throw error
errorReturn = 'return;';
this.writeError(errorReturn, errId, 'mxCreateString(ssGetPath(S))');
this.writer.wBlockEnd;

end

function writeSectionEnableBody(this)
if ~this.SILPILWrapperUtils.getSILPILInterface.isXRelBlock || ...
this.SILPILWrapperUtils.getSILPILInterface.isXRelBlockFromTopModelCode
% Error for non-cross release blocks or cross release blocks
% with Top model code interface. The generated code does not
% support Enable so we disallow calling it from such a context.
% Cross release blocks with model reference code interface will
% not export if the code has an Enable function. Therefore,
% if we got this far, we know nothing needs to be done for
% Enable (no Enable function) for this model reference cross
% release block => ok to skip the error.
isEnable = true;
this.writeSectionEmptyEnableDisableFunction(isEnable);
end
end

function writeSectionDisableBody(this)
if ~this.SILPILWrapperUtils.getSILPILInterface.isXRelBlock || ...
this.SILPILWrapperUtils.getSILPILInterface.isXRelBlockFromTopModelCode
% Error for non-cross release blocks or cross release blocks
% with Top model code interface. The generated code does not
% support Disable so we disallow calling it from such a context.
% Cross release blocks with model reference code interface will
% not export if the code has an Disable function. Therefore,
% if we got this far, we know nothing needs to be done for
% Disable (no Disable function) for this model reference cross
% release block => ok to skip the error.
isEnable = false;
this.writeSectionEmptyEnableDisableFunction(isEnable);
end
end

function sfunIdx = getSfunIdxForDataInterface(this, dataInterface)
if isa(dataInterface, 'rtw.pil.CodeInfoData')
% correctly calculate sfunIdx
dataInterfaceType = dataInterface.getDataInterfaceType;
switch dataInterfaceType
case 'input'
% Regarding the getNumExportFunctions check: it is
% possible that we have an export function diagram
% that has Simulink functions but no function call
% subsystems. In this case, the model is export
% functions but there is no expInports (since there
% is no function call inport). For such models, we
% should call the super class method
% getSfunIdxForDataInterface below.
if this.codeInfoUtils.isExportFunctions && ...
(this.getCodeInfoUtils.getNumExportFunctions > 0)
% map back via codeInfoUtils
dataInterfaceIdx = dataInterface.getDataInterfaceIdx;
inportIdx = this.codeInfoUtils.getExportFunctionsInportIndex(dataInterfaceIdx);
% zero-based idx
sfunIdx = int2str(inportIdx - 1);
else
% call super class method
sfunIdx = getSfunIdxForDataInterface@rtw.pil.Sfunction(this, dataInterface);
end
case 'parameter'
di = dataInterface.getRTWDataInterface;
assert(this.codeInfoUtils.isWorkspaceParam(di), ...
'Parameter is not a workspace param.');
% find param relative only to workspace
% params
workspaceParamIndices = this.SILPILWrapperUtils.getSupportedTunableWorkspaceParams;
dataInterfaceIdx = dataInterface.getDataInterfaceIdx;
paramIndex = find(dataInterfaceIdx == workspaceParamIndices);
% 0-based index
sfunIdx = int2str(paramIndex - 1);
otherwise
% call super class method
sfunIdx = getSfunIdxForDataInterface@rtw.pil.Sfunction(this, dataInterface);
end
else
% call super class method
sfunIdx = getSfunIdxForDataInterface@rtw.pil.Sfunction(this, dataInterface);
end
end

function validateInterface(this)
% call super class method
validateInterface@rtw.pil.Sfunction(this);

% Global DataStore Support:
%
% Model block SIL/PIL and Top-model SIL/PIL:
% we support global DS for all scheduling
% scenarios, i.e. periodic (single rate, multi rate),
% inherited, and export functions.
%
% SIL/PIL block: No support for periodic multi-rate components.
% see comments in getSharedDataStoresForTask@rtw.connectivity.CodeInfoUtils
% for more info.
dataInterfaces = this.codeInfoUtils.getAllSharedDataStores;
if ~isempty(dataInterfaces)
if ~this.codeInfoUtils.isExportFunctions && this.codeInfoUtils.isMultiRate
rtw.pil.ProductInfo.error('pil', 'MultiRateDataStore', ...
this.SILPILWrapperUtils.getSILPILInterface.getFullComponentPath);
end
end
end

function writeSectionInitializeSizesPort(this, ...
isInport, ...
portTypeString, ...
dataInterface, ...
sfunPortIdx)
this.writer.writeLine(['/* ' portTypeString ' Port ' sfunPortIdx ' */']);
if isInport
% specify contiguous inports
% access to inputs will be via ssGetInputPortSignal
this.writer.writeLine('/* contiguous inport */');
this.writer.writeLine(['ssSetInputPortRequiredContiguous(S, ' sfunPortIdx ', 1);']);

this.writer.writeLine('/* directfeedthrough */');
if this.isInportDirectFeedThrough(sfunPortIdx)
this.writer.writeLine(['ssSetInputPortDirectFeedThrough(S, ' sfunPortIdx ', 1);']);
else
this.writer.writeLine(['ssSetInputPortDirectFeedThrough(S, ' sfunPortIdx ', 0);']);
end
end
% datatype
%
% SS_SIMMODE_SIZES_CALL_ONLY protects from checking out
% fixed point licenses when just opening models (as
% opposed to updating / simulating models)
this.writer.writeLine('if (ssGetSimMode(S) != SS_SIMMODE_SIZES_CALL_ONLY) {');
this.writer.incIndent;
% setup dataTypeId variable
type = rtw.connectivity.CodeInfoTypeUtils.getSimulinkInterfaceType(dataInterface);

this.writeSectionInitializeSizesDatatypeId(type);

this.writer.writeLine(['ssSet' portTypeString 'PortDataType(S, ' sfunPortIdx ', dataTypeId);']);
this.writer.decIndent;
this.writer.writeLine('}');
% Bus / StructType registration
[storageId, namedObject] = rtw.connectivity.CodeInfoTypeUtils.getCodeInfoStoredValueDTypeId(type);
if rtw.connectivity.CodeInfoTypeUtils.isBusOrStruct(type)
% Determine if bus or struct
if this.isBusIOPort(isInport, sfunPortIdx)
if isInport
isNonVirtualBusOutput = true;
else
isNonVirtualBusOutput = this.IsNonVirtualBusOutput(str2double(sfunPortIdx)+1);
end
if isNonVirtualBusOutput
nonVirtualBusOutputStr = '1';
else
nonVirtualBusOutputStr = '0';
end
this.writer.writeLine(['ssSetBus' portTypeString 'AsStruct(S, ' ...
sfunPortIdx ', ' nonVirtualBusOutputStr ');']);
if ~isInport
this.writer.writeLine(['ssSetBusOutputObjectName(S, ' sfunPortIdx ', (void *) "' namedObject '");']);
end
end
% buses are always registered as having fixed dimensions
% even if the bus contains var dims elements
%
% there is no support for var dims arrays of buses
% (with fixed elements)
isVarDims = false;
else
% handle var dims
if isInport
isVarDims = this.codeInfoUtils.isInportVarSize(dataInterface);
else
isVarDims = this.codeInfoUtils.isOutportVarSize(dataInterface);
end
end
% dimensions
this.writer.writeLine('/* dimensions */');
if storageId == rtw.connectivity.CodeInfoTypeEnum.TypeChar
%For string ports, the strings are always scalar as the
%type captures the length of the string. Therefore we don't
%need to deal with symbolic dimensions
symbDims = {'1'};
width = '1';
else
symbDims = ...
rtw.connectivity.CodeInfoTypeUtils.getSymbolicDimensions(type);
width = rtw.connectivity.CodeInfoTypeUtils.getSymbolicWidth(type);
end

numDims = length(symbDims);

this.writer.writeLine('{');
this.writer.incIndent;
this.writer.writeLine('DECL_AND_INIT_DIMSINFO(di);');
this.writeSymbolicDimsVarInitialization(symbDims, 'dims');
this.writer.wLine('di.numDims = %d;', numDims);
this.writer.writeLine('di.dims = dims;');
this.writer.wLine('di.width = %s;', width);
this.writer.writeLine(['ssSet' portTypeString 'PortDimensionInfo(S, ' sfunPortIdx ', &di);']);
this.writer.decIndent;
this.writer.writeLine('}');
if isVarDims
this.writer.writeLine(['ssSet' portTypeString 'PortDimensionsMode(S, ' sfunPortIdx ', VARIABLE_DIMS_MODE);']);
else
this.writer.writeLine(['ssSet' portTypeString 'PortDimensionsMode(S, ' sfunPortIdx ', FIXED_DIMS_MODE);']);
end
if rtw.connectivity.CodeInfoTypeUtils.hasSymbolicDimensions(type)
this.writer.writeLine('{');
this.writer.incIndent;
if 1==numDims
symbDimsExpr = symbDims{1};
else
symbDimsExpr = '[';
sep = '';
for dimsIdx=1:numDims
symbDimsExpr = strcat(symbDimsExpr, sep, ...
symbDims{dimsIdx});
sep = ',';
end
symbDimsExpr = strcat(symbDimsExpr, ']');
end
varName = [portTypeString, sfunPortIdx, 'DimsId'];
this.writer.writeLine(...
['const SymbDimsId ', varName,' = ', ...
'ssRegisterSymbolicDimsExpr(S, "', symbDimsExpr, '");']);
this.writer.writeLine(...
['ssSet', portTypeString, 'PortSymbolicDimsId(S, ', ...
sfunPortIdx ', ', varName, ');']);
this.writer.decIndent;
this.writer.writeLine('}');
end
% complexity
this.writer.writeLine('/* complexity */');
if rtw.connectivity.CodeInfoTypeUtils.isComplex(type)
this.writer.writeLine(['ssSet' portTypeString 'PortComplexSignal(S, ' sfunPortIdx ', COMPLEX_YES);']);
else
this.writer.writeLine(['ssSet' portTypeString 'PortComplexSignal(S, ' sfunPortIdx ', COMPLEX_NO);']);
end
% sample time
if ~this.codeInfoUtils.isExportFunctions
% export functions uses block based sample times
if this.usePortBasedSampleTimes
this.writer.writeLine('/* using port based sample times */');
else
this.writer.writeLine('/* using hybrid block and port based sample times */');
end
[sampleTimeCExpr, sampleOffsetCExpr] = this.getSampleTimeCExpression(dataInterface.Timing);
this.writer.writeLine(['ssSet' portTypeString 'PortSampleTime(S, ' sfunPortIdx ', ' sampleTimeCExpr ');']);
this.writer.writeLine(['ssSet' portTypeString 'PortOffsetTime(S, ' sfunPortIdx ', ' sampleOffsetCExpr ');']);
elseif this.getHasInitTermPorts
this.writeIRTSampleTimeForExportFunctions(dataInterface.Timing, portTypeString, sfunPortIdx);
end
% sampling mode
this.writer.writeLine('/* sampling mode */');
if rtw.connectivity.CodeInfoTypeUtils.isFrame(type)
this.writer.writeLine(['ssSet' portTypeString 'PortFrameData(S, ' sfunPortIdx ', FRAME_YES);']);
else
this.writer.writeLine(['ssSet' portTypeString 'PortFrameData(S, ' sfunPortIdx ', FRAME_NO);']);
end
this.writer.newLine;
% units
if (slfeature('SLUnit') > 0)
this.writer.writeLine('/* units */');
portUnitsWriter = coder.internal.modelreference.PortUnitsWriter(this.writer);
portUnitsWriter.writeSfunctionRegisterAndSetUnit(portTypeString,...
this.getIOPortUnits(isInport, sfunPortIdx), sfunPortIdx);
end

if this.codeInfoUtils.isQueued(dataInterface)
if isInport
this.writer.wLine('slmsg_ssSetInputPortIsMessage(S, %s, 1);', sfunPortIdx);
this.writer.wLine('slmsg_ssSetUseInternalQueue(S, %s, false);', sfunPortIdx);
else
this.writer.wLine('slmsg_ssSetOutputPortIsMessage(S, %s, 1);', sfunPortIdx);
end
end
end

function writeSectionInitializeSizesBodyInportsAndOutports(this)
% process Inports
if this.codeInfoUtils.isExportFunctions
this.writeSectionInitializeSizesExportFunctionsPortSetup;
else
isInport = true;
this.writeSectionInitializeSizesPortSetup(isInport);
end
% process Outports
isInport = false;
this.writeSectionInitializeSizesPortSetup(isInport);
end

function writeSectionExportFunctionsEnableDisableWrapper(this, ...
isEnableFcn, ...
portIdx, ...
sfunPortIdx)
if isEnableFcn
typeStr = 'Enable';
else
typeStr = 'Disable';
end

enableDisableFcn = this.codeInfoUtils.getExportFunctionsEnableDisableFcn(portIdx, isEnableFcn);

% SIL/PIL block is not supported for top model export functions
% so we don't need to worry about creating dummy enable /
% disable functions that throw errors if the block is called
% from invalid contexts.
%
% Here we support the legacy subsystem-based export functions
% by generating enable / disable functions if they exist in the
% code.
if ~isempty(enableDisableFcn)
this.writeSectionExportFunctionsEnableDisableFcn(isEnableFcn, ...
sfunPortIdx, ...
typeStr, ...
enableDisableFcn);
end
end

function writeSectionInitializeSizesDWorks(this)
% Write code to initialize Opaque-typed DWorks to support reusable model
% reference code interface cross release blocks.
xrelDWorkTypes = this.SILPILWrapperUtils.getSILPILInterface.XRelDWorkTypes;
xrelDWorkNames = this.SILPILWrapperUtils.getSILPILInterface.XRelDWorkNames;
if this.SILPILWrapperUtils.getSILPILInterface.isXRelBlock
assert(numel(xrelDWorkTypes) == numel(xrelDWorkNames), ...
'xrelDWorkTypes and xrelDWorkNames should have an equal number of elements');

if ~isempty(xrelDWorkTypes)
this.writer.writeLine('if (!ssSetNumDWork(S, %d)) return;', numel(xrelDWorkTypes));
end
for kDWork = 1:numel(xrelDWorkTypes)
typeName = xrelDWorkTypes{kDWork};
dWorkName = xrelDWorkNames{kDWork};
dWorkIdx = kDWork-1; % Zero-based indexing in C code
this.writer.wBlockStart('if (ssGetSimMode(S) != SS_SIMMODE_SIZES_CALL_ONLY)');
this.writer.writeLine('DTypeId dataTypeIdReg;');
this.writer.writeLine('int_T status;');
this.writer.newLine;

% Register the opaque data type
this.writer.writeLine('dataTypeIdReg = ssRegisterDataType(S, "%s");', typeName);
this.writer.writeLine('if(dataTypeIdReg == INVALID_DTYPE_ID) return;');
this.writer.newLine;

% We have to set the data type size - but we will never be
% accessing it from the C-MEX S-function, so a default of
% sizeof(int_T) should be sufficient.
this.writer.writeLine('status = ssSetDataTypeSize(S, dataTypeIdReg, sizeof(int_T));');
this.writer.writeLine('if(status == 0) return;');
this.writer.newLine;

% Configure the DWork's data type
this.writer.writeLine('ssSetDWorkDataType(S, %d, dataTypeIdReg);', dWorkIdx);
this.writer.wBlockEnd();

this.writer.writeLine('ssSetDWorkUsageType(S, %d, SS_DWORK_USED_AS_DWORK);', dWorkIdx);
this.writer.writeLine('ssSetDWorkName(S, %d, "%s");', dWorkIdx, dWorkName);
this.writer.writeLine('ssSetDWorkWidth(S, %d, %d);', dWorkIdx, 1);
this.writer.writeLine('ssSetDWorkComplexSignal(S, %d, COMPLEX_NO);', dWorkIdx);

end
else
% DWorks ONLY should be configured for cross release blocks
assert(isempty(xrelDWorkTypes), 'xrelDWorkTypes should only be non-empty for cross-release XIL blocks');
assert(isempty(xrelDWorkNames), 'xrelDWorkNames should only be non-empty for cross-release XIL blocks');
end
end

function fcnName = getExportFunctionEnableDisableFcnName(~, sfunPortIdx, ~, typeStr)
fcnName = ['mdlExported' typeStr 'Fcn' sfunPortIdx];
end
function writeSectionSetInputPortSampleTime(this)
if this.codeInfoUtils.isExportFunctions || ~this.usePortBasedSampleTimes
writeSectionSetInputPortSampleTime@rtw.pil.Sfunction(this);
end
end
function writeSectionSetOutputPortSampleTime(this)
if this.codeInfoUtils.isExportFunctions || ~this.usePortBasedSampleTimes
writeSectionSetOutputPortSampleTime@rtw.pil.Sfunction(this);
end
end
end

methods (Access = 'private')
function ioPortIdx = getIOPortIdx(this, isInport, sfunPortIdx)
% The correct index to use to access the information in
% infoStruct.modelInterface via
% this.SILPILWrapperUtils.getModelInterfaceIOPort is the 'data
% port index', which takes into account whether the port is a
% 'real' data port or it is something else, for example, a
% function-call port: only 'real' data ports are assigned a
% (non-zero) 'data port index'.
%
% Considering a simple model where input ports #1 and #3 are
% function-call ports, while input ports #2 and #4 are regular
% input ports, the 'data port index' for port #2 will be 1 and
% for port #4 will be 2.
%
% This is important for models with export functions, as
% different APIs (codeInfo, infoStruct.modelInterface,
% S-function, etc.) require different indexes to access the
% same port.

% Get a 1-based index from the 0-based S-function index, as
% both codeInfo and infoStruct.modelInterface work with 1-based
% indexes
portIdx = str2double(sfunPortIdx) + 1;

if this.codeInfoUtils.isExportFunctions && isInport
% If the model exports functions additional care is needed
% in the case of input ports, as some of them might be
% function-call triggers rather than regular data ports.
% Look up the corresponding 'data port index' in codeInfo.
ioPortIdx = ...
this.codeInfoUtils.getExportFunctionsDataPortIndex(portIdx);
else
ioPortIdx = portIdx;
end
end

function units = getIOPortUnits(this, isInport, sfunPortIdx)
% Get Simulink Units information from infoStruct.modelInterface
ioPortIdx = this.getIOPortIdx(isInport, sfunPortIdx);
port = this.SILPILWrapperUtils.getModelInterfaceIOPort(...
isInport, ioPortIdx);

units = port.UnitExpr;
end

function isBusIOPort = isBusIOPort(this, isInport, sfunPortIdx)
% return whether an I/O port is a bus

% Get information on whether the port is a bus from
% infoStruct.modelInterface
ioPortIdx = this.getIOPortIdx(isInport, sfunPortIdx);
port = this.SILPILWrapperUtils.getModelInterfaceIOPort(...
isInport, ioPortIdx);

% modelInterface.Inports{xx}.IsStruct is implemented using
% LibDataTypeIsBus.
isBusIOPort = logical(port.IsStruct);
end

function isInportDirectFeedThrough = isInportDirectFeedThrough(...
this, sfunPortIdx)
% Establish if each single port is DF or not (access the
% PUBLIC vector of DFs in silpilwrapperutils)
ioPortIdx = this.getIOPortIdx(true,... % It is always an inport
sfunPortIdx);
isInportDirectFeedThrough = ...
this.SILPILWrapperUtils.InportDirectFeedThrough(ioPortIdx);
end

function writeSectionSetIOPortSampleTimeBody(this, isInport)
if this.codeInfoUtils.isSampleTimeIndependent && ~this.codeInfoUtils.isExportFunctions
this.writer.writeLine('/* block cannot inherit constant sample time owing to SS_OPTION_DISALLOW_CONSTANT_SAMPLE_TIME */');

if isInport
numInputs = int2str(length(this.codeInfo.Inports));
this.writer.writeCellLines({'int i;';
'';
['for(i = 0 ; i < ' numInputs '; ++i) {'];
' ssSetInputPortSampleTime(S,i,sampleTime);';
' ssSetInputPortOffsetTime(S,i,offsetTime);';
' }';
'';})
else
numOutputs = int2str(length(this.codeInfo.Outports));
this.writer.writeCellLines({'int i;';
'';
['for(i = 0 ; i < ' numOutputs '; ++i) {'];
' if(ssGetOutputPortSampleTime(S,i) == mxGetInf() &&';
' ssGetOutputPortOffsetTime(S,i) == 0.0) {';
' /* already set to const in mdlInitializeSizes */';
' continue;';
' }';
' ssSetOutputPortSampleTime(S,i,sampleTime);';
' ssSetOutputPortOffsetTime(S,i,offsetTime);';
'}';});
end
else
this.writer.writeLine('/* sample times are fully specified */');
end
end

function writeSectionSetExpFcnMdlSideIOPortSampleTimeBody(this)
if ~this.getHasInitTermPorts
this.writer.writeLine('/* sample times are fully specified */');
else
numInputs = int2str(this.codeInfoUtils.getNumExportFunctionsInports);
% Here we assume that all the port groups have the same rate, so we can
% use the rate of the first ctrl port to set all the non-IRT side IOs.
% In the future, if multi-rate case is supported, this logic should be
% revisited.
CtrlPort = 1;
for portIdx = 1:this.codeInfoUtils.getNumExportFunctionsInports
if ~isempty(this.codeInfoUtils.getExportFunctionsCodeInfoOutputFunction(portIdx))
CtrlPort = portIdx - 1;
break;
end
end
numOutputs = int2str(length(this.codeInfo.Outports));
this.writer.writeCellLines({
['if (portIdx == ' int2str(CtrlPort) ') {']});
this.writer.writeCellLines({'int i;';
'';
['for(i = 0 ; i < ' numInputs '; ++i) {'];
' if(ssGetInputPortSampleTime(S,i) == mxGetInf()) {';
' continue;';
' }';
' ssSetInputPortSampleTime(S,i,sampleTime);';
' ssSetInputPortOffsetTime(S,i,offsetTime);';
' }';
'';
['for(i = 0 ; i < ' numOutputs '; ++i) {'];
' if(ssGetOutputPortSampleTime(S,i) == mxGetInf()) {';
' continue;';
' }';
' ssSetOutputPortSampleTime(S,i,sampleTime);';
' ssSetOutputPortOffsetTime(S,i,offsetTime);';
'}';});
this.writer.writeCellLines({'}'});
end
end

function writeSectionInitializeSizesExportFunctionsPortSetup(this)
numPorts = this.codeInfoUtils.getNumExportFunctionsInports;
this.writer.writeLine(['if (!ssSetNumInputPorts(S, ' int2str(numPorts) ')) return;']);
this.writer.newLine;

for portIdx = 1:numPorts
% 0-based indices in s-function
sfunPortIdx = int2str(portIdx - 1);
% see if port maps to an OutputFunction
outputFcn = this.codeInfoUtils.getExportFunctionsCodeInfoOutputFunction(portIdx);
if ~isempty(outputFcn)
this.writer.writeLine(['/* Register exported function-call wrapper: port ' sfunPortIdx ' */']);
this.writer.writeLine(['ssSetInputPortDataType(S, ' sfunPortIdx ', SS_FCN_CALL);']);
this.writer.writeLine(['ssSetInputPortWidth(S, ' sfunPortIdx ', 1);']);
this.writer.writeLine(['ssSetInputPortDirectFeedThrough(S, ' sfunPortIdx ', 1);']);
this.writer.writeLine(['ssExportOutputFcn(S, ' sfunPortIdx ', mdlExportedOutputFcn' sfunPortIdx ');']);
end
% see if port maps to an Inport
inport = this.codeInfoUtils.getExportFunctionsCodeInfoInport(portIdx);
if ~isempty(inport)
% regular setup
isInport = true;
portTypeString = 'Input';
this.writeSectionInitializeSizesPort(isInport, ...
portTypeString, ...
inport.codeInfoData, ...
sfunPortIdx);
% configure latching
isLatched = this.codeInfoUtils.isExportFunctionsLatchedInport(portIdx);
if isLatched
this.writer.writeLine('/* Declare latched input port */');
this.writer.writeLine(['ssInportIsLatched(S, ' sfunPortIdx ');']);
this.writer.newLine;
end
elseif this.getHasInitTermPorts
this.writer.writeLine(['ssSetInputPortSampleTime(S, ' sfunPortIdx ', -1);']);
end
% see if port maps to an Enable function
isEnableFcn = true;
enableDisableFcn = this.codeInfoUtils.getExportFunctionsEnableDisableFcn(portIdx, isEnableFcn);
if ~isempty(enableDisableFcn)
fcnName = this.getExportFunctionEnableDisableFcnName(sfunPortIdx, [], 'Enable');
this.writer.writeLine(['ssExportEnableFcn(S, ' sfunPortIdx ', ' fcnName ');']);
end
% see if port maps to a Disable function
isEnableFcn = false;
enableDisableFcn = this.codeInfoUtils.getExportFunctionsEnableDisableFcn(portIdx, isEnableFcn);
if ~isempty(enableDisableFcn)
fcnName = this.getExportFunctionEnableDisableFcnName(sfunPortIdx, [], 'Disable');
this.writer.writeLine(['ssExportDisableFcn(S, ' sfunPortIdx ', ' fcnName ');']);
end
end
end
function writeIRTSampleTimeForExportFunctions(this, rate, portTypeString, sfunPortIdx)
assert(this.codeInfoUtils.isExportFunctions);

if strcmp(rate.TimingMode,'UNION')
rate = this.codeInfoUtils.getArray(rate.UnionTimingInfo);
end

initRate = [];
termRate = [];
resetOrConstRate = [];
hasRunTimeRate = false;
for rateIdx = 1:length(rate)
period = rate(rateIdx).SamplePeriod;
offset = rate(rateIdx).SampleOffset;
if period == Inf
if offset == 1
initRate = rate(rateIdx);
break;
elseif offset == 2
termRate = rate(rateIdx);
else
resetOrConstRate = rate(rateIdx);
end
else
hasRunTimeRate = true;
end
end

if ~isempty(initRate)
% Use init rate as the representative of the union. This is so that the root inport driving it can
% load data correctly during initialization.
[sampleTimeCExpr, sampleOffsetCExpr] = this.getSampleTimeCExpression(initRate);
this.writer.writeLine(['ssSet' portTypeString 'PortSampleTime(S, ' sfunPortIdx ', ' sampleTimeCExpr ');']);
this.writer.writeLine(['ssSet' portTypeString 'PortOffsetTime(S, ' sfunPortIdx ', ' sampleOffsetCExpr ');']);
elseif hasRunTimeRate
% 2nd precedence. Use run-time rate as the representative of the union.
this.writer.writeLine(['ssSet' portTypeString 'PortSampleTime(S, ' sfunPortIdx ', -1);']);
elseif ~isempty(termRate)
% 3rd precedence. Use term rate as the representative of the union.
[sampleTimeCExpr, sampleOffsetCExpr] = this.getSampleTimeCExpression(termRate);
this.writer.writeLine(['ssSet' portTypeString 'PortSampleTime(S, ' sfunPortIdx ', ' sampleTimeCExpr ');']);
this.writer.writeLine(['ssSet' portTypeString 'PortOffsetTime(S, ' sfunPortIdx ', ' sampleOffsetCExpr ');']);
else
% Only reset rate(s) are present. In Block SIL context these rates
% can't actually be accessed.
[sampleTimeCExpr] = this.getSampleTimeCExpression(resetOrConstRate);
this.writer.writeLine(['ssSet' portTypeString 'PortSampleTime(S, ' sfunPortIdx ', ' sampleTimeCExpr ');']);
end
end
end
end