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 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 Input and Output Ports 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).

2. Associate each variant choice in the Controller block with variant controls that are represented by the Simulink.VariantExpression objects Vss_Linear_Controller and Vss_NonLinear_Controller. The Vss_Linear_Controller object has the variant condition V == 1 and Vss_NonLinear_Controller has the variant condition V == 2.

The Simulink.VariantExpression objects enable you to reuse common variant conditions across models and help you encapsulate complex variant condition expressions. Alternatively, you can use other types of variant control variables as per your requirement.

Vss_Linear_Controller = Simulink.VariantExpression("V==1");
Vss_NonLinear_Controller = Simulink.VariantExpression("V==2");
set_param("slexVariantSubsystems/Controller/Linear Controller", "VariantControl", "Vss_Linear_Controller")
set_param("slexVariantSubsystems/Controller/Nonlinear Controller", "VariantControl", "Vss_NonLinear_Controller")

Here, V is a scalar MATLAB™ variant control variable. You can specify V 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 V 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 V to 1 and simulate the model. During simulation, the variant condition V == 1 evaluates to true, activating the Linear Controller block.

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

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

V = 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:

Model                  Build Reason                                         Status                        Build Duration
========================================================================================================================
slexVariantSubsystems  Information cache folder or artifacts were missing.  Code generated and compiled.  0h 0m 9.3064s 

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

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 V is 2.

cfile=fullfile(pwd, "slexVariantSubsystems_ert_rtw", "slexVariantSubsystems_types.h");
coder.example.extractLines(cfile, "Validate the variant control variables", "/* MW_VALIDATE_PREPROCESSOR_VARIANT_CHOICES", 1, 0);
 * Validate the variant control variables are consistent with the model requirements
 */
#ifndef V
#define V                              2
#endif

#ifndef Vss_Linear_Controller
#define Vss_Linear_Controller          (V == 1)
#endif

#ifndef Vss_NonLinear_Controller
#define Vss_NonLinear_Controller       (V == 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 V and recompile the same code for any other variant choice of the Controller block.

cfile=fullfile(pwd, "slexVariantSubsystems_ert_rtw", "slexVariantSubsystems.c");
coder.example.extractLines(cfile, '/* Model step', '/* Model initialize', 1, 0);
/* Model step function */
void slexVariantSubsystems_step(void)
{

#if Vss_Linear_Controller

  real_T denAccum;

#endif

  real_T rtb_sine1;
  real_T rtb_sine3;
  real_T sine2;

  /* Sin: '<Root>/sine1' */
  if (slexVariantSubsystems_DW.systemEnable != 0) {
    rtb_sine1 = ((slexVariantSubsystems_M->Timing.clockTick0) * 0.01);
    slexVariantSubsystems_DW.lastSin = sin(1.0471975511965976 * rtb_sine1);
    slexVariantSubsystems_DW.lastCos = cos(1.0471975511965976 * rtb_sine1);
    slexVariantSubsystems_DW.systemEnable = 0;
  }

  rtb_sine1 = ((slexVariantSubsystems_DW.lastSin * 0.99994516936551214 +
                slexVariantSubsystems_DW.lastCos * -0.010471784116245792) *
               0.99994516936551214 + (slexVariantSubsystems_DW.lastCos *
    0.99994516936551214 - slexVariantSubsystems_DW.lastSin *
    -0.010471784116245792) * 0.010471784116245792) * 0.1;

  /* End of Sin: '<Root>/sine1' */

  /* Sin: '<Root>/sine2' */
  if (slexVariantSubsystems_DW.systemEnable_e != 0) {
    sine2 = ((slexVariantSubsystems_M->Timing.clockTick0) * 0.01);
    slexVariantSubsystems_DW.lastSin_g = sin(sine2);
    slexVariantSubsystems_DW.lastCos_n = cos(sine2);
    slexVariantSubsystems_DW.systemEnable_e = 0;
  }

  /* Sin: '<Root>/sine2' */
  sine2 = ((slexVariantSubsystems_DW.lastSin_g * 0.99995000041666526 +
            slexVariantSubsystems_DW.lastCos_n * -0.0099998333341666645) *
           0.99995000041666526 + (slexVariantSubsystems_DW.lastCos_n *
            0.99995000041666526 - slexVariantSubsystems_DW.lastSin_g *
            -0.0099998333341666645) * 0.0099998333341666645) * 2.0;

  /* Sin: '<Root>/sine3' */
  if (slexVariantSubsystems_DW.systemEnable_b != 0) {
    rtb_sine3 = ((slexVariantSubsystems_M->Timing.clockTick0) * 0.01);
    slexVariantSubsystems_DW.lastSin_i = sin(0.52359877559829882 * rtb_sine3);
    slexVariantSubsystems_DW.lastCos_i = cos(0.52359877559829882 * rtb_sine3);
    slexVariantSubsystems_DW.systemEnable_b = 0;
  }

  rtb_sine3 = ((slexVariantSubsystems_DW.lastSin_i * 0.99998629224742674 +
                slexVariantSubsystems_DW.lastCos_i * -0.00523596383141958) *
               0.99998629224742674 + (slexVariantSubsystems_DW.lastCos_i *
    0.99998629224742674 - slexVariantSubsystems_DW.lastSin_i *
    -0.00523596383141958) * 0.00523596383141958) * 0.3;

  /* End of Sin: '<Root>/sine3' */

  /* Outputs for Atomic SubSystem: '<Root>/Controller' */
#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;

  /* End of Outputs for SubSystem: '<S1>/Linear Controller' */
  /* End of Outputs for SubSystem: '<Root>/Controller' */
#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;

  /* End of Outputs for SubSystem: '<S1>/Nonlinear Controller' */
#endif

  /* Update for Sin: '<Root>/sine1' */
  rtb_sine1 = slexVariantSubsystems_DW.lastSin;
  slexVariantSubsystems_DW.lastSin = slexVariantSubsystems_DW.lastSin *
    0.99994516936551214 + slexVariantSubsystems_DW.lastCos *
    0.010471784116245792;
  slexVariantSubsystems_DW.lastCos = slexVariantSubsystems_DW.lastCos *
    0.99994516936551214 - rtb_sine1 * 0.010471784116245792;

  /* Update for Sin: '<Root>/sine2' */
  rtb_sine1 = slexVariantSubsystems_DW.lastSin_g;
  slexVariantSubsystems_DW.lastSin_g = slexVariantSubsystems_DW.lastSin_g *
    0.99995000041666526 + slexVariantSubsystems_DW.lastCos_n *
    0.0099998333341666645;
  slexVariantSubsystems_DW.lastCos_n = slexVariantSubsystems_DW.lastCos_n *
    0.99995000041666526 - rtb_sine1 * 0.0099998333341666645;

  /* Update for Sin: '<Root>/sine3' */
  rtb_sine1 = slexVariantSubsystems_DW.lastSin_i;
  slexVariantSubsystems_DW.lastSin_i = slexVariantSubsystems_DW.lastSin_i *
    0.99998629224742674 + slexVariantSubsystems_DW.lastCos_i *
    0.00523596383141958;
  slexVariantSubsystems_DW.lastCos_i = slexVariantSubsystems_DW.lastCos_i *
    0.99998629224742674 - rtb_sine1 * 0.00523596383141958;

  /* Update absolute time for base rate */
  /* The "clockTick0" counts the number of times the code of this task has
   * been executed. The resolution of this integer timer is 0.01, which is the step size
   * of the task. Size of "clockTick0" ensures timer will not overflow during the
   * application lifespan selected.
   */
  slexVariantSubsystems_M->Timing.clockTick0++;
}

Related Topics