Skip to Main Content Skip to Search
Product Documentation

Insert S-Function Code

About S-Functions and Code Generation

This chapter describes how to create S-functions that work seamlessly with Simulink Coder code generation. It begins with basic concepts and concludes with an example of how to create a highly optimized direct-index lookup table S-Function block.

This chapter assumes that you understand the following concepts:

Additional Information

See the Target Language Compiler documentation and other Simulink Coder documentation for more information on the code generation process.

See Inlining S-Functions in the Target Language Compiler documentation for additional information on inlining S-functions.

Classes of Problems Solved by S-Functions

S-functions help solve various kinds of problems you might face when working with the Simulink and Simulink Coder products. These problems include

S-functions are written using an application program interface (API) that allows you to implement generic algorithms in the Simulink environment with a great deal of flexibility. This flexibility cannot always be maintained when you use S-functions with the Simulink Coder code generator. For example, it is not possible to access the MATLAB workspace from an S-function that is used with the code generator. However, using the techniques presented in this chapter, you can create S-functions for most applications that work with the Simulink Coder generated code.

Although S-functions provide a generic and flexible solution for implementing complex algorithms in a Simulink model, the underlying API incurs overhead in terms of memory and computation resources. Most often the additional resources are acceptable for real-time rapid prototyping systems. In many cases, though, additional resources are unavailable in real-time embedded applications. You can minimize memory and computational requirements by using the Target Language Compiler technology provided with the Simulink Coder product to inline your S-functions. If you are producing an S-function for existing code, consider using the Simulink Legacy Code Tool.

Types of S-Functions

The implementation of S-functions changes based on your requirements. This chapter discusses the typical problems that you may face and how to create S-functions for applications that need to work with the Simulink and Simulink Coder products. These are some (informally defined) common situations:

  1. "I'm not concerned with efficiency. I just want to write one version of my algorithm and have it work in the Simulink and Simulink Coder products automatically."

  2. "I have a lot of hand-written code that I need to interface. I want to call my function from the Simulink and Simulink Coder products in an efficient manner."

    or said another way:

    "I want to create a block for my blockset that will be distributed throughout my organization. I'd like it to be very maintainable with efficient code. I'd like my algorithm to exist in one place but work with both the Simulink and Simulink Coder products."

  3. "I want to implement a highly optimized algorithm in the Simulink and Simulink Coder products that looks like a built-in block and generates very efficient code."

MathWorks products have adopted terminology for these different requirements. Respectively, the situations described above map to this terminology:

  1. Noninlined S-function

  2. Wrapper S-function

  3. Fully inlined S-function

Noninlined S-Functions.  A noninlined S-function is a C or C++ MEX S-function that is treated identically by the Simulink engine and Simulink Coder generated code. In general, you implement your algorithm once according to the S-function API. The Simulink engine and Simulink Coder generated code call the S-function routines (for example, mdlOutputs) during model execution.

Additional memory and computation resources are required for each instance of a noninlined S-Function block. However, this routine of incorporating algorithms into Simulink models and Simulink Coder applications is typical during the prototyping phase of a project where efficiency is not important. The advantage gained by forgoing efficiency is the ability to change model parameters and structures rapidly.

Writing a noninlined S-function does not involve any TLC coding. Noninlined S-functions are the default case for the Simulink Coder build process in the sense that once you build a MEX S-function in your model, there is no additional preparation prior to clicking Build in the Code Generation pane of the Configuration Parameters dialog box for your model.

Some restrictions exist concerning the names and locations of noninlined S-function files when generating makefiles. See Write Noninlined S-Functions.

Wrapper S-Functions.  A wrapper S-function is ideal for interfacing hand-written code or a large algorithm that is encapsulated within a few procedures. In this situation, usually the procedures reside in modules that are separate from the MEX S-function. The S-function module typically contains a few calls to your procedures. Because the S-function module does not contain any parts of your algorithm, but only calls your code, it is referred to as a wrapper S-function.

In addition to the MEX S-function wrapper, you need to create a TLC wrapper that complements your S-function. The TLC wrapper is similar to the S-function wrapper in that it contains calls to your algorithm.

Fully Inlined S-Functions.  For S-functions to work in the Simulink environment, some overhead code is generated. When the Simulink Coder software generates code from models that contain S-functions (without sfunction.tlc files), it embeds some of this overhead code in the generated code. If you want to optimize your real-time code and eliminate some of the overhead code, you must inline (or embed) your S-functions. This involves writing a TLC (sfunction.tlc) file that eliminates all overhead code from the generated code. The Target Language Compiler processes sfunction.tlc files to define how to inline your S-function algorithm in the generated code.

A fully inlined S-function builds your algorithm (block) into Simulink Coder generated code in a manner that is indistinguishable from a built-in block. Typically, a fully inlined S-function requires you to implement your algorithm twice: once for the Simulink model (C/C++ MEX S-function) and once for Simulink Coder code generation (TLC file). The complexity of the TLC file depends on the complexity of your algorithm and the level of efficiency you're trying to achieve in the generated code. TLC files vary from simple to complex in structure.

The Simulink Legacy Code Tool can automate the generation of a fully inlined S-function and a corresponding TLC file based on information that you register in a Legacy Code Tool data structure. For more information, see Integrating Existing C Functions into Simulink Models with the Legacy Code Tool in the Simulink Writing S-Functions documentation and Integrate External Code Using Legacy Code Tool.

Basic Files Required for Implementation

This section briefly describes what files and functions you need to create noninlined, wrapper, and fully inlined S-functions.

Fully inlined S-functions might require the placement of the mdlRTW routine in your S-function MEX-file sfunction.c or sfunction.cpp. The mdlRTW routine lets you place information in model.rtw, the record file that specifies a model, and which the Simulink Coder code generator invokes the Target Language Compiler to process prior to executing sfunction.tlc when generating code.

Including a mdlRTW routine is useful when you want to introduce nontunable parameters into your TLC file. Such parameters are generally used to determine which operating mode is active in a given instance of the S-function. Based on this information, the TLC file for the S-function can generate highly efficient, optimal code for that operating mode.

Guidelines for Writing S-Functions for Use with Simulink Coder Software

Write Noninlined S-Functions

About Noninlined S-Functions

Noninlined S-functions are identified by the absence of an sfunction.tlc file for your S-function. The filename varies depending on your platform. For example, on a 32–bit Microsoft Windows system, the file name would be sfunction.mexw32. Type mexext in the MATLAB Command Window to see which extension your system uses.

Guidelines for Writing Noninlined S-Functions

Noninlined S-Function Parameter Type Limitations

Parameters to noninlined S-functions can be of the following types only:

For more flexibility in the type of parameters you can supply to S-functions or the operations in the S-function, inline your S-function and consider using an mdlRTW S-function routine.

Use of other functions from the MATLAB matrix.h API or other MATLAB APIs, such as mex.h and mat.h, is not supported. If you call unsupported APIs from an S-function source file, compiler errors occur. See the file matlabroot/rtw/c/src/rt_matrx.h(.c) for details on supported MATLAB API functions.

If you use mxGetPr on an empty matrix, the function does not return NULL; rather, it returns a random value. Therefore, you should protect calls to mxGetPr with mxIsEmpty.

Write Wrapper S-Functions

About Wrapper S-Functions

This section describes how to create S-functions that work seamlessly with the Simulink and Simulink Coder products using the wrapper concept. This section begins by describing how to interface your algorithms in Simulink models by writing MEX S-function wrappers (sfunction.mex). It finishes with a description of how to 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 using an S-function wrapper allows you to insert C/C++ code algorithms in Simulink models and Simulink Coder 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 that resides in another module. A TLC S-function wrapper is a TLC file that specifies how the code generator should call your code (the same code that was called from the C MEX S-function wrapper).

Suppose 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). Once this is done, a Simulink model can call my_alg from an S-Function block. However, 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 as well, even though this S-function routine performs no action.

You can integrate my_alg into generated code (that is, embed the call to my_alg in the generated code) by creating a TLC S-function wrapper (for example, wrapsfcn.tlc). The advantage of creating a TLC S-function wrapper is that the empty function calls can be eliminated and the overhead of executing the mdlOutputs function and then the my_alg function can be eliminated.

Wrapper S-functions are useful when you are creating new algorithms that are procedural in nature or when you are integrating legacy code into a Simulink model. However, if you want to create code that is

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 in your Simulink model means that the S-function serves as an interface that calls your C/C++ algorithms from mdlOutputs. S-function wrappers have the advantage that you can quickly integrate large standalone C /C++ programs into your model without having to make changes to the code.

The following sample model includes an S-function wrapper.

There are two files associated with the wrapsfcn block, the S-function wrapper and the C/C++ code that contains the algorithm. The S-function wrapper code for wrapsfcn.c appears below. The first three statements do the following:

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

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

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

For more information, see Templates for C S-Functions in the Simulink documentation.

#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

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. This is the code for my_alg.c:

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

See the section Header Dependencies When Interfacing Legacy/Custom Code with Generated Code in the Simulink Coder documentation for more information.

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

mex wrapsfcn.c my_alg.c

TLC S-Function Wrapper

This section describes how to inline the call to my_alg in the mdlOutputs section of the generated code. In the above 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.

It is instructive to 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 Simulink Coder software generates 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. To do this, specify

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)
{
  /* (no start code 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)
{
  /* (no update code 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 */

In addition to the overhead outlined above, 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.

How to Inline.  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. First, the Simulink engine creates a SimStruct data structure for the S-Function block. Second, 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 (see the Target Language Compiler documentation for details). The TLC file must contain the function call to my_alg. The following figure shows the relationships between the algorithm, the wrapper S-function, and the sfunction.tlc file.

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

This is the wrapsfcn.tlc file 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 tell the code generator that the routine my_alg needs to be declared external in the generated wrapper.h file for any wrapsfcn S-Function blocks in the model. You only need to do this once for all wrapsfcn S-Function blocks, so use the BlockTypeSetup function. In this function, you tell 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. This is done by the Outputs function. In this function, you access the block's 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 no longer contains 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;
}

In addition, wrapper.reg no longer creates a child SimStruct for the S-function because the generated code is calling my_alg directly. This eliminates over 1 KB of memory usage.

Write Fully Inlined S-Functions

Continuing the example of the previous section, you could eliminate the call to my_alg entirely by specifying the explicit code (that is, 2.0 * u) in wrapsfcn.tlc. This is referred to as a fully inlined S-function. While this can improve performance, if you are working with a large amount of C/C++ code, this can be a lengthy task. In addition, you now have to maintain your algorithm in two places, the C/C++ S-function itself and the corresponding TLC file. However, the performance gains might outweigh the disadvantages. To inline the algorithm used in this example, in the Outputs section of your wrapsfcn.tlc file, instead of writing

%<y> = my_alg(%<u>);

use

%<y> = 2.0 * %<u>;

This is the code produced in mdlOutputs:

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 = 2.0 * rtB.Sin; /* Explicit embedding of algorithm */

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

The Target Language Compiler has replaced the call to my_alg with the algorithm itself.

Multiport S-Function Example

A more advanced multiport inlined S-function example is sfun_multiport.c and sfun_multiport.tlc. This S-function demonstrates how to create a fully inlined TLC file for an S-function that contains multiple ports. You might find that looking at this example helps you to understand fully inlined TLC files.

Write Fully Inlined S-Functions with the mdlRTW Routine

About S-Functions and mdlRTW

You can inline more complex S-functions that use the S-function mdlRTW routine. The purpose of the mdlRTW routine is to provide the code generation process with more information about how the S-function is to be inlined, by creating a parameter record of a nontunable parameter for use with a TLC file. The mdlRTW routine does this by placing information in the model.rtw file. The mdlRTW function is described in the text file matlabroot/simulink/src/sfuntmpl_doc.c.

As an example of how to use the mdlRTW function, this section discusses the steps you must take to create a direct-index lookup S-function. Lookup tables are collections of ordered data points of a function. Typically, these tables use some interpolation scheme to approximate values of the associated function between known data points. To incorporate the example lookup table algorithm into a Simulink model, the first step is to write an S-function that executes the algorithm in mdlOutputs. To produce the most efficient code, the next step is to create a corresponding TLC file to eliminate computational overhead and improve the performance of the lookup computations.

For your convenience, the Simulink product provides support for two general-purpose lookup 1-D and 2-D algorithms. You can use these algorithms as they are or create a custom lookup table S-function to fit your requirements. This section demonstrates how to create a 1-D lookup S-function, sfun_directlook.c, and its corresponding inlined sfun_directlook.tlc file. (See the Target Language Compiler documentation for more details on the Target Language Compiler.) This 1-D direct-index lookup table example demonstrates the following concepts that you need to know to create your own custom lookup tables:

S-Function RTWdata

There is a property of blocks called RTWdata, which can be used by the Target Language Compiler when inlining an S-function. RTWdata is a structure of strings that you can attach to a block. It is saved with the model and placed in the model.rtw file when generating code. For example, this set of MATLAB commands,

mydata.field1 = 'information for field1';
mydata.field2 = 'information for field2';
set_param(gcb,'RTWdata',mydata)
get_param(gcb,'RTWdata')

produces this result:

ans = 
 
    field1: 'information for field1'
    field2: 'information for field2'

Inside the model.rtw file for the associated S-Function block is this information.

Block {
  Type                    "S-Function"
  RTWdata {
    field1                  "information for field1"
    field2                  "information for field2"
  }

Direct-Index Lookup Table Algorithm

The 1-D lookup table block provided in the Simulink library uses interpolation or extrapolation when computing outputs. This extra accuracy might not be required. In this example, you create a lookup table that directly indexes the output vector (y-data vector) based on the current input (x-data) point.

This direct 1-D lookup example computes an approximate solution p(x) to a partially known function f(x) at x=x0, given data point pairs (x,y) in the form of an x-data vector and a y-data vector. For a given data pair (for example, the i'th pair), y_i = f(x_i). It is assumed that the x-data values are monotonically increasing. If x0 is outside the range of the x-data vector, the first or last point is returned.

The parameters to the S-function are

XData, YData, XEvenlySpaced

XData and YData are double vectors of equal length representing the values of the unknown function. XDataEvenlySpaced is a scalar, 0.0 for false and 1.0 for true. If the XData vector is evenly spaced, XDataEvenlySpaced is 1.0 and more efficient code is generated.

The following graph shows how the parameters XData=[1:6]and YData=[1,2,7,4,5,9] are handled. For example, if the input (x-value) to the S-Function block is 3, the output (y-value) is 7.

Direct-Index Lookup Table Example

This section shows how to improve the lookup table by inlining a direct-index S-function with a TLC file. This direct-index lookup table S-function does not require a TLC file. Here the example uses a TLC file for the direct-index lookup table S-function to reduce the code size and increase efficiency of the generated code.

Implementation of the direct-index algorithm with inlined TLC file requires the S-function main module, sfun_directlook.c, and a corresponding lookup_index.c module. The lookup_index.c module contains the GetDirectLookupIndex function that is used to locate the index in the XData for the current x input value when the XData is unevenly spaced. The GetDirectLookupIndex routine is called from both the S-function and the generated code. Here the example uses the wrapper concept for sharing C/C++ code between Simulink MEX-files and the generated code.

If the XData is evenly spaced, then both the S-function main module and the generated code contain the lookup algorithm (not a call to the algorithm) to compute the y-value of a given x-value, because the algorithm is short. This demonstrates the use of a fully inlined S-function for generating optimal code.

The inlined TLC file, which either performs a wrapper call or embeds the optimal C/C++ code, is sfun_directlook.tlc (see the example in mdlRTW Usage).

Error Handling.  In this example, the mdlCheckParameters routine verifies that

The mdlInitializeSizes function explicitly calls mdlCheckParameters after it verifies the number of parameters passed to the S-function. After the Simulink engine calls mdlInitializeSizes, it then calls mdlCheckParameters whenever you change the parameters or there is a need to reevaluate them.

User Data Caching.  The mdlStart routine shows how to cache information that does not change during the simulation (or while the generated code is executing). The example caches the value of the XDataEvenlySpaced parameter in UserData, a field of the SimStruct. The following line in mdlInitializeSizes tells the Simulink engine to disallow changes to XDataEvenlySpaced.

ssSetSFcnParamTunable(S, iParam, SS_PRM_NOT_TUNABLE);

During execution, mdlOutputs accesses the value of XDataEvenlySpaced from UserData rather than calling the mxGetPr MATLAB API function. This increases performance.

mdlRTW Usage.  The Simulink Coder code generator calls the mdlRTW routine while generating the model.rtw file. To produce optimal code for your Simulink model, you can add information to the model.rtw file about the mode in which your S-Function block is operating.

The following example adds parameter settings to the model.rtw file. The parameter settings do not change during execution. In this case, the XDataEvenlySpaced S-function parameter cannot change during execution (ssSetSFcnParamTunable was specified as false (0) for it in mdlInitializeSizes). The example writes it out as a parameter setting (XSpacing) using the function ssWriteRTWParamSettings.

Because xData and yData are registered as run-time parameters in mdlSetWorkWidths, the code generator handles writing to the model.rtw file automatically.

Before examining the S-function and the inlined TLC file, consider the generated code for the following model.

The model uses evenly spaced XData in the top S-Function block and unevenly spaced XData in the bottom S-Function block. When creating this model, you need to specify the following for each S-Function block.

set_param(`sfun_directlook_ex/S-Function','SFunctionModules','lookup_index')
set_param(`sfun_directlook_ex/S-Function1','SFunctionModules','lookup_index')

This informs the Simulink Coder build process to use the module lookup_index.c when creating the executable.

When generating code for this model, the Simulink Coder software uses the S-function's mdlRTW method to generate a model.rtw file with the value EvenlySpaced for the XSpacing parameter for the top S-Function block, and the value UnEvenlySpaced for the XSpacing parameter for the bottom S-Function block. The TLC-file uses the value of XSpacing to determine what algorithm to include in the generated code. The generated code contains the lookup algorithm when the XData is evenly spaced, but calls the GetDirectLookupIndex routine when the XData is unevenly spaced. The generated model.c or model.cpp code for the lookup table example model is similar to the following:

/*
 * sfun_directlook_ex.c
 * 
 * Code generation for Simulink model 
 * "sfun_directlook_ex.mdl".
 *
...
 */

#include "sfun_directlook_ex.h"
#include "sfun_directlook_ex_private.h"

/* External output (root outports fed by signals with auto storage) */
ExternalOutputs_sfun_directlook_ex sfun_directlook_ex_Y;

/* Real-time model */
rtModel_sfun_directlook_ex sfun_directlook_ex_M_;
rtModel_sfun_directlook_ex *sfun_directlook_ex_M = &sfun_directlook_ex_M_;

/* Model output function */
static void sfun_directlook_ex_output(int_T tid)
{

  /* local block i/o variables */

  real_T rtb_SFunction_h;
  real_T rtb_temp1;

  /* Sin: '<Root>/Sine Wave' */
  rtb_temp1 = sfun_directlook_ex_P.SineWave_Amp *
    sin(sfun_directlook_ex_P.SineWave_Freq * sfun_directlook_ex_M->Timing.t[0] +
    sfun_directlook_ex_P.SineWave_Phase) + sfun_directlook_ex_P.SineWave_Bias;

  /* Code that is inlined for the top S-function block in the 
   * sfun_directlook_ex model
   */
  /* S-Function Block: <Root>/S-Function */
  {
    const real_T *xData = &sfun_directlook_ex_P.SFunction_XData[0];
    const real_T *yData = &sfun_directlook_ex_P.SFunction_YData[0];
    real_T spacing = xData[1] - xData[0];

    if ( rtb_temp1 <= xData[0] ) {
      rtb_SFunction_h = yData[0];
    } else if ( rtb_temp1 >= yData[20] ) {
      rtb_SFunction_h = yData[20];
    } else {
      int_T idx = (int_T)( ( rtb_temp1 - xData[0] ) / spacing );
      rtb_SFunction_h = yData[idx];
    }
  }

  /* Outport: '<Root>/Out1' */
  sfun_directlook_ex_Y.Out1 = rtb_SFunction_h;

/* Code that is inlined for the bottom S-function block in the 
   * sfun_directlook_ex model
   */
  /* S-Function Block: <Root>/S-Function1 */
  {
    const real_T *xData = &sfun_directlook_ex_P.SFunction1_XData[0];
    const real_T *yData = &sfun_directlook_ex_P.SFunction1_YData[0];
    int_T idx;

    idx = GetDirectLookupIndex(xData, 5, rtb_temp1);
    rtb_temp1 = yData[idx];
  }

  /* Outport: '<Root>/Out2' */
  sfun_directlook_ex_Y.Out2 = rtb_temp1;
}

/* Model update function */
static void sfun_directlook_ex_update(int_T tid)
{

  /* Update absolute time for base rate */

  if(!(++sfun_directlook_ex_M->Timing.clockTick0))
  ++sfun_directlook_ex_M->Timing.clockTickH0;
  sfun_directlook_ex_M->Timing.t[0] = sfun_directlook_ex_M->Timing.clockTick0 *
    sfun_directlook_ex_M->Timing.stepSize0 +
    sfun_directlook_ex_M->Timing.clockTickH0 *
    sfun_directlook_ex_M->Timing.stepSize0 * 0x10000;

  {
    /* Update absolute timer for sample time: [0.1s, 0.0s] */

    if(!(++sfun_directlook_ex_M->Timing.clockTick1))
    ++sfun_directlook_ex_M->Timing.clockTickH1;
    sfun_directlook_ex_M->Timing.t[1] = sfun_directlook_ex_M->Timing.clockTick1
      * sfun_directlook_ex_M->Timing.stepSize1 +
      sfun_directlook_ex_M->Timing.clockTickH1 *
      sfun_directlook_ex_M->Timing.stepSize1 * 0x10000;
  }
}
...

matlabroot/toolbox/simulink/simdemos/simfeatures/src/sfun_directlook.c.  

/*
* File    :  sfun_directlook.c
 * Abstract:
 *
 *      Direct 1-D lookup. Here we are trying to compute an approximate
 *      solution, p(x) to an unknown function f(x) at x=x0, given data point 
 *      pairs (x,y) in the form of a x data vector and a y data vector. For a
 *      given data pair (say the i'th pair), we have y_i = f(x_i). It is 
 *      assumed that the x data values are monotonically increasing.  If the 
 *      x0 is outside of the range of the x data vector, then the first or 
 *      last point will be returned.
 *
 *      This function returns the "nearest" y0 point for a given x0. No
 *      interpolation is performed.
 *
 *      The S-function parameters are:
 *        XData              - double vector
 *        YData              - double vector
 *        XDataEvenlySpacing - double scalar 0 (false) or 1 (true)
 *        The third parameter cannot be changed during simulation.
 *
 *      To build:
 *        mex sfun_directlook.c lookup_index.c
 *
 * Copyright 1990-2004 The MathWorks, Inc.
 * $Revision: 1.1.4.7 $
 */

#define S_FUNCTION_NAME  sfun_directlook
#define S_FUNCTION_LEVEL 2


#include <math.h>
#include "simstruc.h"
#include <float.h>

/* use utility function IsRealVect() */
#if defined(MATLAB_MEX_FILE)
#include "sfun_slutils.h"
#endif

/*================*
 * Build checking *
 *================*/
#if !defined(MATLAB_MEX_FILE)
/*
 * This file cannot be used directly with Simulink Coder. However,
 * this S-function does work with Simulink Coder via
 * the Target Language Compiler technology. See matlabroot/
 * toolbox/simulink/simdemos/simfeatures/tlc_c/sfun_directlook.tlc
 * for the C version
 */
# error This_file_can_be_used_only_during_simulation_inside_Simulink
#endif


/*=========*
 * Defines *
 *=========*/

#define XVECT_PIDX             0
#define YVECT_PIDX             1
#define XDATAEVENLYSPACED_PIDX 2
#define NUM_PARAMS             3

#define XVECT(S)             ssGetSFcnParam(S,XVECT_PIDX)
#define YVECT(S)             ssGetSFcnParam(S,YVECT_PIDX)
#define XDATAEVENLYSPACED(S) ssGetSFcnParam(S,XDATAEVENLYSPACED_PIDX)


/*==============*
 * misc defines *
 *==============*/
#if !defined(TRUE)
#define TRUE  1
#endif
#if !defined(FALSE)
#define FALSE 0
#endif

/*===========*
 * typedef's *
 *===========*/

typedef struct SFcnCache_tag {
    boolean_T evenlySpaced;
} SFcnCache;

/*===================================================================*
 * Prototype define for the function in separate file lookup_index.c *
 *===================================================================*/
extern int_T GetDirectLookupIndex(const real_T *x, int_T xlen, real_T u);


/*====================*
 * S-function methods *
 *====================*/


#define MDL_CHECK_PARAMETERS           /* Change to #undef to remove function */
#if defined(MDL_CHECK_PARAMETERS) && defined(MATLAB_MEX_FILE)
/* Function: mdlCheckParameters ================================================
 * Abstract:
 *    This routine will be called after mdlInitializeSizes, whenever
 *    parameters change or get re-evaluated. The purpose of this routine is
 *    to verify  the new parameter settings.
 *
 *    You should add a call to this routine from mdlInitalizeSizes
 *    to check the parameters. After setting your sizes elements, you should:
 *       if (ssGetSFcnParamsCount(S) == ssGetNumSFcnParams(S)) {
 *           mdlCheckParameters(S);
 *       }
 */
static void mdlCheckParameters(SimStruct *S)
{

    if (!IsRealVect(XVECT(S))) {
        ssSetErrorStatus(S,"1st, X-vector parameter must be a real finite "
                         " vector");
        return;
    }

    if (!IsRealVect(YVECT(S))) {
        ssSetErrorStatus(S,"2nd, Y-vector parameter must be a real finite "
                         "vector");
        return;
    }

    /*
     * Verify that the dimensions of X and Y are the same.
     */
    if (mxGetNumberOfElements(XVECT(S)) != mxGetNumberOfElements(YVECT(S)) ||
        mxGetNumberOfElements(XVECT(S)) == 1) {
        ssSetErrorStatus(S,"X and Y-vectors must be of the same dimension "
                         "and have at least two elements");
        return;
    }

    /*
     * Verify we have a valid XDataEvenlySpaced parameter.
     */
    if ((!mxIsNumeric(XDATAEVENLYSPACED(S)) && 
          !mxIsLogical(XDATAEVENLYSPACED(S))) ||
        mxIsComplex(XDATAEVENLYSPACED(S)) ||
        mxGetNumberOfElements(XDATAEVENLYSPACED(S)) != 1) {
        ssSetErrorStatus(S,"3rd, X-evenly-spaced parameter must be logical 
scalar");
        return;
    }

    /*
     * Verify x-data is correctly spaced.
     */
    {
        int_T     i;
        boolean_T spacingEqual;
        real_T    *xData = mxGetPr(XVECT(S));
        int_T     numEl  = mxGetNumberOfElements(XVECT(S));

        /*
         * spacingEqual is TRUE if user  XDataEvenlySpaced 
         */
        spacingEqual = (mxGetScalar(XDATAEVENLYSPACED(S)) != 0.0);

        if (spacingEqual) {    /* XData is 'evenly-spaced' */
            boolean_T badSpacing = FALSE;
            real_T    spacing    = xData[1] - xData[0];
            real_T    space;

            if (spacing <= 0.0) {
                badSpacing = TRUE;
            } else {
                real_T eps = DBL_EPSILON;

                for (i = 2; i < numEl; i++) {
                    space = xData[i] - xData[i-1];
                    if (space <= 0.0 || 
                        fabs(space-spacing) >= 128.0*eps*spacing ){
                        badSpacing = TRUE;
                        break;
                    }
                }
            }

            if (badSpacing) {
                ssSetErrorStatus(S,"X-vector must be an evenly spaced "
                                 "strictly monotonically increasing vector");
                return;
            }
        } else {     /* XData is 'unevenly-spaced' */
            for (i = 1; i < numEl; i++) {
                if (xData[i] <= xData[i-1]) {
                    ssSetErrorStatus(S,"X-vector must be a strictly "
                                     "monotonically increasing vector");
                    return;
                }
            }
        }
    }
}
#endif /* MDL_CHECK_PARAMETERS */



/* Function: mdlInitializeSizes ================================================
 * Abstract:
 *    The sizes information is used by Simulink to determine the S-function
 *    block's characteristics (number of inputs, outputs, states, and so on).
 */
static void mdlInitializeSizes(SimStruct *S)
{
    ssSetNumSFcnParams(S, NUM_PARAMS);  /* Number of expected parameters */

    /*
     * Check parameters passed in, providing the correct number was specified
     * in the S-function dialog box. If an incorrect number of parameters
     * was specified, Simulink will detect the error since ssGetNumSFcnParams
     * and ssGetSFcnParamsCount will differ.
     *   ssGetNumSFcnParams   - This sets the number of parameters your
     *                          S-function expects.
     *   ssGetSFcnParamsCount - This is the number of parameters entered by
     *                          the user in the Simulink S-function dialog box.
     */
#if defined(MATLAB_MEX_FILE)
    if (ssGetNumSFcnParams(S) == ssGetSFcnParamsCount(S)) {
        mdlCheckParameters(S);
        if (ssGetErrorStatus(S) != NULL) {
            return;
        }
    } else {
        return; /* Parameter mismatch will be reported by Simulink */
    }
#endif

    {
        int iParam = 0;
        int nParam = ssGetNumSFcnParams(S);

        for ( iParam = 0; iParam < nParam; iParam++ )
        {
            switch ( iParam )
            {
              case XDATAEVENLYSPACED_PIDX:
                
                ssSetSFcnParamTunable( S, iParam, SS_PRM_NOT_TUNABLE );
                break;

              default:
                ssSetSFcnParamTunable( S, iParam, SS_PRM_TUNABLE );
                break;
            }
        }
    }

    ssSetNumContStates(S, 0);
    ssSetNumDiscStates(S, 0);

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

    ssSetInputPortOptimOpts(S, 0, SS_REUSABLE_AND_LOCAL);
    ssSetInputPortOverWritable(S, 0, TRUE);

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

    ssSetOutputPortOptimOpts(S, 0, SS_REUSABLE_AND_LOCAL);

    ssSetNumSampleTimes(S, 1);

    ssSetOptions(S,
                 SS_OPTION_WORKS_WITH_CODE_REUSE |
                 SS_OPTION_EXCEPTION_FREE_CODE |
                 SS_OPTION_USE_TLC_WITH_ACCELERATOR);

} /* mdlInitializeSizes */


/* Function: mdlInitializeSampleTimes ==========================================
 * Abstract:
 *    The lookup inherits its sample time from the driving block.
 */
static void mdlInitializeSampleTimes(SimStruct *S)
{
    ssSetSampleTime(S, 0, INHERITED_SAMPLE_TIME);
    ssSetOffsetTime(S, 0, 0.0);
    ssSetModelReferenceSampleTimeDefaultInheritance(S);
} /* end mdlInitializeSampleTimes */

/* Function: mdlSetWorkWidths ===============================================
 * Abstract:
 *    Set up the [X,Y] data as run-time parameters
 *    that is, these values can be changed during execution.
 */
#define MDL_SET_WORK_WIDTHS
static void mdlSetWorkWidths(SimStruct *S)
{
    const char_T    *rtParamNames[] = {"XData","YData"};
    ssRegAllTunableParamsAsRunTimeParams(S, rtParamNames);
}


#define MDL_START                      /* Change to #undef to remove function */
#if defined(MDL_START)
/* Function: mdlStart ==========================================================
 * Abstract:
 *      Here we cache the state (true/false) of the XDATAEVENLYSPACED parameter.
 *      We do this primarily to illustrate how to "cache" parameter values (or
 *      information which is computed from parameter values) which do not change
 *      for the duration of the simulation (or in the generated code). In this
 *      case, rather than repeated calls to mxGetPr, we save the state once.
 *      This results in a slight increase in performance.
 */
static void mdlStart(SimStruct *S)
{
    SFcnCache *cache = malloc(sizeof(SFcnCache));

    if (cache == NULL) {
        ssSetErrorStatus(S,"memory allocation error");
        return;
    }

    ssSetUserData(S, cache);

    if (mxGetScalar(XDATAEVENLYSPACED(S)) != 0.0){
        cache->evenlySpaced = TRUE;
    }else{
        cache->evenlySpaced = FALSE;
    }

}
#endif /*  MDL_START */



/* Function: mdlOutputs ========================================================
 * Abstract:
 *    In this function, you compute the outputs of your S-function
 *    block. Generally outputs are placed in the output vector, ssGetY(S).
 */
static void mdlOutputs(SimStruct *S, int_T tid)
{
    SFcnCache         *cache = ssGetUserData(S);
    real_T            *xData = mxGetPr(XVECT(S));
    real_T            *yData = mxGetPr(YVECT(S));
    InputRealPtrsType uPtrs  = ssGetInputPortRealSignalPtrs(S,0);
    real_T            *y     = ssGetOutputPortRealSignal(S,0);
    int_T             ny     = ssGetOutputPortWidth(S,0);
    int_T             xLen   = mxGetNumberOfElements(XVECT(S));
    int_T             i;

    /*
     * When the XData is evenly spaced, we use the direct lookup algorithm
     * to calculate the lookup
     */
    if (cache->evenlySpaced) {
        real_T spacing = xData[1] - xData[0];
        for (i = 0; i < ny; i++) {
            real_T u = *uPtrs[i];

            if (u <= xData[0]) {
                y[i] = yData[0];
            } else if (u >= xData[xLen-1]) {
                y[i] = yData[xLen-1];
            } else {
                int_T idx = (int_T)((u - xData[0])/spacing);
                y[i] = yData[idx];
            }
        }
    } else {
        /*
         * When the XData is unevenly spaced, we use a bisection search to
         * locate the lookup index.
         */
        for (i = 0; i < ny; i++) {
            int_T idx = GetDirectLookupIndex(xData,xLen,*uPtrs[i]);
            y[i] = yData[idx];
        }
    }

} /* end mdlOutputs */



/* Function: mdlTerminate ======================================================
 * Abstract:
 *    Free the cache which was allocated in mdlStart.
 */
static void mdlTerminate(SimStruct *S)
{
    SFcnCache *cache = ssGetUserData(S);
    if (cache != NULL) {
        free(cache);
    }
} /* end mdlTerminate */



#define MDL_RTW                        /* Change to #undef to remove function */
#if defined(MDL_RTW) && (defined(MATLAB_MEX_FILE) || defined(NRT))
/* Function: mdlRTW ============================================================
 * Abstract:
 *    This function is called when Simulink Coder is generating the
 *    model.rtw file. In this routine, you can call the following functions
 *    which add fields to the model.rtw file.
 *
 *    Important! Since this S-function has this mdlRTW method, it is required
 *    to have a corresponding .tlc file so as to work with Simulink Coder. See the
 *    sfun_directlook.tlc in matlabroot/toolbox/simulink/simdemos/simfeatures/tlc_c/.
 */
static void mdlRTW(SimStruct *S)
{
    /*
     * Write out the spacing setting as a param setting, that is, this cannot be
     * changed during execution.
     */
    {
        boolean_T even = (mxGetScalar(XDATAEVENLYSPACED(S)) != 0.0);

        if (!ssWriteRTWParamSettings(S, 1,
                                     SSWRITE_VALUE_QSTR,
                                     "XSpacing",
                                     even ? "EvenlySpaced" : "UnEvenlySpaced")){
            return;/* An error occurred which will be reported by Simulink */
        }
    }
}
#endif /* MDL_RTW */


/*=============================*
 * Required S-function trailer *
 *=============================*/

#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


/* [EOF] sfun_directlook.c */

matlabroot/toolbox/simulink/simdemos/simfeatures/src/lookup_index.c.  

/* File    : lookup_index.c
* Abstract:
 *
 *     Contains a routine used by the S-function sfun_directlookup.c to
 *     compute the index in a vector for a given data value.
 * 
 *  Copyright 1990-2004 The MathWorks, Inc.
 *  $Revision: 1.1.4.7 $
 */
#include "tmwtypes.h"

/*
 * Function: GetDirectLookupIndex ==============================================
 * Abstract:
 *     Using a bisection search to locate the lookup index when the x-vector
 *     isn't evenly spaced.
 *
 *     Inputs:
 *        *x   : Pointer to table, x[0] ....x[xlen-1]
 *        xlen : Number of values in xtable
 *        u    : input value to look up
 *
 *     Output:
 *        idx  : the index into the table such that:
 *              if u is negative
 *                 x[idx] <= u < x[idx+1]
 *              else
 *                 x[idx] < u <= x[idx+1]
 */
int_T GetDirectLookupIndex(const real_T *x, int_T xlen, real_T u)
{
    int_T idx    = 0;
    int_T bottom = 0;
    int_T top    = xlen-1;
    
    /*
     * Deal with the extreme cases first:
     *
     *  i] u <= x[bottom] then idx = bottom
     * ii] u >= x[top] then idx = top-1
     *
     */
    if (u <= x[bottom]) {
        return(bottom);
    } else if (u >= x[top]) {
        return(top);
    }
    
    /*
     * We have: x[bottom] < u < x[top], onward
     * with search for the  index ...
     */
    for (;;) {
        idx = (bottom + top)/2;
        if (u < x[idx]) {
            top = idx;
        } else if (u > x[idx+1]) {
            bottom = idx + 1;
        } else {
            /*
             * We have:  x[idx] <= u <= x[idx+1], only need
             * to do two more checks and we have the answer
             */
            if (u < 0) {
                /*
                 * We want right continuity, that is,
                 * if u == x[idx+1]
                 *    then x[idx+1] <= u < x[idx+2]
                 * else    x[idx  ] <= u < x[idx+1]
                 */
                return( (u == x[idx+1]) ? (idx+1) : idx);
            } else {
                /*
                 * We want left continuity, that is, 
                 * if u == x[idx]
                 *    then x[idx-1] < u <= x[idx  ]
                 * else    x[idx  ] < u <= x[idx+1]
                 */
                return( (u == x[idx]) ? (idx-1) : idx);
            }
        }
    }
} /* end GetDirectLookupIndex */

/* [EOF] lookup_index.c */

matlabroot/toolbox/simulink/simdemos/simfeatures/tlc_c/sfun_directlook.tlc.  

%% File    : sfun_directlook.tlc
%% Abstract: 
%%      Level-2 S-function sfun_directlook block target file.
%%      It is using direct lookup algorithm without interpolation
%%
%% Copyright 1990-2004 The MathWorks, Inc.
%% $Revision: 1.1.4.7 $ 


%implements "sfun_directlook" "C"

%% Function: BlockTypeSetup ====================================================
%% Abstract:
%%     Place include and function prototype in the model's header file.
%%
%function BlockTypeSetup(block, system) void

  %% To add this external function's prototype in the header of the generated 
  %% file.
  %%
  %openfile buffer
  extern int_T GetDirectLookupIndex(const real_T *x, int_T xlen, real_T u);
  %closefile buffer
  
  %<LibCacheFunctionPrototype(buffer)>
   
%endfunction

%% Function: mdlOutputs ========================================================
%% Abstract:
%%      Direct 1-D lookup table S-function example. 
%%      Here we are trying to compute an approximate solution, p(x) to an 
%%      unknown function f(x) at x=x0, given data point pairs (x,y) in the 
%%      form of a x data vector and a y data vector. For a given data pair
%%      (say the i'th pair), we have y_i = f(x_i). It is assumed that the x 
%%      data values are monotonically increasing.  If the first or last x is 
%%      outside of the range of the x data vector, then the first or last 
%%      point will be returned.
%%
%%      This function returns the "nearest" y0 point for a given x0. 
%%      No interpolation is performed.
%%
%%      The S-function parameters are:
%%        XData
%%        YData
%%        XEvenlySpaced: 0 or 1
%%      The third parameter cannot be changed during execution and is
%%      written to the model.rtw file in XSpacing filed of the SFcnParamSettings
%%      record as "EvenlySpaced" or "UnEvenlySpaced". The first two parameters
%%      can change during execution and show up in the parameter vector.
%%
%function Outputs(block, system) Output
  /* %<Type> Block: %<Name> */  
  {
    %assign rollVars = ["U", "Y"]
    %%
    %% Load XData and YData as local variables
    %%
    const real_T *xData  = %<LibBlockParameterAddr(XData, "", "", 0)>;
    const real_T *yData  = %<LibBlockParameterAddr(YData, "", "", 0)>;
    %assign xDataLen = SIZE(XData.Value, 1)
    %%
    %% When the XData is evenly spaced, we use the direct lookup algorithm
    %% to locate the lookup index.
    %%
    %if SFcnParamSettings.XSpacing == "EvenlySpaced"
      real_T spacing = xData[1] - xData[0];

      %roll idx = RollRegions, lcv = RollThreshold, block, "Roller", rollVars
        %assign u = LibBlockInputSignal(0, "", lcv, idx)
        %assign y = LibBlockOutputSignal(0, "", lcv, idx)
        if ( %<u> <= xData[0] ) {
          %<y> = yData[0];
        } else if ( %<u> >= yData[%<xDataLen-1>] ) {
          %<y> = yData[%<xDataLen-1>];
        } else {
          int_T idx = (int_T)( ( %<u> - xData[0] ) / spacing );
          %<y> = yData[idx];
        }
        %%
        %% Generate an empty line if we are not rolling,
        %% so that it looks nice in the generated code.
        %%
        %if lcv == ""
          
        %endif
      %endroll
    %else
      %% When the XData is unevenly spaced, we use a bisection search to 
      %% locate the lookup index.
      int_T idx;

      %assign xDataAddr = LibBlockParameterAddr(XData, "", "", 0)
      %roll idx = RollRegions, lcv = RollThreshold, block, "Roller", rollVars
        %assign u = LibBlockInputSignal(0, "", lcv, idx)
        idx =  GetDirectLookupIndex(xData, %<xDataLen>, %<u>);
        %assign y = LibBlockOutputSignal(0, "", lcv, idx)
        %<y> = yData[idx];
        %%
        %% Generate an empty line if we are not rolling,
        %% so that it looks nice in the generated code.
        %%
        %if lcv == ""
          
        %endif
      %endroll
    %endif
  }
%endfunction
%% EOF: sfun_directlook.tlc

Guidelines for Writing Inlined S-Functions

Write S-Functions That Support Expression Folding

About S-Functions that Support Expression Folding

This section describes how you can take advantage of expression folding to increase the efficiency of code generated by your own inlined S-Function blocks, by calling macros provided in the S-Function API. This section assumes that you are familiar with:

The S-Function API lets you specify whether a given S-Function block should nominally accept expressions at a given input port. A block should not always accept expressions. For example, if the address of the signal at the input is used, expressions should not be accepted at that input, because it is not possible to take the address of an expression.

The S-Function API also lets you specify whether an expression can represent the computations associated with a given output port. When you request an expression at a block's input or output port, the Simulink engine determines whether or not it can honor that request, given the block's context. For example, the engine might deny a block's request to output an expression if the destination block does not accept expressions at its input, if the destination block has an update function, or if multiple output destinations exist.

The decision to honor or deny a request to output an expression can also depend on the category of output expression the block uses (see Categories of Output Expressions).

The sections that follow explain

To take advantage of expression folding in your S-functions, you should understand when to request acceptance and generation of expressions for specific blocks. You do not have to understand the algorithm by which the Simulink engine chooses to accept or deny these requests. However, if you want to trace between the model and the generated code, it is helpful to understand some of the more common situations that lead to denial of a request.

Categories of Output Expressions

When you implement a C MEX S-function, you can specify whether the code corresponding to a block's output is to be generated as an expression. If the block generates an expression, you must specify that the expression is constant, trivial, or generic.

A constant output expression is a direct access to one of the block's parameters. For example, the output of a Constant block is defined as a constant expression because the output expression is simply a direct access to the block's Value parameter.

A trivial output expression is an expression that can be repeated, without any performance penalty, when the output port has multiple output destinations. For example, the output of a Unit Delay block is defined as a trivial expression because the output expression is simply a direct access to the block's state. Because the output expression involves no computations, it can be repeated more than once without degrading the performance of the generated code.

A generic output expression is an expression that should be assumed to have a performance penalty if repeated. As such, a generic output expression is not suitable for repeating when the output port has multiple output destinations. For instance, the output of a Sum block is a generic rather than a trivial expression because it is costly to recompute a Sum block output expression as an input to multiple blocks.

Examples of Trivial and Generic Output Expressions.  Consider the following block diagram. The Delay block has multiple destinations, yet its output is designated as a trivial output expression, so that it can be used more than once without degrading the efficiency of the code.

The following code excerpt shows code generated from the Unit Delay block in this block diagram. The three root outputs are directly assigned from the state of the Unit Delay block, which is stored in a field of the global data structure rtDWork. Since the assignment is direct, involving no expressions, there is no performance penalty associated with using the trivial expression for multiple destinations.

void MdlOutputs(int_T tid)
{
   ...
  /* Outport: <Root>/Out1 incorporates:

   *   UnitDelay: <Root>/Unit Delay */
  rtY.Out1 = rtDWork.Unit_Delay_DSTATE;

  /* Outport: <Root>/Out2 incorporates:
   *   UnitDelay: <Root>/Unit Delay */
  rtY.Out2 = rtDWork.Unit_Delay_DSTATE;

  /* Outport: <Root>/Out3 incorporates:
   *   UnitDelay: <Root>/Unit Delay */
  rtY.Out3 = rtDWork.Unit_Delay_DSTATE;

   ...
}

On the other hand, consider the Sum blocks in the following model:

The upper Sum block in the preceding model generates the signal labeled non_triv. Computation of this output signal involves two multiplications and an addition. If the Sum block's output were permitted to generate an expression even when the block had multiple destinations, the block's operations would be duplicated in the generated code. In the case illustrated, the generated expressions would proliferate to four multiplications and two additions. This would degrade the efficiency of the program. Accordingly the output of the Sum block is not allowed to be an expression because it has multiple destinations

The code generated for the previous block diagram shows how code is generated for Sum blocks with single and multiple destinations.

The Simulink engine does not permit the output of the upper Sum block to be an expression because the signal non_triv is routed to two output destinations. Instead, the result of the multiplication and addition operations is stored in a temporary variable (rtb_non_triv) that is referenced twice in the statements that follow, as seen in the code excerpt below.

In contrast, the lower Sum block, which has only a single output destination (Out2), does generate an expression.

void MdlOutputs(int_T tid)
{
  /* local block i/o variables */
  real_T rtb_non_triv;
  real_T rtb_Sine_Wave;

  /* Sum: <Root>/Sum incorporates:
   *   Gain: <Root>/Gain
   *   Inport: <Root>/u1
   *   Gain: <Root>/Gain1
   *   Inport: <Root>/u2
   *
   * Regarding <Root>/Gain:
   *   Gain value: rtP.Gain_Gain
   *
   * Regarding <Root>/Gain1:
   *   Gain value: rtP.Gain1_Gain
   */
  rtb_non_triv = (rtP.Gain_Gain * rtU.u1) + (rtP.Gain1_Gain * 
rtU.u2);

  /* Outport: <Root>/Out1 */
  rtY.Out1 = rtb_non_triv;

  /* Sin Block: <Root>/Sine Wave */

  rtb_Sine_Wave = rtP.Sine_Wave_Amp *
	sin(rtP.Sine_Wave_Freq * rtmGetT(rtM_model) + 
	rtP.Sine_Wave_Phase) + rtP.Sine_Wave_Bias;

  /* Outport: <Root>/Out2 incorporates:
   *   Sum: <Root>/Sum1
   */
  rtY.Out2 = (rtb_non_triv + rtb_Sine_Wave);
}

Specify the Category of an Output Expression.  The S-Function API provides macros that let you declare whether an output of a block should be an expression, and if so, to specify the category of the expression. The following table specifies when to declare a block output to be a constant, trivial, or generic output expression.

Types of Output Expressions

Category of Expression

When to Use

Constant

Use only if block output is a direct memory access to a block parameter.

Trivial

Use only if block output is an expression that can appear multiple times in the code without reducing efficiency (for example, a direct memory access to a field of the DWork vector, or a literal).

Generic

Use if output is an expression, but not constant or trivial.

You must declare outputs as expressions in the mdlSetWorkWidths function using macros defined in the S-Function API. The macros have the following arguments:

The following macros are available for setting an output to be a constant, trivial, or generic expression:

The following macros are available for querying the status set by any prior calls to the macros above:

The set of generic expressions is a superset of the set of trivial expressions, and the set of trivial expressions is a superset of the set of constant expressions.

Therefore, when you query an output that has been set to be a constant expression with ssGetOutputPortTrivialOutputExprInRTW, it returns True. A constant expression is considered a trivial expression, because it is a direct memory access that can be repeated without degrading the efficiency of the generated code.

Similarly, an output that has been configured to be a constant or trivial expression returns True when queried for its status as a generic expression.

Acceptance or Denial of Requests for Input Expressions

A block can request that its output be represented in code as an expression. Such a request can be denied if the destination block cannot accept expressions at its input port. Furthermore, conditions independent of the requesting block and its destination blocks can prevent acceptance of expressions.

This section discusses block-specific conditions under which requests for input expressions are denied. For information on other conditions that prevent acceptance of expressions, see Generic Conditions for Denial of Requests to Output Expressions.

A block should not be configured to accept expressions at its input port under the following conditions:

If a block refuses to accept expressions at an input port, then any block that is connected to that input port is not permitted to output a generic or trivial expression.

A request to output a constant expression is never denied, because there is no performance penalty for a constant expression, and it is always possible to take the parameter's address.

Use the S-Function API to Specify Input Expression Acceptance.  The S-Function API provides macros that let you

By default, block inputs do not accept nonconstant expressions.

You should call the macros in your mdlSetWorkWidths function. The macros have the following arguments:

The macro available for specifying whether or not a block input should accept a nonconstant expression is as follows:

void ssSetInputPortAcceptExprInRTW(SimStruct *S, int portIdx, bool value)

The corresponding macro available for querying the status set by any prior calls to ssSetInputPortAcceptExprInRTW is as follows:

bool ssGetInputPortAcceptExprInRTW(SimStruct *S, int portIdx)

Generic Conditions for Denial of Requests to Output Expressions.  Even after a specific block requests that it be allowed to generate an output expression, that request can be denied, for generic reasons. These reasons include, but are not limited to

You do not need to consider these generic factors when deciding whether or not to utilize expression folding for a particular block. However, these rules can be helpful when you are examining generated code and analyzing cases where the expression folding optimization is suppressed.

Use Expression Folding in a TLC Block Implementation

To take advantage of expression folding, you must modify the TLC block implementation of an inlined S-Function such that it informs the Simulink engine whether it generates or accepts expressions at its

This section discusses required modifications to the TLC implementation.

Expression Folding Compliance.  In the BlockInstanceSetup function of your S-function, register your block to be compliant with expression folding. Otherwise, any expression folding requested or allowed at the block's outputs or inputs will be disabled, and temporary variables will be used.

To register expression folding compliance, call the TLC library function LibBlockSetIsExpressionCompliant(block), which is defined in matlabroot/rtw/c/tlc/lib/utillib.tlc. For example:

%% Function: BlockInstanceSetup ===========================================
%%
%function BlockInstanceSetup(block, system) void
  %%
  %<LibBlockSetIsExpressionCompliant(block)>
  %%
%endfunction

You can conditionally disable expression folding at the inputs and outputs of a block by making the call to this function conditionally.

If you have overridden one of the TLC block implementations provided by the Simulink Coder product with your own implementation, you should not make the preceding call until you have updated your implementation, as described by the guidelines for expression folding in the following sections.

Output Expressions.  The BlockOutputSignal function is used to generate code for a scalar output expression or one element of a nonscalar output expression. If your block outputs an expression, you should add a BlockOutputSignal function. The prototype of the BlockOutputSignal is

%function BlockOutputSignal(block,system,portIdx,ucv,lcv,idx,retType) void

The arguments to BlockOutputSignal are as follows:

The BlockOutputSignal function returns a text string for the output signal or address. The string should enforce the precedence of the expression by using opening and terminating parentheses, unless the expression consists of a function call. The address of an expression can only be returned for a constant expression; it is the address of the parameter whose memory is being accessed. The code implementing the BlockOutputSignal function for the Constant block is shown below.

%% Function: BlockOutputSignal =================================================
%% Abstract:
%%      Return the reference to the parameter.  This function *may*
%%      be used by Simulink when optimizing the Block IO data structure.
%%
%function BlockOutputSignal(block,system,portIdx,ucv,lcv,idx,retType) void
  %switch retType
    %case "Signal"
      %return LibBlockParameter(Value,ucv,lcv,idx)
    %case "SignalAddr"
      %return LibBlockParameterAddr(Value,ucv,lcv,idx)
    %default
      %assign errTxt = "Unsupported return type: %<retType>"
      %<LibBlockReportError(block,errTxt)>
  %endswitch
%endfunction

The code implementing the BlockOutputSignal function for the Relational Operator block is shown below.

%% Function: BlockOutputSignal =================================================
%% Abstract:
%%      Return an output expression.  This function *may*
%%      be used by Simulink when optimizing the Block IO data structure.
%%
%function BlockOutputSignal(block,system,portIdx,ucv,lcv,idx,retType) void
%switch retType
%case "Signal"
%assign logicOperator = ParamSettings.Operator
 %if ISEQUAL(logicOperator, "~=")
 %assign op = "!="
elseif ISEQUAL(logicOperator, "==") %assign op = "=="
  %else
%assign op = logicOperator
%endif
 %assign u0 = LibBlockInputSignal(0, ucv, lcv, idx)
%assign u1 = LibBlockInputSignal(1, ucv, lcv, idx)
  %return "(%<u0> %<op> %<u1>)"
 %default
 %assign errTxt = "Unsupported return type: %<retType>"
 %<LibBlockReportError(block,errTxt)>
%endswitch
%endfunction

Expression Folding for Blocks with Multiple Outputs.  When a block has a single output, the Outputs function in the block's TLC file is called only if the output port is not an expression. Otherwise, the BlockOutputSignal function is called.

If a block has multiple outputs, the Outputs function is called if any output port is not an expression. The Outputs function should guard against generating code for output ports that are expressions. This is achieved by guarding sections of code corresponding to individual output ports with calls to LibBlockOutputSignalIsExpr().

For example, consider an S-Function with two inputs and two outputs, where

The Outputs and BlockOutputSignal functions for the S-function are shown in the following code excerpt.

%% Function: BlockOutputSignal =================================================
%% Abstract:
%%      Return an output expression.  This function *may*
%%      be used by Simulink when optimizing the Block IO data structure.
%%
%function BlockOutputSignal(block,system,portIdx,ucv,lcv,idx,retType) void
%switch retType
%case "Signal"
   %assign u = LibBlockInputSignal(portIdx, ucv, lcv, idx)
 %case "Signal"
 %if portIdx == 0
  %return "(2 * %<u>)"
%elseif portIdx == 1
 %return "(4 * %<u>)"
%endif
%default
%assign errTxt = "Unsupported return type: %<retType>"
 %<LibBlockReportError(block,errTxt)>
%endswitch
%endfunction  
%%
%% Function: Outputs =================================================
%% Abstract:
%%      Compute output signals of block
%%
%function Outputs(block,system) Output
%assign rollVars = ["U", "Y"]
%roll sigIdx = RollRegions, lcv = RollThreshold, block, "Roller", rollVars 
%assign u0 = LibBlockInputSignal(0, "", lcv, sigIdx)
 %assign u1 = LibBlockInputSignal(1, "", lcv, sigIdx)
 %assign y0 = LibBlockOutputSignal(0, "", lcv, sigIdx)
 %assign y1 = LibBlockOutputSignal(1, "", lcv, sigIdx)
%if !LibBlockOutputSignalIsExpr(0)
%<y0> = 2 * %<u0>;
%endif
%if !LibBlockOutputSignalIsExpr(1)
 %<y1> = 4 * %<u1>;
%endif
%endroll
%endfunction

Comments for Blocks That Are Expression-Folding-Compliant.  In the past, all blocks preceded their outputs code with comments of the form

/* %<Type> Block: %<Name> */

When a block is expression-folding-compliant, the initial line shown above is generated automatically. You should not include the comment as part of the block's TLC implementation. Additional information should be registered using the LibCacheBlockComment function.

The LibCacheBlockComment function takes a string as an input, defining the body of the comment, except for the opening header, the final newline of a single or multiline comment, and the closing trailer.

The following TLC code illustrates registering a block comment. Note the use of the function LibBlockParameterForComment, which returns a string, suitable for a block comment, specifying the value of the block parameter.

%openfile commentBuf
  $c(*) Gain value: %<LibBlockParameterForComment(Gain)>
  %closefile commentBuf
  %<LibCacheBlockComment(block, commentBuf)>

Write S-Functions That Specify Port Scope and Reusability

You can use the following SimStruct macros in the mdlInitializeSizes method to specify the scope and reusability of the memory used for your S-function's input and output ports:

You declare an input or output as local or global, and indicate its reusability, by passing one of the following four options to the ssSetInputPortOptimOpts and ssSetOutputPortOptimOpts macros:

The reusability setting indicates if the memory associated with an input or output port can be overwritten. To reuse input and output port memory:

  1. Indicate the ports are reusable using either the SS_REUSABLE_AND_LOCAL or SS_REUSABLE_AND_GLOBAL option in the ssSetInputPortOptimOpts and ssSetOutputPortOptimOpts macros

  2. Indicate the input port memory is overwritable using ssSetInputPortOverWritable

  3. If your S-function has multiple input and output ports, use ssSetOutputPortOverwritesInputPort to indicate which output and input ports share memory

The following example shows how different scope and reusability settings effect the generated code. The following model contains an S-function block pointing to the C MEX S-function matlabroot/toolbox/simulink/simdemos/simfeatures/src/sfun_directlook.c, which models a direct 1-D lookup table.

The S-function's mdlInitializeSizes method declares the input port as reusable, local, and overwritable and the output port as reusable and local, as follows:

static void mdlInitializeSizes(SimStruct *S)
{
/* snip */
    ssSetInputPortOptimOpts(S, 0, SS_REUSABLE_AND_LOCAL);
    ssSetInputPortOverWritable(S, 0, TRUE);

/* snip */
    ssSetOutputPortOptimOpts(S, 0, SS_REUSABLE_AND_LOCAL);

/* snip */
}

The generated code for this model stores the input and output signals in a single local variable rtb_SFunction, as shown in the following output function:

static void sl_directlook_output(int_T tid)
{
  /* local block i/o variables */
  real_T rtb_SFunction[2];

  /* Sin: '<Root>/Sine Wave' */
  rtb_SFunction[0] = sin(((real_T)sl_directlook_DWork.counter[0] +
    sl_directlook_P.SineWave_Offset) * 2.0 * 3.1415926535897931E+000 /
    sl_directlook_P.SineWave_NumSamp) * sl_directlook_P.SineWave_Amp[0] +
    sl_directlook_P.SineWave_Bias;
  rtb_SFunction[1] = sin(((real_T)sl_directlook_DWork.counter[1] +
    sl_directlook_P.SineWave_Offset) * 2.0 * 3.1415926535897931E+000 /
    sl_directlook_P.SineWave_NumSamp) * sl_directlook_P.SineWave_Amp[1] +
    sl_directlook_P.SineWave_Bias;

  /* S-Function Block: <Root>/S-Function */
  {
    const real_T *xData = &sl_directlook_P.SFunction_XData[0];
    const real_T *yData = &sl_directlook_P.SFunction_YData [0];
    real_T spacing = xData[1] - xData[0];
    if (rtb_SFunction[0] <= xData[0] ) {
      rtb_SFunction[0] = yData[0];
    } else if (rtb_SFunction[0] >= yData[20] ) {
      rtb_SFunction[0] = yData[20];
    } else {
      int_T idx = (int_T)( ( rtb_SFunction[0] - xData[0] ) / spacing );
      rtb_SFunction[0] = yData[idx];
    }

    if (rtb_SFunction[1] <= xData[0] ) {
      rtb_SFunction[1] = yData[0];
    } else if (rtb_SFunction[1] >= yData[20] ) {
      rtb_SFunction[1] = yData[20];
    } else {
      int_T idx = (int_T)( ( rtb_SFunction[1] - xData[0] ) / spacing );
      rtb_SFunction[1] = yData[idx];
    }
  }

  /* Outport: '<Root>/Out1' */
  sl_directlook_Y.Out1[0] = rtb_SFunction[0];
  sl_directlook_Y.Out1[1] = rtb_SFunction[1];
  UNUSED_PARAMETER(tid);
}

The following table shows variations of the code generated for this model when using the generic real-time target (GRT). Each row explains a different setting for the scope and reusability of the S-function's input and output ports.

Scope and reusabilityS-function mdlInitializeSizes codeGenerated code
Inputs: Local, reusable, overwritable

Outputs: Local, reusable

ssSetInputPortOptimOpts(S, 0,
SS_REUSABLE_AND_LOCAL);

ssSetInputPortOverWritable(S, 0,
TRUE);

ssSetOutputPortOptimOpts(S, 0,
SS_REUSABLE_AND_LOCAL);
The model.c file declares a local variable in the output function.
/* local block i/o variables */
  real_T rtb_SFunction[2];
Inputs: Global, reusable, overwritable


Outputs:
Global, reusable

ssSetInputPortOptimOpts(S, 0,
SS_REUSABLE_AND_GLOBAL);

ssSetInputPortOverWritable(S, 0,
TRUE);

ssSetOutputPortOptimOpts(S, 0,
SS_REUSABLE_AND_GLOBAL);
The model.h file defines a block signals structure with a single element to store the S-function's input and output.
/* Block signals (auto storage) */
typedef struct {
  real_T SFunction[2];
} BlockIO_sl_directlook;
The model.c file uses this element of the structure in calculations of the S-function's input and output signals.
 /* Sin: '<Root>/Sine Wave' */
sl_directlook_B.SFunction[0] = sin ...
/* snip */
/*S-Function Block:<Root>/S-Function*/
{
const real_T *xData =
  &sl_directlook_P.SFunction_XData[0]
Inputs: Local, not reusable


Outputs:
Local, not reusable

ssSetInputPortOptimOpts(S, 0,
SS_NOT_REUSABLE_AND_LOCAL);

ssSetInputPortOverWritable(S, 0,
FALSE);

ssSetOutputPortOptimOpts(S, 0,
SS_NOT_REUSABLE_AND_LOCAL);
The model.c file declares local variables for the S-function's input and output in the output function
/* local block i/o variables */
  real_T rtb_SineWave[2];
  real_T rtb_SFunction[2];
Inputs: Global, not reusable


Outputs:
Global, not reusable

ssSetInputPortOptimOpts(S, 0,
SS_NOT_REUSABLE_AND_GLOBAL);

ssSetInputPortOverWritable(S, 0,
FALSE);

ssSetOutputPortOptimOpts(S, 0,
SS_NOT_REUSABLE_AND_GLOBAL);
The model.h file defines a block signal structure with individual elements to store the S-function's input and output.
/* Block signals (auto storage) */
typedef struct {
  real_T SineWave[2];
  real_T SFunction[2];
} BlockIO_sl_directlook;
The model.c file uses the different elements in this structure when calculating the S-function's input and output.
 /* Sin: '<Root>/Sine Wave' */
sl_directlook_B.SineWave[0] = sin ...
/* snip */
/*S-Function Block:<Root>/S-Function*/
{
const real_T *xData =
  &sl_directlook_P.SFunction_XData[0]

Write S-Functions That Specify Sample Time Inheritance Rules

For the Simulink engine to determine whether a model can inherit a sample time, the S-functions in the model need to specify how they use sample times. You can specify this information by calling the macro ssSetModelReferenceSampleTimeInheritanceRule from mdlInitializeSizes or mdlSetWorkWidths. To use this macro:

  1. Check whether the S-function calls any of the following macros:

    • ssGetSampleTime

    • ssGetInputPortSampleTime

    • ssGetOutputPortSampleTime

    • ssGetInputPortOffsetTime

    • ssGetOutputPortOffsetTime

    • ssGetSampleTimePtr

    • ssGetInputPortSampleTimeIndex

    • ssGetOutputPortSampleTimeIndex

    • ssGetSampleTimeTaskID

    • ssGetSampleTimeTaskIDPtr

  2. Check for the following in your S-function TLC code:

    • LibBlockSampleTime

    • CompiledModel.SampleTime

    • LibBlockInputSignalSampleTime

    • LibBlockInputSignalOffsetTime

    • LibBlockOutputSignalSampleTime

    • LibBlockOutputSignalOffsetTime

  3. Depending on your search results, use ssSetModelReferenceSampleTimeInheritanceRule as indicated in the following table.

    If...Use...
    None of the macros or functions are present, the S-function does not preclude the model from inheriting a sample time.
    ssSetModelReferenceSampleTimeInheritanceRule
        (S, USE_DEFAULT_FOR_DISCRETE_INHERITANCE)
    

    Any of the macros or functions are used for

    • Throwing errors if sample time is inherited, continuous, or constant

    • Checking ssIsSampleHit

    • Checking whether sample time is inherited in either mdlSetInputPortSampleTime or mdlSetOutputPortSampleTime before setting

    ssSetModelReferenceSampleTimeInheritanceRule...
    (S,USE_DEFAULT_FOR_DISCRETE_INHERITANCE)
    
    The S-function uses its sample time for computing parameters, outputs, and so on
    ssSetModelReferenceSampleTimeInheritanceRule
    (S, DISALLOW_SAMPLE_TIME_INHERITANCE)
    

You can use settings on the Diagnostics/Solver pane of the Configuration Parameters dialog box or Model Explorer to control how the Simulink engine responds when it encounters S-functions that have unspecified sample time inheritance rules. Toggle the Unspecified inheritability of sample time diagnostic to none, warning, or error. The default is warning.

Write S-Functions That Support Code Reuse

The Simulink Coder code reuse feature generates code for a subsystem in the form of a single function that is invoked wherever the subsystem occurs in the model (see Subsystems). If a subsystem contains S-functions, the S-functions must be compatible with the code reuse feature. Otherwise, the code generator might not generate reusable code from the subsystem or might generate incorrect code.

If you want your S-function to support the subsystem code reuse feature, the S-function must meet the following requirements:

In addition to meeting the preceding requirements, your S-function must set the SS_OPTION_WORKS_WITH_CODE_REUSE flag (see the description of ssSetOptions in the Simulink Writing S-Function documentation). This flag indicates that your S-function meets the requirements for subsystem code reuse.

Write S-Functions for Multirate Multitasking Environments

About S-Functions for Multirate Multitasking Environments

S-functions can be used in models with multiple sample rates and deployed in multitasking target environments. Likewise, S-functions themselves can have multiple rates at which they operate. The Embedded Coder product generates code for multirate multitasking models using an approach called rate grouping. In code generated for ERT-based targets, rate grouping generates separate model_step functions for the base rate task and each subrate task in the model. Although rate grouping is a code generation feature found in ERT targets only, your S-functions can use it in other contexts when you code them as explained below.

Rate Grouping Support in S-Functions

To take advantage of rate grouping, you must inline your multirate S-functions if you have not done so. You need to follow certain Target Language Compiler protocols to exploit rate grouping. Coding TLC to exploit rate grouping does not prevent your inlined S-functions from functioning properly in GRT. Likewise, your inlined S-functions will still generate valid ERT code even if you do not make them rate-grouping-compliant. If you do so, however, they will generate more efficient code for multirate models.

For instructions and examples of Target Language Compiler code illustrating how to create and upgrade S-functions to generate rate-grouping-compliant code, see Rate Grouping Compliance and Compatibility Issues in the Embedded Coder documentation.

For each multirate S-function that is not rate grouping-compliant, the Simulink Coder software issues the following warning when you build:

Warning:  Simulink Coder: Code of output function for multirate block
'<Root>/S-Function' is guarded by sample hit checks rather than being rate
grouped. This will generate the same code for all rates used by the block,
possibly generating dead code. To avoid dead code, you must update the TLC
file for the block.

You will also find a comment such as the following in code generated for each noncompliant S-function:

/* Because the output function of multirate block
   <Root>/S-Function is not rate grouped,
   the following code might contain unreachable blocks of code.
   To avoid this, you must update your block TLC file. */

The words "update function" are substituted for "output function" in these warnings.

Create Multitasking, Multirate, Port-Based Sample Time S-Functions

The following instructions show how to support both data determinism and data integrity in multirate S-functions. They do not cover cases where there is no determinism nor integrity. Support for frame-based processing does not affect the requirements.

Rules for Properly Handling Fast-to-Slow Transitions.  The rules that multirate S-functions should observe for inputs are

The input can be read at every sample hit of the input rate and written into DWork memory, but this DWork memory cannot then be directly accessed by the slower rate. Any DWork memory that will be read by the slow rate must only be written by the fast rate when there is a special sample hit. A special sample hit occurs when both this input port rate and rate to which it is interfacing have a hit. Depending on their requirements and design, algorithms can process the data in several locations.

The rules that multirate S-functions should observe for outputs are

If these conditions are met, the S-Function block can specify that the input port and output port can both be made local and reusable.

You can include an optimization when little or no processing needs to be done on the data. In such cases, the input rate code can directly write to the output (instead of by using DWork) when there is a special sample hit. If you do this, however, you must declare the outport port to be global and not reusable. This optimization results in one less memcpy but does introduce nonuniform processing requirements on the faster rate.

Whether you use this optimization or not, the most recent input data, as seen by the slower rate, is always the value when both the faster and slower rate had their hits (and possible earlier input data as well, depending on the algorithm). Any subsequent steps by the faster rate and the associated input data updates are not seen by the slower rate until the next hit for the slow rate occurs.

Pseudocode Examples of Fast-to-Slow Rate Transition.  The pseudocode below abstracts how you should write your C MEX code to handle fast-to-slow transitions, illustrating with an input rate of 0.1 second driving an output rate of one second. A similar approach can be taken when inlining the code. The block has following characteristics:

An alternative, slightly optimized approach for simple algorithms:

Example adding a simple algorithm:

Rules for Properly Handling Slow-to-Fast Transitions.  When output rates are faster than input rates, input should only be read at the rate that is associated with the input port sample time, observing the following rules:

The block can request that the input port be made local but it cannot be set to reusable. The output port can be set to local and reusable.

As in the fast-to-slow transition case, the input should not be read by any rate other than the one assigned to the input port. Similarly, the output should not be written to at any rate other than the rate assigned to the output port.

An optimization can be made when the algorithm being implemented is only required to run at the slow rate. In such cases, you use only one DWork. The input still writes to the DWork in the update function. When there is a special sample hit between the rates, the output function copies the same DWork directly to the output. You must set the output port to be global and not reusable in this case. This optimization results in one less memcpy operation per special sample hit.

In either case, the data that the fast rate computations operate on is always delayed, that is, the data is from the previous step of the slow rate code.

Pseudocode Examples of Slow-to-Fast Rate Transition.  The pseudocode below abstracts what your S-function needs to do to handle slow-to-fast transitions, illustrating with an input rate of one second driving an output rate of 0.1 second. The block has following characteristics:

An alternative, optimized approach can be used by some algorithms:

Example adding a simple algorithm:

Build Support for S-Functions

About Build Support for S-Functions

User-written S-Function blocks provide a powerful way to incorporate legacy and custom code into the Simulink and Simulink Coder development environment. In most cases, you should use S-functions to integrate existing code with Simulink Coder generated code. Several approaches to writing S-functions are available as discussed in

S-functions also provide the most flexible and capable way of including build information for legacy and custom code files in the Simulink Coder build process.

This section discusses the different ways of adding S-functions to the Simulink Coder build process.

Implicit Build Support

When building models with S-functions, the code generator automatically adds rules, include paths, and source filenames to the generated makefile. For this to occur, the source files (.h, .c, and .cpp) for the S-function must be in the same folder as the S-function MEX-file. The code generator propagates this information through the token expansion mechanism of converting a template makefile (TMF) to a makefile. The propagation requires the TMF to support the tokens.

Details of the implicit build support follow:

SpecifyAdditional Source Files for an S-Function

If your S-function has additional source file dependencies, you must add the names of the additional modules to the build process. You can do this by specifying the filenames

For example, suppose you build your S-function with multiple modules, as in

mex sfun_main.c sfun_module1.c sfun_module2.c

You can then add the modules to the build process by doing one of the following:

For more complicated S-function file dependencies, such as specifying source files in other locations or specifying libraries or object files, use the rtwmakecfg.m API, as explained in Use rtwmakecfg.m API to Customize Generated Makefiles.

Use TLC Library Functions

If you inline your S-function by writing a TLC file, you can add source filenames to the build process by using the TLC library function LibAddToModelSources. For details, see LibAddSourceFileCustomSection(file, builtInSection, newSection) in the Target Language Compiler documentation.

Another useful TLC library function is LibAddToCommonIncludes. Use this function in a #include statement to include S-function header files in the generated model.h header file. For details, see LibAddToCommonIncludes(incFileName) in the Target Language Compiler documentation.

For more complicated S-function file dependencies, such as specifying source files in other locations or specifying libraries or object files, use the rtwmakecfg.m API, as explained in Use rtwmakecfg.m API to Customize Generated Makefiles.

Use rtwmakecfg.m API to Customize Generated Makefiles

Overview.  Simulink Coder TMFs provide tokens that let you add the following items to generated makefiles:

S-functions can add this information to the makefile by using an rtwmakecfg function. This function is particularly useful when building a model that contains one or more of your S-Function blocks, such as device driver blocks.

To add information pertaining to an S-function to the makefile,

  1. Create the MATLAB language function rtwmakecfg in a file rtwmakecfg.m. The Simulink Coder software associates this file with your S-function based on its folder location. Create the rtwmakecfg Function discusses the requirements for the rtwmakecfg function and the data it should return.

  2. Modify your target's TMF such that it supports macro expansion for the information returned by rtwmakecfg functions. Modify the Template Makefile discusses the required modifications.

After the TLC phase of the build process, when generating a makefile from the TMF, the Simulink Coder code generator searches for an rtwmakecfg.m file in the folder that contains the S-function component. If it finds the file, the code generator calls the rtwmakecfg function.

Create the rtwmakecfg Function.  Create the rtwmakecfg.m file containing the rtwmakecfg function in the same folder as your S-function component (sfcname.mexext on a Microsoft Windows system and sfcname and a platform-specific extension on The Open Group UNIX system). The function must return a structured array that contains the following fields:

FieldDescription
makeInfo.includePathA cell array that specifies additional include folder names, organized as a row vector. The Simulink Coder code generator expands the folder names into include instructions in the generated makefile.
makeInfo.sourcePathA cell array that specifies additional source folder names, organized as a row vector. You must include the folder names of files entered into the S-function modules field on the S-Function Block Parameters dialog box or into the block's SFunctionModules parameter if they are not in the same folder as the S-function. The Simulink Coder code generator expands the folder names into make rules in the generated makefile.
makeInfo.sourcesA cell array that specifies additional source filenames (C or C++), organized as a row vector. Do not include the name of the S-function or any files entered into the S-function modules field on the S-Function Block Parameters dialog box or into the block's SFunctionModules parameter. The Simulink Coder code generator expands the filenames into make variables that contain the source files. You should specify only filenames (with extension). Specify path information with the sourcePath field.
makeInfo.linkLibsObjsA cell array that specifies additional, fully qualified paths to object or library files against which the Simulink Coder generated code should link. The Simulink Coder code generator does not compile the specified objects and libraries. However, it includes them when linking the final executable. This can be useful for incorporating libraries that you do not want the Simulink Coder code generator to recompile or for which the source files are not available. You might also use this element to incorporate source files from languages other than C and C++. This is possible if you first create a C compatible object file or library outside of the Simulink Coder build process.
makeInfo.precompileA Boolean flag that indicates whether the libraries specified in the rtwmakecfg.m file exist in a specified location (precompile==1) or if the libraries need to be created in the build folder during the Simulink Coder build process (precompile==0).
makeInfo.libraryA structure array that specifies additional run-time libraries and module objects, organized as a row vector. The Simulink Coder code generator expands the information into make rules in the generated makefile. See the next table for a list of the library fields.

The makeInfo.library field consists of the following elements:

ElementDescription
makeInfo.library(n).NameA character array that specifies the name of the library (without an extension).
makeInfo.library(n).LocationA character array that specifies the folder in which the library is located when precompiled. See the description of makeInfo.precompile in the preceding table for more information. A target can use the TargetPreCompLibLocation parameter to override this value. See Specify the Location of Precompiled Libraries for details.
makeInfo.library(n).ModulesA cell array that specifies the C or C++ source file base names (without an extension) that comprise the library. Do not include the file extension. The makefile appends the object extension.

Example:

 disp(['Running rtwmakecfg from folder: ',pwd]);
    makeInfo.includePath = { fullfile(pwd, 'somedir2') };
    makeInfo.sourcePath = {fullfile(pwd, 'somedir2'), fullfile(pwd, 'somedir3')};
    makeInfo.sources  = { 'src1.c', 'src2.cpp'};
    makeInfo.linkLibsObjs = { fullfile(pwd, 'somedir3', 'src3.object'),...
                              fullfile(pwd, 'somedir4', 'mylib.library')};
    makeInfo.precompile = 1;
    makeInfo.library(1).Name     = 'myprecompiledlib';
    makeInfo.library(1).Location = fullfile(pwd,'somdir2','lib');
    makeInfo.library(1).Modules  = {'srcfile1' 'srcfile2' 'srcfile3' };

Modify the Template Makefile.   To expand the information generated by an rtwmakecfg function, you can modify the following sections of your target's TMF:

The TMF code examples below may not apply to your make utility. For additional examples, see the GRT or ERT TMFs located in matlabroot/rtw/c/grt/*.tmf or matlabroot/rtw/c/ert/*.tmf.

Example — Adding Folder Names to the Makefile Include Path.  

The following TMF code example adds folder names to the include path in the generated makefile:

ADD_INCLUDES = \
|>START_EXPAND_INCLUDES<|   -I|>EXPAND_DIR_NAME<| \
|>END_EXPAND_INCLUDES<|

Additionally, the ADD_INCLUDES macro must be added to the INCLUDES line, as shown below.

INCLUDES = -I. -I.. $(MATLAB_INCLUDES) $(ADD_INCLUDES) $(USER_INCLUDES)
Example — Adding Library Names to the Makefile.  

The following TMF code example adds library names to the generated makefile.

LIBS =
|>START_PRECOMP_LIBRARIES<|
LIBS += |>EXPAND_LIBRARY_NAME<|.a |>END_PRECOMP_LIBRARIES<|
|>START_EXPAND_LIBRARIES<|
LIBS += |>EXPAND_LIBRARY_NAME<|.a |>END_EXPAND_LIBRARIES<|

For more information on how to use configuration parameters to control library names and location during the build process, see Control Library Location and Naming During the Build Process.

Example — Adding Rules to the Makefile.  

The following TMF code example adds rules to the generated makefile.

|>START_EXPAND_RULES<|
$(BLD)/%.o: |>EXPAND_DIR_NAME<|/%.c $(SRC)/$(MAKEFILE) rtw_proj.tmw
    @$(BLANK)
    @echo ### "|>EXPAND_DIR_NAME<|\$*.c"
    $(CC) $(CFLAGS) $(APP_CFLAGS) -o $(BLD)$(DIRCHAR)$*.o \
    |>EXPAND_DIR_NAME<|$(DIRCHAR)$*.c > $(BLD)$(DIRCHAR)$*.lst
|>END_EXPAND_RULES<|

|>START_EXPAND_LIBRARIES<|MODULES_|>EXPAND_LIBRARY_NAME<| = \
|>START_EXPAND_MODULES<|    |>EXPAND_MODULE_NAME<|.o \
|>END_EXPAND_MODULES<|

|>EXPAND_LIBRARY_NAME<|.a : $(MAKEFILE) rtw_proj.tmw
$(MODULES_|>EXPAND_LIBRARY_NAME<|:%.o=$(BLD)/%.o)
    @$(BLANK)
    @echo ### Creating $@
    $(AR) -r $@ $(MODULES_|>EXPAND_LIBRARY_NAME<|:%.o=$(BLD)/%.o)
|>END_EXPAND_LIBRARIES<|

|>START_PRECOMP_LIBRARIES<|MODULES_|>EXPAND_LIBRARY_NAME<| = \
|>START_EXPAND_MODULES<|    |>EXPAND_MODULE_NAME<|.o \
|>END_EXPAND_MODULES<|

|>EXPAND_LIBRARY_NAME<|.a : $(MAKEFILE) rtw_proj.tmw
$(MODULES_|>EXPAND_LIBRARY_NAME<|:%.o=$(BLD)/%.o)
    @$(BLANK)
    @echo ### Creating $@
    $(AR) -r $@ $(MODULES_|>EXPAND_LIBRARY_NAME<|:%.o=$(BLD)/%.o)
|>END_PRECOMP_LIBRARIES<|

Precompile S-Function Libraries

You can precompile new or updated S-function libraries (MEX-files) for a model by using the MATLAB language function rtw_precompile_libs. Using a specified model and a library build specification, this function builds and places the libraries in a precompiled library folder.

By precompiling S-function libraries, you can optimize system builds. Once your precompiled libraries exist, the Simulink Coder code generator can omit library compilation from subsequent builds. For models that use numerous libraries, the time savings for build processing can be significant.

To use rtw_precompile_libs,

  1. Set the library file suffix, including the file type extension, based on the platform in use.

  2. Set the precompiled library folder.

  3. Define a build specification.

  4. Issue a call to rtw_precompile_libs.

The following procedure explains these steps in more detail.

  1. Set the library file suffix, including the file type extension, based on the platform in use.

    Consider checking for the type of platform in use and then using the TargetLibSuffix parameter to set the library suffix accordingly. For example, you might set the suffix to .a for a UNIX platform and _vc.lib otherwise.

      if isunix
        suffix = '.a';
      else
        suffix = '_vc.lib';
      end
    
    set_param(my_model,'TargetLibSuffix', suffix);
  2. Set the precompiled library folder.

    Use one of the following methods to set the precompiled library folder.

    If you set both TargetPreCompLibLocation and makeInfo.precompile, the setting for TargetPreCompLibLocation takes precedence.

    The following command sets the precompiled library folder for model my_model to folder lib under the current working folder.

     set_param(my_model,'TargetPreCompLibLocation', fullfile(pwd,'lib'));
    

      Note   If you set both the target folder for the precompiled library files and a target library file suffix, the Simulink Coder code generator automatically detects whether any precompiled library files are missing while processing builds.

  3. Define a build specification.

    Set up a structure that defines a build specification. The following table describes fields you can define in the structure. All fields except rtwmakecfgDirs are optional.

    FieldDescription
    rtwmakecfgDirs

    A cell array of strings that name the folders containing rtwmakecfg files for libraries to be precompiled. The function uses the Name and Location elements of makeInfo.library, as returned by rtwmakecfg, to specify the name and location of the precompiled libraries. If you set the TargetPreCompLibLocation parameter to specify the library folder, that setting overrides the makeInfo.library.Location setting.

    Note: The specified model must contain blocks that use precompiled libraries specified by the rtwmakecfg files because the TMF-to-makefile conversion generates the library rules only if the libraries are used.

    libSuffixA string that specifies the suffix, including the file type extension, to be appended to the name of each library (for example, .a or _vc.lib). The string must include a period (.). You must set the suffix with either this field or the TargetLibSuffix parameter. If you specify a suffix with both mechanisms, the TargetLibSuffix setting overrides the setting of this field.
    intOnlyBuildA Boolean flag. When set to true, the flag indicates the libraries are to be optimized such that they are compiled from integer code only. This field applies to ERT targets only.
    makeOptsA string that specifies an option to be included in the rtwMake command line.
    addLibs

    A cell array of structures that specify libraries to be built that are not specified by an rtwmakecfg function. Each structure must be defined with two fields that are character arrays:

    • libName — the name of the library without a suffix

    • libLoc — the location for the precompiled library

    The TMF can specify other libraries and how those libraries are to be built. Use this field if you need to precompile those libraries.

    The following commands set up build specification build_spec, which indicates that the files to be compiled are in folder src under the current working folder.

    build_spec = [];
    build_spec.rtwmakecfgDirs = {fullfile(pwd,'src')};
    
  4. Issue a call to rtw_precompile_libs.

    Issue a call to rtw_precompile_libs that specifies the model for which you want to build the precompiled libraries and the build specification. For example:

    rtw_precompile_libs(my_model,build_spec);

  


Related Products & Applications

Learn more about Simulink through this collection of videos, articles, technical literature and the Getting Started with Simulink Guide.

 © 1984-2012- The MathWorks, Inc.    -   Site Help   -   Patents   -   Trademarks   -   Privacy Policy   -   Preventing Piracy   -   RSS