Main Content

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 code generator produces 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 (Embedded Coder).

For each multirate S-function that is not rate grouping-compliant, the code generator 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.

Note

The slow rates must be multiples of the fastest rate. The instructions do not apply when two rates being interfaced are not multiples or when the rates are not periodic.

Rules for Properly Handling Fast-to-Slow Transitions

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

  • The input should only be read at the rate that is associated with the input port sample time.

  • Generally, the input data is written to DWork, and the DWork can then be accessed at the slower (downstream) rate.

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. 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

  • The output should not be written by a rate other than the rate assigned to the output port, except in the optimized case described below.

  • The output should always be written when the sample rate of the output port has a hit.

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 the value when both the faster and slower rate had their hits (and possible earlier input data as well, depending on the algorithm). 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:

  • File: sfun_multirate_zoh.c, Equation: y = u(tslow)

  • Input: local and reusable

  • Output: local and reusable

  • DirectFeedthrough: yes

    OutputFcn
    if (ssIsSampleHit(".1")) {
        if (ssIsSepcialSampleHit("1")) {
            DWork = u;
        }
    }
    if (ssIsSampleHit("1")) {
        y = DWork;
    }

An alternative, slightly optimized approach for simple algorithms:

  • Input: local and reusable

  • Output: global and not reusable because it needs to persist between special sample hits

  • DirectFeedthrough: yes

    OutputFcn
    if (ssIsSampleHit(".1")) {
        if (ssIsSpecialSampleHit("1")) {
            y = u;
        }
    }

Example adding a simple algorithm:

  • File: sfun_multirate_avg.c; Equation: y = average(u)

  • Input: local and reusable

  • Output: local and reusable

  • DirectFeedthrough: yes

    (Assume DWork[0:10] and DWork[mycounter] are initialized to zero)

    OutputFcn
    if (ssIsSampleHit(".1")) {
        /* In general, processing on 'u' could be done here,
            it runs on every hit of the fast rate. */
        DWork[DWork[mycounter]++] = u;
        if (ssIsSpecialSampleHit("1")) {
        /* In general, processing on DWork[0:10] can be done 
           here, but it does cause the faster rate to have 
           nonuniform processing requirements (every 10th hit, 
           more code needs to be run).*/
            DWork[10] = sum(DWork[0:9])/10;
            DWork[mycounter] = 0;
        }
    }
    if (ssIsSampleHit("1")) {
        /* Processing on DWork[10] can be done here before
           outputing. This code runs on every hit of the 
           slower task. */
        y = DWork[10];
    }

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:

  • Always read input from the update function.

  • Use no special sample hit checks when reading input.

  • Write the input to a DWork.

  • When there is a special sample hit between the rates, copy the DWork into a second DWork in the output function.

  • Write the second DWork to the output at every hit of the output sample rate.

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 a rate other than the one assigned to the input port. Similarly, the output should not be written to at a 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:

  • File: sfun_multirate_delay.c, Equation: y = u(tslow-1)

  • Input: Set to local, will be local if output/update are combined (ERT) otherwise will be global. Set to not reusable because input needs to be preserved until the update function runs.

  • Output: local and reusable

  • DirectFeedthrough: no

    OutputFcn
    if (ssIsSampleHit(".1") {
        if (ssIsSpecialSampleHit("1") {
            DWork[1] = DWork[0];
        }
        y = DWork[1];
    }
    UpdateFcn
    if (ssIsSampleHit("1")) {
        DWork[0] = u;
    }

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

  • Input: Set to local, will be local if output/update are combined (ERT) otherwise will be global. Set to not reusable because input needs to be preserved until the update function runs.

  • Output: global and not reusable because it needs to persist between special sample hits.

  • DirectFeedthrough: no

    OutputFcn
    if (ssIsSampleHit(".1") {
        if (ssIsSpecialSampleHit("1") {
            y = DWork;
        }
    }
    UpdateFcn
    if (ssIsSampleHit("1")) {
        DWork = u;
    }

Example adding a simple algorithm:

  • File: sfun_multirate_modulate.c, Equation: y = sin(tfast) + u(tslow-1)

  • Input: Set to local, will be local if output/update are combined (an ERT feature) otherwise will be global. Set to not reusable because input needs to be preserved until the update function runs.

  • Output: local and reusable

  • DirectFeedthrough: no

    OutputFcn
    if (ssIsSampleHit(".1") {
        if (ssIsSpecialSampleHit("1") {
        /* Processing not likely to be done here. It causes 
         * the faster rate to have nonuniform processing 
         * requirements (every 10th hit, more code needs to 
         * be run).*/
            DWork[1] = DWork[0];
        }
        /* Processing done at fast rate */
        y = sin(ssGetTaskTime(".1")) + DWork[1];
    }
    UpdateFcn
    if (ssIsSampleHit("1")) {
        /* Processing on 'u' can be done here. There is a delay of
           one slow rate period before the fast rate sees it.*/
        DWork[0] = u;}