Target Language Compiler | ![]() ![]() |
A Complete Example
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 code the Real-Time Workshop generates for model.c
containing noninlined and inlined versions of S-function foogain
. The model contained no other Simulink blocks.
For information about how to generate code with the Real-Time Workshop, see the Real-Time Workshop documentation.
Comparison of Noninlined and Inlined Versions of model.c
Without a TLC file to define the S-function specifics, the Real-Time Workshop must call the MEX-file S-function through the S-function API. The code below is the model.c
file for the noninlined S-function (i.e., no corresponding TLC file).
/* *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 */
This code is model
.c
with the foogain
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 */
By including this simple target file for this S-function block, the model.c
code is generated as
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:
%implements
is required by all block target files, and must be the first executable statement in the block target file. This directive guarantees that the Target Language Compiler does not execute an inappropriate target file for S-function foogain
.
foo
is rtGROUND
(a Real-Time Workshop global equal to 0.0) since foo
is the only block in the model and its input is unconnected.
foogain
eliminated the need for an S-function registration segment for foogain
. This significantly reduces code size.
gain
parameter when Real-Time Workshop 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
%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's block target file).
Comparison of Noninlined and Inlined Versions of model_reg.h
Inlining a Level 2 S-function significantly reduces the size of the model
_reg.h
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 all this code:
/*
* 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 for purposes such as caching 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., ssSetRWork
, ssSetIWork
), or data-typed work vectors known as DWork
s. For the smallest effort in writing both the S-function and block target file and for automatic conformance to both static and malloc instance data on targets such as grt
and grt_malloc
, The MathWorks recommends using data-typed work vectors when writing S-functions with instance data, accessed with the ssSetDWork
and ssGetDWork
methods.
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 model.rtw
file for you automatically, including the DWork
name, data type, and size. This makes writing the block target file a snap, since you have no TLC code to write for allocating and freeing the DWork
memory -- Real-Time Workshop takes care of this for you.
Additionally, if you want to bundle up groups of DWorks
into structures for passing to functions, you can populate the structure with pointers to DWork
arrays in both your S-function's 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 DWorks
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, i.e., both implementations use DWorks
in the same way so that the inlined code can be used with the Simulink Accelerator without any changes to the C MEX S-function or the block target file.
Using Inlined Code With the Simulink Accelerator
By default, the Simulink Accelerator will call your C MEX S-function as part of an accelerated model simulation. If you want to instead 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 will not be able to 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 are not used in generating code for an accelerated model.
![]() | S-Function Parameters | Inlining M-File S-Functions | ![]() |