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
objects Simulink.VariantExpression
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
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 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 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: 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++; }