Main Content

Write Fully Inlined S-Functions

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 interface optimizes S-Function block code integration, see Enhanced TLC Block Interface.

  • 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, and determining if an input signal of a block is complex.

    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

    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. However, if data needs to be created during the block TLC interface execution for later use in the target TLC phase, create global records. These 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.

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