Inline C MEX S-Functions
Inline S-Function Overview
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 ifMDL_INITIALIZE_CONDITIONS
is declared with#define
.mdlStart
is called only ifMDL_START
is declared with#define
.mdlUpdate
is called only ifMDL_UPDATE
is declared with#define
.mdlDerivatives
is called only ifMDL_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.
S-Function Parameters
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 usingssWriteRTWParamSettings
. The S-function TLC implementation file can then directly access the values of these parameter settings from theSFcnParamSettings
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 themodel
.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/sfuntemplates/tlc_c/sfun_runtime1.tlc
toolbox/simulink/simdemos/simfeatures/src/sfun_runtime2.c
toolbox/simulink/sfuntemplates/tlc_c/sfun_runtime2.tlc
toolbox/simulink/simdemos/simfeatures/src/sfun_runtime3.c
toolbox/simulink/sfuntemplates/tlc_c/sfun_runtime3.tlc
Sample Code for S-Function
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 Approaches for Building Code Generated from Simulink Models.
Comparison of Noninlined and Inlined Versions of model.c
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-functionfoogain
.The input to
foo
isrtGROUND
(a Simulink Coder™ global equal to 0.0) becausefoo
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 forfoogain
. 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 TLCOutputs
function generatesrtB.foo = input * 2.5;
Use the
%generatefile
directive if your operating system has a filename size restriction and the name of the S-function isfoosfunction
(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 offoosfunction.tlc
.
Comparison of Noninlined and Inlined Versions of model_reg.h
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 */ } }
A TLC File to Inline S-Function foogain
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
Managing Block Instance Data with an Eye Toward Code Generation
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.
Using Inlined Code with the Simulink Accelerator Software
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.