Main Content

Compile Code Conditionally for Variations of Component Represented Using Variant Block

This example explains how to generate code for multiple implementations of a component represented using a Variant Subsystem, Variant Model, Variant Assembly Subsystem block. The different variations, referred to as variant choices, are guarded by preprocessor conditionals #if and #elif in the generated code. These conditionals enable conditional compilation, selectively including or excluding code based on the variant condition that evaluates to true during the code compilation. By including multiple variant choices, you are not required to regenerate the code each time the value of the variant control variable changes. This approach also allows for analyzing variant choices for potential incompatibilities, such as data type and dimension mismatches, prior to simulation and code generation.

Prerequisites

To learn more about how to use Variant Subsystem blocks in Simulink®, see Implement Variations in Separate Hierarchy Using Variant Subsystems.

To get started with variant code generation, see Generate Code for Variant Subsystem Blocks.

Represent Variant Choices in Variant Subsystem Block

The slexVariantSubsystems model contains a Variant Subsystem block named Controller. The Controller block has two different implementations Linear Controller and Nonlinear Controller as variant choices.

model = "slexVariantSubsystems";
open_system(model)

To open the Controller block and view its variant choices, double-click the block and then click the Up to Parent button located in the toolbar at the top of the Simulink® model window.

The Linear Controller and Nonlinear Controller variant choice blocks have the same number of input ports and output ports as the containing Controller block. The variant choice blocks can have different numbers of input and output ports as described in Map Inports and Outports of Variant Choices in Variant Subsystem.

For information on variant choices, see Working with Variant Choices.

open_system(model+"/Controller")

Use of VariantMerge Blocks

The VariantMerge block is an internal block used by Simulink during the code generation process for models that contain Variant Subsystem blocks. The block facilitates the generation of preprocessor conditionals for different variant choices within a Variant Subsystem. When generating code, Simulink inserts a VariantMerge block at the input of each Outport block inside the Variant Subsystem block, which allows all the child subsystems to connect to the VariantMerge blocks. They differ from generic Merge blocks in that they only have one parameter, which is the number of inputs. This block is not available for manual use in models; it is only used internally during code generation.

The number of inputs for VariantMerge is determined and wired as shown in the figure below.

Specify Variant Controls for Variant Choice Selection

Each variant choice in the model is associated with a variant control. Variant controls determine which variant choice is active. By changing the value of the variant control, you can switch the active variant choice. While each variant choice is associated with a variant control, only one variant control can evaluate to true. When a variant control evaluates to true, Simulink activates the variant choice that corresponds to that variant control. For more information, see Introduction to Variant Controls.

1. Right-click the variant badge on the Controller block and select Block Parameters (Subsystem). In this example, each variant choice in the Controller block is associated with variant controls that are represented by the Simulink.VariantExpression objects VSS_LINEAR_CONTROLLER and VSS_NONLINEAR_CONTROLLER. The objects are defined in the the PreLoadFcn callback of Modeling > Model Settings > Model Properties. The VSS_LINEAR_CONTROLLER object has the variant condition VSS_MODE == 1 and VSS_NONLINEAR_CONTROLLER has the variant condition VSS_MODE == 2. These variant conditions are displayed in the Condition column of the Controller Block Parameters dialog box.

Here, VSS_MODE is a scalar MATLAB™ variant control variable. You can specify VSS_MODE as a different type of variant control variable based on your requirements. For example, if you intend to specify attributes, such as data type and storage class to control the appearance and placement of variant control variables in the generated code, specify variant control variable VSS_MODE as a Simulink.Parameter object. For more information, see Types of Variant Control Variables (Operands) in Variant Blocks.

2. In the MATLAB Command Window, set the value of VSS_MODE to 1 and simulate the model. During simulation, the variant condition VSS_MODE == 1 evaluates to true, activating the Linear Controller block.

VSS_MODE = 1;
lcSim = sim(model);
youtl = lcSim.yout;

3. Set the value of VSS_MODE to 2 and simulate the model again. During simulation, the variant condition VSS_MODE == 2 evaluates to true, activating the Nonlinear Controller block.

VSS_MODE = 2;
nlcSim = sim(model);
youtnl = nlcSim.yout;

This mechanism allows you to swap the active and inactive choices in the Controller block without modifying the model structure, making it flexible and adaptable to different scenarios.

If the Simulink.VariantExpression objects are not suitable for your requirements, you can use different types of variant controls as described in Compare Different Types of Variant Control Modes in Variant Blocks.

You can plot and compare the response of the Linear and Nonlinear controllers.

figure('Tag','CloseMe');
plot(lcSim.tout, youtl, 'r-', nlcSim.tout, youtnl, 'b-')
title('Response of Left Channel Linear and Nonlinear Controllers');
ylabel('Response');
xlabel('Time (seconds)');
legend('linear','nonlinear')

Configure Model for Generating Preprocessor Conditionals

By default, Simulink supports generating code only for a specific choice of a variant block. You can customize the model to generate code for multiple choices of the variant block using the Variant activation time parameter. Using the Variant activation time parameter enables you to set the active variant choice at different stages of simulation and code generation. This can improve the speed of simulation and allow you to reuse the artifacts from previous runs in code generation. It also enables you to analyze variant choices for incompatibilities, such as data type and dimension mismatches, prior to simulation and code generation. For more information, see Activate Variant During Different Stages of Simulation and Code Generation Workflow.

1. To generate code for Linear Controller and Nonlinear Controller blocks, check these settings:

  • The system target configuration file is specified as ert.tlc.

set_param(model,"SystemTargetFile","ert.tlc");
  • The Variant activation time parameter of the Controller block is set to code compile.

set_param(model+"/Controller","VariantActivationTime","code compile");
  • The Linear Controller and Linear Controller blocks are atomic.

lControllerPath = model+"/Controller/Linear Controller";
nlControllerPath = model+"/Controller/Nonlinear Controller";
set_param(lControllerPath,"TreatAsAtomicUnit","on");
set_param(nlControllerPath,"TreatAsAtomicUnit","on");

2. In the Apps tab of the toolstrip, navigate to Embedded Coder. For detailed information on the settings to generate code, see Generate Code Using Embedded Coder.

3. In the C code tab, select Build > Generate code. Observe that the generated code includes the logic for the Linear Controller and Nonlinear Controller blocks.

Alternatively, enter this command in the Command Window.

slbuild(model);
### Starting build procedure for: slexVariantSubsystems
### Successful completion of build procedure for: slexVariantSubsystems

Build Summary

Top model targets built:

Model                  Action                        Rebuild Reason                                    
=======================================================================================================
slexVariantSubsystems  Code generated and compiled.  Code generation information file does not exist.  

1 of 1 models built (0 models already up to date)
Build duration: 0h 0m 21.969s

Review Generated Code

1. In the C Code tab, select Open Report.

2. Locate and select the slexVariantSubsystems_types.h file from the left pane. This file defines variant controls by using the #define preprocessor directive. The value of the variant control variable VSS_MODE is 2.

#ifndef VSS_MODE
#define VSS_MODE                       2
#endif
#ifndef VSS_LINEAR_CONTROLLER
#define VSS_LINEAR_CONTROLLER          (VSS_MODE == 1)
#endif
#ifndef VSS_NONLINEAR_CONTROLLER
#define VSS_NONLINEAR_CONTROLLER       (VSS_MODE == 2)
#endif

3. Select the slexVariantSubsystems.c file from the left pane. Observe that the calls to the step and initialization functions contain the logic for the Linear Controller and Nonlinear Controller blocks and are guarded by C preprocessor conditional #if and #elif statements. When you compile this code, Simulink evaluates the preprocessor conditionals and compiles the code only for the active variant choice of the Controller block. You can then specify a different value for VSS_MODE and recompile the same code for any other variant choice of the Controller block.

/* Model step function */
void slexVariantSubsystems_step(void)
{
 #if VSS_LINEAR_CONTROLLER
 real_T denAccum;
 #endif
...
#if VSS_LINEAR_CONTROLLER
/* Outputs for Atomic SubSystem: '<S1>/Linear Controller' */
/* Update for DiscreteTransferFcn: '<S2>/Discrete Transfer Fcn' */
denAccum = (sine2 - 0.09 *
            slexVariantSubsystems_DW.DiscreteTransferFcn_states[0]) - 0.5 *
  slexVariantSubsystems_DW.DiscreteTransferFcn_states[1];
/* VariantMerge generated from: '<S1>/Out1' incorporates:
 *  DiscreteTransferFcn: '<S2>/Discrete Transfer Fcn'
 *  Sum: '<S2>/Add'
 */
slexVariantSubsystems_Y.Out1 = ((0.7 *
  slexVariantSubsystems_DW.DiscreteTransferFcn_states[1] +
  slexVariantSubsystems_DW.DiscreteTransferFcn_states[0]) + rtb_sine1) +
  rtb_sine3;
/* Update for DiscreteTransferFcn: '<S2>/Discrete Transfer Fcn' */
slexVariantSubsystems_DW.DiscreteTransferFcn_states[1] =
  slexVariantSubsystems_DW.DiscreteTransferFcn_states[0];
slexVariantSubsystems_DW.DiscreteTransferFcn_states[0] = denAccum;
#elif VSS_NONLINEAR_CONTROLLER
/* Outputs for Atomic SubSystem: '<S1>/Nonlinear Controller' */
/* VariantMerge generated from: '<S1>/Out1' incorporates:
 *  Lookup_n-D: '<S3>/1-D Lookup Table'
 *  Sin: '<Root>/sine2'
 *  Sum: '<S3>/Add'
 */
slexVariantSubsystems_Y.Out1 = (rtb_sine1 + look1_binlxpw(sine2,
  slexVariantSubsystems_ConstP.uDLookupTable_bp01Data,
  slexVariantSubsystems_ConstP.uDLookupTable_tableData, 10U)) + rtb_sine3;
#endif
...
}

Related Topics