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:
mdlInitializeConditionsis called only ifMDL_INITIALIZE_CONDITIONSis declared with#define.mdlStartis called only ifMDL_STARTis declared with#define.mdlUpdateis called only ifMDL_UPDATEis declared with#define.mdlDerivativesis called only ifMDL_DERIVATIVESis 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
mdlRTWmethod of the S-function usingssWriteRTWParamSettings. The S-function TLC implementation file can then directly access the values of these parameter settings from theSFcnParamSettingsrecord 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.rtwLibBlockParameterlibrary 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.ctoolbox/simulink/sfuntemplates/tlc_c/sfun_runtime1.tlctoolbox/simulink/simdemos/simfeatures/src/sfun_runtime2.ctoolbox/simulink/sfuntemplates/tlc_c/sfun_runtime2.tlctoolbox/simulink/simdemos/simfeatures/src/sfun_runtime3.ctoolbox/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"
#endifThe 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 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
%implementsis 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
fooisrtGROUND(a Simulink Coder™ global equal to 0.0) becausefoois the only block in the model and its input is unconnected.Including a TLC file for
foogaineliminates the need for an S-function registration segment forfoogain. This significantly reduces code size.The TLC code inlines the
gainparameter 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 TLCOutputsfunction generatesrtB.foo = input * 2.5;
Use the
%generatefiledirective 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.tlcinstead 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.