Main Content

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.

FunctionPurpose

mdlInitializeSizes

Initialize the sizes array

mdlInitializeSampleTimes

Initialize the sample times array

mdlInitializeConditions

Initialize the states

mdlOutputs

Compute the outputs

mdlUpdate

Update discrete states

mdlDerivatives

Compute the derivatives of continuous states

mdlTerminate

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 sfunc_name, you create a custom S-function block target file called sfunc_name.tlc 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 .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 model.rtw file for Target Language Compiler files to access:

  • 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 model.rtw file. Within the TLC file for the S-function, you can access run-time parameters and their attributes using the LibBlockParameter 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

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 model.c containing noninlined and inlined versions of S-function foogain. 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.

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 model.c file for the noninlined S-function (i.e., no corresponding TLC file exists).

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 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 */

If you include this target file for this S-function block, the resulting model.c code is

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.

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 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 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 model.rtw file for you automatically, including the DWork 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.

Related Topics