Main Content

Write Fully Inlined S-Functions

R2026b

A fully inlined S-function builds your algorithm (block) into generated code that you cannot distinguish 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 code generation (TLC file).

Using the example in Write Wrapper S-Function and TLC Files, you can eliminate the call to my_alg entirely by specifying the explicit code (that is, 2.0 * u) in wrapsfcn.tlc. While this can improve performance, if you are working with a large amount of C/C++ code, the task can be lengthy. You also have to maintain your algorithm in two places, the C/C++ S-function itself and the corresponding TLC file. Consider whether 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 code 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 replaces the call to my_alg with the algorithm itself.

Multiport S-Function

A more advanced multiport inlined S-function example is sfun_multiport.c and sfun_multiport.tlc. This S-function illustrates how to create a fully inlined TLC file for an S-function that contains multiple ports.

Guidelines for Writing Inlined S-Functions

To generate efficient code for S-Function blocks and to prevent unexpected behavior, adhere to these guidelines when writing inlined S-Functions.

Implementing the Block TLC Interface

  • Enable the enhanced TLC block interface for all blocks and target TLC files to integrate the S-Function block code more effectively into the generated code for the model. To understand how the enhanced TLC block interface optimizes S-Function block code integration, see Enhanced TLC Block Interface.

    When the enhanced TLC block interface is enabled, the BlockTypeSetup function executes twice for each block type: once during the Block TLC Analysis phase and again during the Target TLC phase. Therefore, any setup code inside BlockTypeSetup that must execute only once, such as caching header includes, function prototypes, or defines, must be guarded using a TLC global variable. This is necessary to prevent duplicates of setup code in the generated code.

    This example uses a global variable ::_A2D_SETUP_ as a guard to prevent the setup code of a block from being added multiple times into the generated code.

    Without Guard Code (At Risk of Undefined Behavior)With Guard Code (Recommended Practice)
    %function BlockTypeSetup(block, system) void
     
      %% Place a #define in the model's header file
      %openfile buffer
        #define A2D_CHANNEL 0
      %closefile buffer
      %<LibCacheDefine(buffer)>
     
      %% Place function prototypes in the model's header file
      %openfile buffer
        void start_a2d(void);
        void reset_a2d(void);
      %closefile buffer
      %<LibCacheFunctionPrototype(buffer)>
     
    %endfunction
    
    %function BlockTypeSetup(block, system) void
      %if EXISTS("::_A2D_SETUP_") == 0
        %assign ::_A2D_SETUP_ = 1
     
        %% Place a #define in the model's header file
        %openfile buffer
          #define A2D_CHANNEL 0
        %closefile buffer
        %<LibCacheDefine(buffer)>
     
        %% Place function prototypes in the model's header file
        %openfile buffer
          void start_a2d(void);
          void reset_a2d(void);
        %closefile buffer
        %<LibCacheFunctionPrototype(buffer)>
      %endif %% ::_A2D_SETUP_
    %endfunction
    

  • When accessing model-related data such as configset records, block input records, block output records, block parameter records and data type records, always use the documented public functions available in the matlabroot/rtw/c/tlc/public_api directory. Avoid using undocumented functions or directly accessing fields or records from the model.rtw file, as this can lead to unexpected results. For a comprehensive list of documented functions and their effective usage, see Target Language Compiler Library Functions Overview.

    The following examples illustrate how to interact with model-related data using documented public functions, including accessing fields within datatype and block input signal records, checking and setting sample time fields, determining if an input signal of a block is complex, and accessing configuration settings.

    Use CaseDirect Access (Not Recommended)Recommended Access
    Access the IdAliasedThruTo field for a data type record
    %assign aIdx = dt.IdAliasedThruTo
    %assign aIdx = LibGetDataTypeIdAliasedThruToFromId(id)
    Access the InputPortContiguous field for the input signal record of a block
    %assign isContig = block.Connections.InputPortContiguous[0]
    %assign isContig = LibBlockIsInputPortContiguous(0)
    Check the value of the NeedFloatTime field from a sample time record
    %if SampleTime[tid].NeedFloatTime == "yes"
    %if LibGetSampleTimeNeedsFloatTime(tid)
    Set the value for the NeedFloatTime field of a sample time
    %assign ::CompiledModel.SampleTime[tid].NeedFloatTime = "yes"
    %<LibSetSampleTimeNeedsFloatTime(tid, TLC_TRUE)>
    Check if the input signal of a block is complex
    %assign ipRecord = FcnGetInputPortRecord(0)
    %% FcnGetInputPortRecord is not documented
    %assign dataRecord = SLibGetSourceRecord(ipRecord, 0)
    %% SLibGetSourceRecord is not documented
    %assign isComplex = LibCGTypeIsComplex(dataRecord.CGTypeIdx)
    
    %assign isComplex = LibBlockInputSignalIsComplex(0) 
    %% LibBlockInputSignalIsComplex is a documented function
    Access the value of the TargetLang field from the current active configuration setting
    %assign genLang = ::CompiledModel.ConfigSet.TargetLang
    %assign genLang = LibGetConfigSetParam("TargetLang")
    Check if the AutosarMatrixIOAsArray field exists in the current active configuration setting
    %if ISFIELD(::CompiledModel.ConfigSet, "AutosarMatrixIOAsArray")
    %if LibIsConfigSetParam("AutosarMatrixIOAsArray")

    Using TLC library functions not only provides a stable interface for effectively managing model behavior and structure but also helps prevent issues that may arise from future updates to rtw records.

  • Avoid modifying existing records in model.rtw such as Block, System, or CompiledModel, as they are read-only and changes to them can result in data loss during the code generation process. To manage custom records, you can use global records or store them in the UserData field inside each block or system. Use the block.UserData field for block-specific records and the system.UserData field for system-level records. Both UserData records and global records persist throughout the entire Simulink Coder TLC execution, remaining accessible and reliable for the target TLC phase unless explicitly modified by the user.

    This example shows how to use a global record to effectively manage block instance counts in TLC for logging without modifying the compiled model file.

    Modifying Existing Records (Not Recommended)Creating Global Records (Recommended)

    This code directly modifies the compiled model by updating LookupBlockCount to track instances of the custom S-Function block sfcn_custom_lookup.

    %implements "sfcn_custom_lookup" "C"
    %function BlockTypeSetup(block, system) void
        %addtorecord ::CompiledModel LookupBlockCount 0
    %endfunction
    %function BlockInstanceSetup(block, system) void
        %<LibEnableBlockFcnOptimizations(block)>
        %assign ::CompiledModel.LookupBlockCount = ::CompiledModel.LookupBlockCount + 1
    %endfunction

    This code creates a global record LookupBlockCount to track block instances without directly modifying the compiled model.

    %implements "sfcn_custom_lookup" "C"
    %function BlockTypeSetup(block, system) void
        %assign ::LookupBlockCount = 0
    %endfunction
    %function BlockInstanceSetup(block, system) void
        %<LibEnableBlockFcnOptimizations(block)>
        %assign ::LookupBlockCount = ::LookupBlockCount + 1
    %endfunction

  • Do not write block TLC code that relies on a specific execution order for block interface functions. For example, requiring an Outputs function of one block to execute before the Start function of another block creates a dependency. Similarly, within the same block, all functions must operate independently and not depend on the execution order of each other. For example, the Outputs function and Start function of a block should not rely on the sequence in which they are executed. Avoid such dependencies, as they can lead to undefined behavior during code generation. The underlying infrastructure is subject to change with updates based on Simulink® requirements.

  • When the Enhanced TLC Block Interface is enabled, use LibCreateSourceFile to create source files and LibSetSourceFileSection to insert code into specific sections (for example, the Includes and Functions sections). Do not use %openfile with a filename to create source files directly. Files created with LibCreateSourceFile automatically include the code-generation file banner and appear in the code generation report.

    This example shows how to use LibCreateSourceFile and LibSetSourceFileSection to correctly generate a custom source file and populate its standard sections.

    Using Direct File I/O (Not Recommended)Using Source File Functions (Recommended)

    This code writes directly to a custom source file (custom_io.c) from BlockTypeSetup using manual file output. It bypasses the TLC file‑generation API, so the file does not include the code-generation file banner or appear in the code generation report.

    %function BlockTypeSetup(block, system) void
       %if EXISTS("::_CUSTOM_IO_SETUP_") == 0
         %assign ::_CUSTOM_IO_SETUP_ = 1
         %openfile srcFile = "custom_io.c"
         %selectfile srcFile
         #include <math.h>
         void custom_function(void)
         {
             /* function body */
         }
         %closefile srcFile
       %endif
     %endfunction

    This code calls LibCreateSourceFile and LibSetSourceFileSection to create a custom source file (custom_io.c) and populate its standard sections. The resulting file has the code-generation file banner and is listed in the code generation report.

    %function BlockTypeSetup(block, system) void
    
       %if EXISTS("::_CUSTOM_IO_SETUP_") == 0
         %assign ::_CUSTOM_IO_SETUP_ = 1
         %assign fileH = LibCreateSourceFile("Source", "Simulink", "custom_io")
         %openfile headerBuffer
         #include <math.h>
         %closefile headerBuffer
         %<LibSetSourceFileSection(fileH, "Includes", headerBuffer)>
         %openfile functionBuffer
         void custom_function(void)
         {
             /* function body */
         }
         %closefile functionBuffer
         %<LibSetSourceFileSection(fileH, "Functions", functionBuffer)>
       %endif
     %endfunction

  • For data store memory access, avoid DWork-based patterns such as using block.ParamSettings.DataStoreSource or directly accessing DWork records. Use the dedicated Data Store Memory (DSM) TLC functions instead (for example, LibBlockDataStore and LibBlockDataStoreAddr), and invoke them only from code-generation sections such as Outputs, Update, and Start. For data store attributes such as width, data type, and complexity, use the corresponding DSM property functions rather than reading from rtw records or DWork fields. For detailed usage information on these related functions, see Block State and Work Vector Functions.

    DWork-Based Pattern (Not Recommended)Using DSM TLC Functions (Recommended)

    This code sets the dWorkSrc variable to the DataStoreSource entry of the block (the reference to the Data Store Memory) and then uses LibBlockDWorkAddr and LibBlockDWork to retrieve the address and value of a data store element.

    %assign dWorkSrc = block.ParamSettings.DataStoreSource[0]
    %assign elemPtr = LibBlockDWorkAddr(dWorkSrc, "", "", 0) %% address of element 0
    %assign val = LibBlockDWork(dWorkSrc, "", "", 2) %% value of element 2

    This code uses DSM TLC functions to directly access a Data Store Memory element by index. It calls LibBlockDataStoreAddr to get the pointer to element 0 and LibBlockDataStore to get the value of element 2.

    %assign elemPtr = LibBlockDataStoreAddr(0, "", "", 0)
    %assign val = LibBlockDataStore(0, "", "", 2)

    This code example uses FcnGetDworkAndRec to retrieve the DWork record dwRec, then calls SLibDWorkWidth to determine the number of elements stored in the data store.

    %assign dwRec = FcnGetDworkAndRec(block.ParamSettings.DataStoreSource[0]).DWorkRec
    %assign width  = SLibDWorkWidth(dwRec)

    In this code, LibBlockDataStoreWidth(0) returns the data store width, with no intermediate DWork record needed.

    %assign width = LibBlockDataStoreWidth(0)

Using RTWdata and mdlRTW

  • Consider using the block property RTWdata (see S-Function RTWdata). This property is a structure of character vectors that you can associate with a block. The code generator saves the structure with the model in the model.rtw file and makes the .rtw file more readable. For example in the MATLAB Command Window, suppose you enter these commands:

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

    The .rtw file that the code generator produces for the block includes the comments specified in the structure mydata.

  • Consider using the mdlRTW function to inline your C MEX S-function in the generated code for:

    • Renaming tunable parameters in the generated code.

    • Introducing non-tunable parameters into a TLC file.

See Also

Topics