When a Simulink® model contains an S-function and a corresponding TLC block target file exists for that S-function, the code generator inlines the S-function. Inlining an S-function can produce more efficient code by eliminating the S-function API layer from the generated code.
For S-functions that can perform a variety of tasks, inlining them gives you the opportunity to generate code only for the current mode of operation set for each instance of the block. As an example of this, if an S-function accepts an arbitrary signal width and loops through each element of the signal, you would want to generate inlined code that has loops when the signal has two or more elements, but generates a simple nonlooped calculation when the signal has just one element.
Level 1 C MEX S-functions (written to an older form of the S-function API) that are not inlined will cause the generated code to make calls to all of these functions even if the routine is empty for the particular S-function.
Function | Purpose |
---|---|
Initialize the sizes array | |
Initialize the sample times array | |
Initialize the states | |
Compute the outputs | |
Update discrete states | |
Compute the derivatives of continuous states | |
Clean up when the simulation terminates |
Level 2 C MEX S-functions (i.e., those written to the current S-function API) that are not inlined make calls to the above functions, with the following exceptions:
mdlInitializeConditions
is called only if
MDL_INITIALIZE_CONDITIONS
is declared with
#define
.
mdlStart
is called only if MDL_START
is
declared with #define
.
mdlUpdate
is called only if MDL_UPDATE
is
declared with #define
.
mdlDerivatives
is called only if
MDL_DERIVATIVES
is declared with #define
.
By inlining an S-function, you can eliminate the calls to these possibly empty functions in the simulation loop. This can greatly improve the efficiency of the generated code.
To inline an S-function called
,
you create a custom S-function block target file called
sfunc
_name
and place it in the same
folder as the S-function MEX-file. Then, at build time, the target file is executed instead
of setting up function calls into the S-function sfunc
_name.tlc.c
file. The S-function
target file “inlines” the S-function by directing the Target Language Compiler
to insert only the statements defined in the target file.
In general, inlining an S-function is especially useful when
The time required to execute the contents of the S-function is small in comparison to the overhead required to call the S-function.
Certain S-function routines are empty (e.g., mdlUpdate
).
The behavior of the S-function changes between simulation and code generation. For example, device driver I/O S-functions might read from the MATLAB® workspace during simulation, but read from an actual hardware address in the generated code.
An S-function can write two different types of parameters into the
file for Target Language
Compiler files to access:model
.rtw
Parameter settings: These correspond to nontunable parameters (typically set from
check boxes and menus on a masked S-function) that are written via the
mdlRTW
method of the S-function using
ssWriteRTWParamSettings
. The S-function TLC implementation file
can then directly access the values of these parameter settings from the
SFcnParamSettings
record in the block.
Tunable parameters: This class of parameters can be accessed when they are
registered as run-time parameters within the S-function. Note that such tunable
parameters are automatically written out to the
file. Within the TLC file
for the S-function, you can access run-time parameters and their attributes using the
model
.rtwLibBlockParameter
library function and its variants.
For more information on how to create and use run-time parameters, see Create and Update S-Function Run-Time Parameters. Also see the example sfcndemo_runtime
in the S-function
examples for how to create and use the two classes of parameters. The example source files,
which you can inspect and adapt, are
toolbox/simulink/simdemos/simfeatures/src/sfun_runtime1.c
toolbox/simulink/simdemos/simfeatures/tlc_c/sfun_runtime1.tlc
toolbox/simulink/simdemos/simfeatures/src/sfun_runtime2.c
toolbox/simulink/simdemos/simfeatures/tlc_c/sfun_runtime2.tlc
toolbox/simulink/simdemos/simfeatures/src/sfun_runtime3.c
toolbox/simulink/simdemos/simfeatures/tlc_c/sfun_runtime3.tlc
Suppose you have a simple S-function that mimics the Gain block, with one input, one
output, and a scalar gain. That is, y = u * p
. If the Simulink block’s name is foo
and the name of the Level 2 S-function
is foogain
, the C MEX S-function must contain this code:
#define S_FUNCTION_NAME foogain #define S_FUNCTION_LEVEL 2 #include "simstruc.h" #define GAIN mxGetPr(ssGetSFcnParam(S,0))[0] static void mdlInitializeSizes(SimStruct *S) { ssSetNumContStates (S, 0); ssSetNumDiscStates (S, 0); if (!ssSetNumInputPorts(S, 1)) return; ssSetInputPortWidth (S, 0, 1); ssSetInputPortDirectFeedThrough(S, 0, 1); if (!ssSetNumOutputPorts(S, 1)) return; ssSetOutputPortWidth (S, 0, 1); ssSetNumSFcnParams (S, 1); ssSetNumSampleTimes (S, 0); ssSetNumIWork (S, 0); ssSetNumRWork (S, 0); ssSetNumPWork (S, 0); } static void mdlOutputs(SimStruct *S, int_T tid) { real_T *y = ssGetOutputPortRealSignal(S, 0); const InputRealPtrsType u = ssGetInputPortRealSignalPtrs(S, 0); y[0] = (*u)[0] * GAIN; } static void mdlInitializeSampleTimes(SimStruct *S){} static void mdlTerminate(SimStruct *S) {} #define MDL_RTW /* Change to #undef to remove function */ #if defined(MDL_RTW)&&(defined(MATLAB_MEX_FILE)||defined(NRT)) static void mdlRTW (SimStruct *S) { if (!ssWriteRTWParameters(S, 1,SSWRITE_VALUE_VECT,"Gain","", mxGetPr(ssGetSFcnParam(S,0)),1)) { return; } } #endif #ifdef MATLAB_MEX_FILE #include "simulink.c" #else #include "cg_sfun.h" #endif
The following two sections show the difference in the generated code for
containing noninlined and inlined
versions of S-function model
.cfoogain
. The model contains
no other Simulink blocks.
For more information about these S-function related C library functions, see Configure C/C++ S-Function Features. For information about how to generate code, see Configure Model and Generate Code and Choose Build Approach and Configure Build Process.
Without a TLC file to define the S-function specifics, the code generator must call
the MEX-file S-function through the S-function API. The following code is the
file for the noninlined
S-function (i.e., no corresponding TLC file exists).model
.c
Noninlined S-Function
/* * model.c . . . */ real_T untitled_RGND = 0.0; /* real_T ground */ /* Start the model */ void MdlStart(void) { /* (no start code required) */ } /* Compute block outputs */ void MdlOutputs(int_T tid) { /* Level2 S-Function Block: <Root>/S-Function (foogain) */ { SimStruct *rts = ssGetSFunction(rtS, 0); sfcnOutputs(rts, tid); } } /* Perform model update */ void MdlUpdate(int_T tid) { /* (no update code required) */ } /* Terminate function */ void MdlTerminate(void) { /* Level2 S-Function Block: <Root>/S-Function (foogain) */ { SimStruct *rts = ssGetSFunction(rtS, 0); sfcnTerminate(rts); } } #include "model_reg.h" /* [EOF] model.c */
Inlined S-Function. This code is
with the
model
.cfoogain
S-function fully inlined:
/* * model.c . . . */ /* Start the model */ void MdlStart(void) { /* (no start code required) */ } /* Compute block outputs */ void MdlOutputs(int_T tid) /* S-Function block: <Root>/S-Function */ /* NOTE: There are no calls to the S-function API in the inlined version of model.c. */ rtB.S_Function = 0.0 * rtP.S_Function_Gain; } /* Perform model update */ void MdlUpdate(int_T tid) { /* (no update code required) */ } /* Terminate function */ void MdlTerminate(void) { /* (no terminate code required) */ } #include "model_reg.h" /* [EOF] model.c */
If you include this target file for this S-function block, the resulting
code is model
.c
rtB.S_Function = 0.0 * rtP.S_Function_Gain;
Including a TLC file drastically decreased the code size and increased the execution efficiency of the generated code. These notes highlight some information about the TLC code and the generated output:
The TLC directive %implements
is required by block target
files, and must be the first executable statement in the block target file. This
directive prevents the Target Language Compiler from executing an inappropriate
target file for S-function foogain
.
The input to foo
is rtGROUND
(a
Simulink
Coder™ global equal to 0.0) because foo
is the only block
in the model and its input is unconnected.
Including a TLC file for foogain
eliminates the need for an
S-function registration segment for foogain
. This significantly
reduces code size.
The TLC code inlines the gain
parameter when the build
process is configured to inline parameter values. For example, if the S-function
parameter is specified as 2.5 in the S-function dialog box, the TLC
Outputs
function generates
rtB.foo = input * 2.5;
Use the %generatefile
directive if your operating system has
a filename size restriction and the name of the S-function is
foosfunction
(that exceeds the limit). In this case, you
would include the following statement in the system target file (anywhere prior to a
reference to this S-function block target file).
%generatefile foosfunction "foosfunc.tlc"
This statement tells the Target Language Compiler to open
foosfunc.tlc
instead of
foosfunction.tlc
.
Inlining a Level 2 S-function significantly reduces the size of the
code. Model registration
functions are lengthy; much of the code has been eliminated in this example. The code
below highlights the difference between the noninlined and inlined versions of
model
_reg.h
; inlining eliminates this
code:model
_reg.h
/* * model_reg.h * */ /* Normal model initialization code independent of S-functions */ /* child S-Function registration */ ssSetNumSFunctions(rtS, 1); /* register each child */ { static SimStruct childSFunctions[1]; static SimStruct *childSFunctionPtrs[1]; (void)memset((char_T *)&childSFunctions[0], 0, sizeof(childSFunctions)); ssSetSFunctions(rtS, &childSFunctionPtrs[0]); { int_T i; for(i = 0; i < 1; i++) { ssSetSFunction(rtS, i, &childSFunctions[i]); } } /* Level2 S-Function Block: untitled/<Root>/S-Function (foogain) */ { extern void foogain(SimStruct *rts); SimStruct *rts = ssGetSFunction(rtS, 0); /* timing info */ static time_T sfcnPeriod[1]; static time_T sfcnOffset[1]; static int_T sfcnTsMap[1]; { int_T i; for(i = 0; i < 1; i++) { sfcnPeriod[i] = sfcnOffset[i] = 0.0; } } ssSetSampleTimePtr(rts, &sfcnPeriod[0]); ssSetOffsetTimePtr(rts, &sfcnOffset[0]); ssSetSampleTimeTaskIDPtr(rts, sfcnTsMap); ssSetMdlInfoPtr(rts, ssGetMdlInfoPtr(rtS)); /* inputs */ { static struct _ssPortInputs inputPortInfo[1]; _ssSetNumInputPorts(rts, 1); ssSetPortInfoForInputs(rts, &inputPortInfo[0]); /* port 0 */ { static real_T const *sfcnUPtrs[1]; sfcnUPtrs[0] = &untitled_RGND; ssSetInputPortWidth(rts, 0, 1); ssSetInputPortSignalPtrs(rts, 0, (InputPtrsType)&sfcnUPtrs[0]); } } /* outputs */ { static struct _ssPortOutputs outputPortInfo[1]; _ssSetNumOutputPorts(rts, 1); ssSetPortInfoForOutputs(rts, &outputPortInfo[0]); ssSetOutputPortWidth(rts, 0, 1); ssSetOutputPortSignal(rts, 0, &rtB.S_Function); } /* path info */ ssSetModelName(rts, "S-Function"); ssSetPath(rts, "untitled/S-Function"); ssSetParentSS(rts, rtS); ssSetRootSS(rts, ssGetRootSS(rtS)); ssSetVersion(rts, SIMSTRUCT_VERSION_LEVEL2); /* parameters */ { static mxArray const *sfcnParams[1]; ssSetSFcnParamsCount(rts, 1); ssSetSFcnParamsPtr(rts, &sfcnParams[0]); ssSetSFcnParam(rts, 0, &rtP.S_Function_P1Size[0]); } /* registration */ foogain(rts); sfcnInitializeSizes(rts); sfcnInitializeSampleTimes(rts); /* adjust sample time */ ssSetSampleTime(rts, 0, 0.2); ssSetOffsetTime(rts, 0, 0.0); sfcnTsMap[0] = 0; /* Update the InputPortReusable and BufferDstPort flags for each input port */ ssSetInputPortReusable(rts, 0, 0); ssSetInputPortBufferDstPort(rts, 0, -1); /* Update the OutputPortReusable flag of each output port */ } }
To avoid unnecessary calls to the S-function and to generate the minimum code required
for the S-function, the following TLC file, foogain.tlc
, is provided as
an example.
%implements "foogain" "C" %function Outputs (block, system) Output /* %<Type> block: %<Name> */ %% %assign y = LibBlockOutputSignal (0, "", "", 0) %assign u = LibBlockInputSignal (0, "", "", 0) %assign p = LibBlockParameter (Gain, "", "", 0) %<y> = %<u> * %<p>; %endfunction
Instance data is extra data or working memory that is unique to each instance of a block in a Simulink model. This does not include parameter or state data (which is stored in the model parameter and state vectors, respectively), but rather is used to cache intermediate results or derived representations of parameters and modes. One example of instance data is the buffer used by a transport delay block.
Allocating and using memory on an instance-by-instance basis can be done several ways
in a Level 2 S-function: via ssSetUserData
, work vectors (e.g.,
ssSetRWorkValue
, ssSetIWorkValue
), or data-typed
work vectors known as DWork
vectors. For the smallest effort in writing
the S-function and block target file and for automatic conformance to both static and
malloc
instance data on targets such as grt
, use
data-typed work vectors when writing S-functions with instance data.
The advantages are twofold. In the first place, writing the S-function is more
straightforward, in that memory allocations and frees are handled for you by Simulink. Secondly, the DWork
vectors are written to the
file for you automatically,
including the model
.rtwDWork
name, data type, and size. This makes writing the
block target file easier, because you do not have to write TLC code for allocating and
freeing the DWork
memory.
Additionally, if you want to bundle groups of DWork
vectors into
structures for passing to functions, you can populate the structure with pointers to
DWork
arrays in both your S-function mdlStart
function and the block target file’s Start
method, achieving
consistency between the S-function and the generated code’s handling of data.
Finally, using a DWork
makes it straightforward to create a
specific version of code (data types, scalar vs. vectorized, etc.) for each block instance
that matches the implementation in the S-function. Both implementations use
DWork
in the same way so that the inlined code can be used with the
Simulink
Accelerator™ software without changes to the C MEX S-function or the block target
file.
By default, the Simulink
Accelerator software calls your C MEX S-function as part of an accelerated model
simulation. If you prefer to have the accelerator inline your S-function before running
the accelerated model, tell the accelerator to use your block target file to inline the
S-function with the SS_OPTION_USE_TLC_WITH_ACCELERATOR
flag in the call
to ssSetOptions()
in the mdlInitializeSizes
function of that S-function.
Note that memory and work vector size and usage must be the same for the TLC generated
code and the C MEX S-function, or the Simulink
Accelerator software cannot execute the inlined code properly. This is because the C MEX
S-function is called to initialize the block and its work vectors, calling the
mdlInitializeSizes
, mdlInitializeConditions
,
mdlCheckParameters
, mdlProcessParameters
, and
mdlStart
functions. In the case of constant signal propagation,
mdlOutputs
is called from the C MEX S-function during the
initialization phase of model execution.
During the time-stepping phase of accelerated model execution, the code generated by
the Output
and Update
block TLC methods will
execute, plus the Derivatives
and zero-crossing methods if they
exist. The Start
method of the block target file is not used in
generating code for an accelerated model.