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
objects Simulink.VariantExpression
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
object. For more information, see Types of Variant Control Variables (Operands) in Variant Blocks.Simulink.Parameter
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 tocode compile
.
set_param(model+"/Controller","VariantActivationTime","code compile");
The
Linear Controller
andLinear 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 ... }