Main Content

Stack Usage Profiling for Code Generated from Simulink Models

To determine the size of stack memory that is required to run generated code, you can run a software-in-the-loop (SIL) or processor-in-the-loop (PIL) simulation that generates a stack usage profile. The SIL or PIL simulation instruments the generated code and uses the instrumentation to calculate stack usage. When the simulation is complete, you can view a code stack profiling report that shows minimum, average, and maximum memory demand. Using the Simulation Data Inspector, you can observe streamed stack usage information during and after the simulation. The profiles that you generate enable you to observe the effect of compiler optimization and data input on stack usage.

You can generate stack usage profiles by using the top-model or Model block SIL/PIL workflows. Subsystem SIL/PIL workflows do not support stack usage profiling.

Benefits of Dynamic Stack Usage Profiling

Static stack usage analysis has some limitations. For example, it does not consider:

  • Memory optimization performed by a compiler. For example, variables that are optimized away or stored in registers.

  • Additional memory that the compiler uses in a function call. Compilers store the address to which the execution returns after the function call.

  • Memory alignment that the compiler applies.

The dynamic stack usage profiling provided by a SIL or PIL simulation does not have the listed limitations because the simulation calculates actual memory usage.

Configure Stack Usage Profiling

To run a SIL or PIL simulation that generates stack usage metrics:

  1. In the Simulink® Editor, open your model.

  2. On the Apps tab, click SIL/PIL Manager.

  3. In the Mode section, select SIL/PIL Simulation Only.

  4. In the Prepare section, specify System Under Test and, if required, SIL/PIL Mode. A PIL simulation requires a driver implementation that obtains stack usage data from the target hardware. For more information, see Implement Driver to Obtain PIL Stack Usage Data.

  5. To enable stack usage profiling, in the Prepare section:

    1. Click Settings.

    2. Under Profiling, click the Task Profiling button off, which disables the generation of execution-time metrics.

    3. Under Coverage, click the Coverage Collection button off, which disables code coverage analysis.

    4. Under Stack Profiling, click the Stack Profiling button on. This action selects the Measure task stack usage configuration parameter, which provides stack usage measurements for each generated task. The top model setting for the parameter overrides the corresponding setting in referenced models.

    5. Click the Settings button to open the Configuration Parameters dialog box. On the Data Import/Export pane, clear the Single simulation output check box.

  6. To view streamed stack usage during the simulation, open the Simulation Data Inspector. In the Results section, click.

  7. In the Run section, click Run SIL/PIL.

In the MATLAB® base workspace, the simulation generates a variable with the default name, stackProfile, which stores the stack usage data. You can specify an alternative name through the Stack workspace variable configuration parameter.

Observe Stack Usage of Generated Code

Suppose you run a SIL simulation of this model:

 mStackProfilingTopModel

When the SIL simulation is complete, the code stack profiling report opens.

Note

If you close the report, you can reopen the report. In the Results gallery, under Stack Profiling Results, click Generate Report.

In section 1, the report:

  • Provides information that indicates how the target compiler views memory and aligns variables.

  • States whether a target-specific or generic driver obtained stack usage data from the target hardware.

  • Provides a timestamp for the report.

For each task, section 2 of the report provides this information:

  • Minimum, average, and maximum stack usage in bytes.

  • Number of times that the generated code is called.

The report provides more information through clickable icons. The specified workspace variable, for example, stackProfile, must be present in the base workspace.

If you click , you can view the stack usage distribution for the simulation.

If you click , you can view the variation of stack usage over the simulation.

Stack memory usage for mStackProfilingRefModel_step fluctuates between 112 and 64 bytes. The fluctuation occurs because the nested function generated from the If Action Subsystem block is called by mStackProfilingRefModel_step only when the signal previous_output has an even value.

…
if ((int32_T)(uint8_T)((int32_T)rtb_previous_output % 2) == 0) {
    /* Outputs for IfAction SubSystem: '<Root>/If Action Subsystem' incorporates:
     *  ActionPort: '<S1>/Action Port'
     */

    /* Outputs for Atomic SubSystem: '<S1>/Subsystem' */
    Subsystem(rtb_previous_output, &rtY.Out1);

    /* End of Outputs for SubSystem: '<S1>/Subsystem' */
    /* End of Outputs for SubSystem: '<Root>/If Action Subsystem' */
  }
…

If you specify a value for Maximum stack size (bytes) (MaxStackSize), the report provides Section 3. This section displays the maximum stack use for each task with reference to the maximum stack size permitted, which the bar chart displays as a horizontal red line. If the MaxStackSize value is much greater than the stack usage of the tasks, the bar chart does not display the red line. To help your visual analysis of stack usage requirements, you can use the MaxStackUsageAllowed property of the workspace variable to respecify the maximum stack size value. For example:

stackProfile.MaxStackUsageAllowed = 64;
report(stackProfile);

Implement Driver to Obtain Stack Usage Data During PIL Simulation

A PIL simulation requires a driver implementation that obtains stack usage data from the target hardware. The driver must return the value of the stack register.

Note

If you do not specify a driver for your target hardware, the PIL simulation tries to use a default generic driver.

When you set up PIL target connectivity, specify the driver through an rtw.connectivity.Config subclass. This code provides an example.

classdef overheadConnectivityConfig < rtw.connectivity.Config
  methods
    function this = customConnectivityConfig(componentArgs)
            
      % Create builder
      targetApplicationFramework = ...
        mypil.TargetApplicationFramework(componentArgs);
      builder = rtw.connectivity.MakefileBuilder(componentArgs, ...
        targetApplicationFramework, '');
            
      % Create launcher
      launcher = mypil.Launcher(componentArgs, builder);
            
      % Set up communication       
      hostCommunicator = rtw.connectivity.RtIOStreamHostCommunicator(...
                         componentArgs, ...
                         launcher, ...
                         rtiostreamLibTCPIP);     

      % Call super class constructor to register components
        this@rtw.connectivity.Config(componentArgs,...
                                     builder,...
                                     launcher,...
                                     hostCommunicator);
            
      % Specify driver implementation that obtains stack usage
      % data from the target hardware
      stackUsageDriver = coder.profile.StackDriver();

      stackUsageDriver.PtrDataType = 'uint64';
      stackUsageDriver.HeaderFile = 'myHeaderFile.h';
      stackUsageDriver.SourceFile = 'mySourceFile.c';
      stackUsageDriver.IncludePaths = {'path/To/myFolder1', ...
                                       'path/To/myFolder2', ...
                                       'path/To/myFolder3'};
      stackUsageDriver.DriverFunction = 'myDriverFunction';
      this.setStackDriver(stackUsageDriver);
    end
  end
end

For more information about setting up PIL target connectivity, see:

See Also

Related Topics