Main Content

Write Wrapper S-Function and TLC Files

Create S-functions that work seamlessly with the Simulink® and code generator products by using the wrapper concept. You can:

  • Interface your algorithms in Simulink models by writing MEX S-function wrappers (sfunction.mex).

  • Direct the code generator to insert your algorithm into the generated code by creating a TLC S-function wrapper (sfunction.tlc).

MEX S-Function Wrapper

Creating S-functions by using an S-function wrapper enables you to insert C/C++ code algorithms in Simulink models and the generated code with little or no change to your original C/C++ function. A MEX S-function wrapper is an S-function that calls code, which resides in another module.

Note

Use a MEX S-function wrapper only in the MATLAB® version in which you created the wrapper.

Suppose that you have an algorithm (that is, a C function) called my_alg that resides in the file my_alg.c. You can integrate my_alg into a Simulink model by creating a MEX S-function wrapper (for example, wrapsfcn.c). A Simulink model can then call my_alg from an S-Function block. The Simulink S-function contains a set of empty functions that the Simulink engine requires for various API related purposes. For example, although only mdlOutputs calls my_alg, the engine calls mdlTerminate, even though this S-function routine performs no action.

You can embed the call to my_alg in the generated code by creating a TLC S-function wrapper (for example, wrapsfcn.tlc). You can eliminate the empty function calls. You can avoid the overhead of executing the mdlOutputs function and you can then eliminate the my_alg function.

Wrapper S-functions are useful when you are creating algorithms that are procedural or when you are integrating legacy code into a Simulink model. If you want to create code that is:

  • Interpretive in nature (that is, highly parameterized by operating modes)

  • Heavily optimized (that is, no extra tests to decide what mode the code is operating in)

then you must create a fully inlined TLC file for your S-function.

The next figure shows the wrapper S-function concept.

Using an S-function wrapper to import algorithms into your Simulink model means that the S-function serves as an interface that calls your C/C++ algorithms from mdlOutputs. You can quickly integrate large standalone C/C++ programs into your model without having to change the code.

This sample model includes an S-function wrapper.

Two files are associated with the wrapsfcn block: the S-function wrapper and the C/C++ code that contains the algorithm. The first three statements:

  1. Define the name of the S-function (what you enter in the Simulink S-Function block dialog box).

  2. Specify that the S-function is using the level 2 format.

  3. Provide access to the SimStruct data structure. SimStruct contains pointers to data used during simulation and code generation and defines macros that store data in and retrieve data from the SimStruct.

#define S_FUNCTION_NAME wrapsfcn
#define S_FUNCTION_LEVEL 2
#include "simstruc.h"

extern real_T my_alg(real_T u);  /* Declare my_alg as extern */

/*
 * mdlInitializeSizes - initialize the sizes array
 */
static void mdlInitializeSizes(SimStruct *S)
{

    ssSetNumSFcnParams( S, 0); /*number of input arguments*/

    if (!ssSetNumInputPorts(S, 1)) return;
    ssSetInputPortWidth(S, 0, 1);
    ssSetInputPortDirectFeedThrough(S, 0, 1);

    if (!ssSetNumOutputPorts(S,1)) return;
    ssSetOutputPortWidth(S, 0, 1);
    
    ssSetNumSampleTimes( S, 1);
}

/*
 * mdlInitializeSampleTimes - indicate that this S-function runs
 * at the rate of the source (driving block)
 */
static void mdlInitializeSampleTimes(SimStruct *S)
{
    ssSetSampleTime(S, 0, INHERITED_SAMPLE_TIME);
    ssSetOffsetTime(S, 0, 0.0);
} 


/*
 * mdlOutputs - compute the outputs by calling my_alg, which
 * resides in another module, my_alg.c
 */
static void mdlOutputs(SimStruct *S, int_T tid)
{
    InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);
    real_T            *y    = ssGetOutputPortRealSignal(S,0);
    *y = my_alg(*uPtrs[0]); /* Call my_alg in mdlOutputs */
 }
/*
 * mdlTerminate - called when the simulation is terminated.
 */
static void mdlTerminate(SimStruct *S)
{
}

#ifdef MATLAB_MEX_FILE /* Is this file being compiled as a MEX-file? */
#include "simulink.c" /* MEX-file interface mechanism */
#else
#include "cg_sfun.h" /* Code generation registration function */
#endif

For more information, see Templates for C S-Functions.

The S-function routine mdlOutputs contains a function call to my_alg, which is the C function containing the algorithm that the S-function performs. For my_alg.c, the code is:

#ifdef MATLAB_MEX_FILE
#include "tmwtypes.h"
#else
#include "rtwtypes.h"
#endif
real_T my_alg(real_T u)
{
return(u * 2.0);
}

For more information, see Manage Build Process File Dependencies.

The wrapper S-function wrapsfcn calls my_alg, which computes u * 2.0. To build wrapsfcn.mex, use this command:

mex wrapsfcn.c my_alg.c

TLC S-Function Wrapper

A TLC S-function wrapper is a TLC file that specifies how the code generator calls your code. For example, you can inline the call to my_alg in the mdlOutputs section of the generated code. In the MEX S-Function Wrapper example, the call to my_alg is embedded in the mdlOutputs section as:

*y = my_alg(*uPtrs[0]);

When you are creating a TLC S-function wrapper, the goal is to embed the same type of call in the generated code.

Look at how the code generator executes S-functions that are not inlined. A noninlined S-function is identified by the absence of the file sfunction.tlc and the existence of sfunction.mex. When generating code for a noninlined S-function, the code generator produces a call to mdlOutputs through a function pointer that, in this example, then calls my_alg.

The wrapper example contains one S-function, wrapsfcn.mex. You must compile and link an additional module, my_alg, with the generated code. At the MATLAB command prompt, enter:

set_param('wrapper/S-Function','SFunctionModules','my_alg')

Code Overhead for Noninlined S-Functions

The code generated when using grt.tlc as the system target file without wrapsfcn.tlc is:

<Generated code comments for wrapper model with noninlined wrapsfcn S-function>

#include <math.h>
#include <string.h>
#include "wrapper.h"
#include "wrapper.prm"

/* Start the model */
void mdlStart(void)
{
  /* (start code not required) */
}

/* Compute block outputs */
void mdlOutputs(int_T tid)
{
  /* Sin Block: <Root>/Sin */
  rtB.Sin = rtP.Sin.Amplitude *
    sin(rtP.Sin.Frequency * ssGetT(rtS) + rtP.Sin.Phase);

  /* Level2 S-Function Block: <Root>/S-Function (wrapsfcn) */
  {
    /* Noninlined S-functions create a SimStruct object and
     * generate a call to S-function routine mdlOutputs
     */
    SimStruct *rts = ssGetSFunction(rtS, 0);
    sfcnOutputs(rts, tid);
  }

  /* Outport Block: <Root>/Out */
  rtY.Out = rtB.S_Function;
}

/* Perform model update */
void mdlUpdate(int_T tid)
{
  /* (update code not required) */
}

/* Terminate function */
void mdlTerminate(void)
{
  /* Level2 S-Function Block: <Root>/S-Function (wrapsfcn) */
  {
/* Noninlined S-functions require a SimStruct object and
     * the call to S-function routine mdlTerminate
     */
    SimStruct *rts = ssGetSFunction(rtS, 0);
    sfcnTerminate(rts);
  }
}

#include "wrapper.reg"

/* [EOF] wrapper.c */

The wrapper.reg generated file contains the initialization of the SimStruct for the wrapper S-Function block. There is one child SimStruct for each S-Function block in your model. You can significantly reduce this overhead by creating a TLC wrapper for the S-function.

Inline a Wrapper S-Function

The generated code makes the call to your S-function, wrapsfcn.c, in mdlOutputs by using this code:

SimStruct *rts = ssGetSFunction(rtS, 0);
sfcnOutputs(rts, tid);

This call has computational overhead associated with it. The Simulink engine creates a SimStruct data structure for the S-Function block. The code generator constructs a call through a function pointer to execute mdlOutputs, then mdlOutputs calls my_alg. By inlining the call to your C/C++ algorithm, my_alg, you can eliminate both the SimStruct and the extra function call, thereby improving the efficiency and reducing the size of the generated code.

Inlining a wrapper S-function requires an sfunction.tlc file for the S-function. The TLC file must contain the function call to my_alg. The figure shows the relationships between the algorithm, the wrapper S-function, and the sfunction.tlc file.

To inline the call to my_alg, place your function call in an sfunction.tlc file with the same name as the S-function (in this example, wrapsfcn.tlc). The Target Language Compiler overrides the default method of placing calls to your S-function in the generated code.

This code is the TLC file wrapsfcn.tlc that inlines wrapsfcn.c:

%% File    : wrapsfcn.tlc
%% Abstract:
%%      Example inlined tlc file for S-function wrapsfcn.c
%%

%implements "wrapsfcn" "C"

%% Function: BlockTypeSetup ====================================================
%% Abstract:
%%      Create function prototype in model.h as:
%%      "extern real_T my_alg(real_T u);" 
%%
%function BlockTypeSetup(block, system) void
  %openfile buffer
    extern real_T my_alg(real_T u); /* This line is placed in wrapper.h */
  %closefile buffer
  %<LibCacheFunctionPrototype(buffer)>
%endfunction %% BlockTypeSetup

%% Function: Outputs ===========================================================
%% Abstract:
%%      y = my_alg( u );
%%
%function Outputs(block, system) Output
  /* %<Type> Block: %<Name> */
  %assign u = LibBlockInputSignal(0, "", "", 0)
  %assign y = LibBlockOutputSignal(0, "", "", 0)
  %% PROVIDE THE CALLING STATEMENT FOR "algorithm"
  %% The following line is expanded and placed in mdlOutputs within wrapper.c
  %<y> = my_alg(%<u>); 

%endfunction %% Outputs

The first section of this code inlines the wrapsfcn S-Function block and generates the code in C:

%implements "wrapsfcn" "C"

The next task is to inform the code generator that the routine my_alg must be declared as external in the generated wrapper.h file for any wrapsfcn S-Function blocks in the model. Do this declaration once for all wrapsfcn S-Function blocks by using the BlockTypeSetup function. In this function, you direct the Target Language Compiler to create a buffer and cache the my_alg as extern in the wrapper.h generated header file.

The final step is the inlining of the call to the function my_alg. The Outputs function inlines the call. In this function, you access the block input and output and place a direct call to my_alg. The call is embedded in wrapper.c.

The Inlined Code

The code generated when you inline your wrapper S-function is similar to the default generated code. The mdlTerminate function does not contain a call to an empty function and the mdlOutputs function now directly calls my_alg.

void mdlOutputs(int_T tid)
{
  /* Sin Block: <Root>/Sin */
  rtB.Sin = rtP.Sin.Amplitude *
    sin(rtP.Sin.Frequency * ssGetT(rtS) + rtP.Sin.Phase);

  /* S-Function Block: <Root>/S-Function */
  rtB.S_Function = my_alg(rtB.Sin); /* Inlined call to my_alg */

  /* Outport Block: <Root>/Out */
  rtY.Out = rtB.S_Function;
}

wrapper.reg does not create a child SimStruct for the S-function because the generated code is calling my_alg directly, eliminating over 1 KB of memory usage.

Related Topics