S-Functions That Support Expression Folding
Use expression folding to increase the efficiency of code generated by your own inlined S-Function blocks, by calling macros provided in the S-Function API.
The S-Function API lets you specify whether a given S-Function block should nominally accept expressions at a given input port. A block should not always accept expressions. For example, if the address of the signal at the input is used, expressions should not be accepted at that input, because it is not possible to take the address of an expression.
The S-Function API also lets you specify whether an expression can represent the computations associated with a given output port. When you request an expression at a block's input or output port, the Simulink® engine determines whether or not it can honor that request, given the block's context. For example, the engine might deny a block's request to output an expression if the destination block does not accept expressions at its input, if the destination block has an update function, or if multiple output destinations exist.
The decision to honor or deny a request to output an expression can also depend on the category of output expression the block uses.
To take advantage of expression folding in your S-functions, you should understand when to request acceptance and generation of expressions for specific blocks. You do not have to understand the algorithm by which the Simulink engine chooses to accept or deny these requests. However, if you want to trace between the model and the generated code, it is helpful to understand some of the more common situations that lead to denial of a request.
Categories of Output Expressions
When you implement a C MEX S-function, you can specify whether the code corresponding to a block's output is to be generated as an expression. If the block generates an expression, you must specify that the expression is constant, trivial, or generic.
A constant output is only valid for cases where the block’s output is only dependent on the block's parameters and is not dependent on the block's inputs or states.
A trivial output expression is an expression that can be repeated, without a performance penalty, which happens when the output port has multiple output destinations. The output expression should not refer to the value of any of the block's inputs since the input might be an expression itself. For example, the output of a Unit Delay block is defined as a trivial expression because the output expression is simply a direct access to the block's state and no other data.
A generic output expression is an expression that should be assumed to have a performance penalty if repeated. As such, a generic output expression is not suitable for repeating when the output port has multiple output destinations. For instance, the output of a Sum block is a generic rather than a trivial expression because it is costly to recompute a Sum block output expression as an input to multiple blocks.
Examples of Trivial and Generic Output Expressions
Consider this block diagram. The Delay block has multiple destinations, yet its output is designated as a trivial output expression, so that it can be used more than once without degrading the efficiency of the code.
This code excerpt shows the code generated from the Unit Delay
block in this block diagram. The three root outputs are directly assigned from
the state of the Unit Delay block, which is stored in a field of
the global data structure rtDWork
. Since the assignment is
direct, without expressions, there is no
performance penalty associated with using the
trivial expression for multiple destinations.
void MdlOutputs(int_T tid) { ... /* Outport: <Root>/Out1 incorporates: * UnitDelay: <Root>/Unit Delay */ rtY.Out1 = rtDWork.Unit_Delay_DSTATE; /* Outport: <Root>/Out2 incorporates: * UnitDelay: <Root>/Unit Delay */ rtY.Out2 = rtDWork.Unit_Delay_DSTATE; /* Outport: <Root>/Out3 incorporates: * UnitDelay: <Root>/Unit Delay */ rtY.Out3 = rtDWork.Unit_Delay_DSTATE; ... }
The code generated shows how code is generated for Sum blocks with single and multiple destinations.
On the other hand, consider the Sum blocks in this model:
The upper Sum block in the model generates the signal labeled
non_triv
. Computation of this output signal involves two
multiplications and an addition. If the Sum block's output were permitted to
generate an expression even when the block had multiple destinations, the
block's operations would be duplicated in the generated code. In this case, the
generated expressions would proliferate to four multiplications and two
additions. This would degrade the efficiency of the program. Accordingly the
output of the Sum block is not allowed to be an expression because it has
multiple destinations.
The Simulink engine does not permit the output of the upper Sum block to be an
expression because the signal non_triv
is routed to two
output destinations. Instead, the result of the multiplication and addition
operations is stored in a temporary variable (rtb_non_triv)
that is referenced twice in the statements that follow, as seen in the code
excerpt below.
In contrast, the lower Sum block, which has only a single output destination
(Out2
), does generate an expression.
void MdlOutputs(int_T tid) { /* local block i/o variables */ real_T rtb_non_triv; real_T rtb_Sine_Wave; /* Sum: <Root>/Sum incorporates: * Gain: <Root>/Gain * Inport: <Root>/u1 * Gain: <Root>/Gain1 * Inport: <Root>/u2 * * Regarding <Root>/Gain: * Gain value: rtP.Gain_Gain * * Regarding <Root>/Gain1: * Gain value: rtP.Gain1_Gain */ rtb_non_triv = (rtP.Gain_Gain * rtU.u1) + (rtP.Gain1_Gain * rtU.u2); /* Outport: <Root>/Out1 */ rtY.Out1 = rtb_non_triv; /* Sin Block: <Root>/Sine Wave */ rtb_Sine_Wave = rtP.Sine_Wave_Amp * sin(rtP.Sine_Wave_Freq * rtmGetT(rtM_model) + rtP.Sine_Wave_Phase) + rtP.Sine_Wave_Bias; /* Outport: <Root>/Out2 incorporates: * Sum: <Root>/Sum1 */ rtY.Out2 = (rtb_non_triv + rtb_Sine_Wave); }
Specify the Category of an Output Expression
The S-Function API provides macros that let you declare whether an output of a block should be an expression, and if so, to specify the category of the expression. This table specifies when to declare a block output to be a constant, trivial, or generic output expression.
Types of Output Expressions
Category of Expression | When to Use |
---|---|
Constant | Use only if block output is a direct memory access to a block parameter and does not depend on the value of any block inputs or states. |
Trivial | Use only if block output is an expression that can
appear multiple times in the code without reducing
efficiency (for example, a direct memory access to a
field of the |
Generic | Use if output is an expression, but not constant or trivial. |
You must declare outputs as expressions in the
mdlSetWorkWidths
function using macros defined in the
S-Function API. The macros have the following arguments:
SimStruct *S
: pointer to the block'sSimStruct
.int idx:
zero-based index of the output port.bool value:
pass in TRUE if the port generates output expressions.
The following macros are available for setting an output to be a constant, trivial, or generic expression:
void ssSetOutputPortConstOutputExprInRTW(SimStruct *S, int idx, bool value)
void ssSetOutputPortTrivialOutputExprInRTW(SimStruct *S, int idx, bool value)
void ssSetOutputPortOutputExprInRTW(SimStruct *S, int idx, bool value)
The following macros are available for querying the status set by prior calls to the macros above:
bool ssGetOutputPortConstOutputExprInRTW(SimStruct *S, int idx)
bool ssGetOutputPortTrivialOutputExprInRTW(SimStruct *S, int idx)
bool ssGetOutputPortOutputExprInRTW(SimStruct *S, int idx)
The set of generic expressions is a superset of the set of trivial expressions, and the set of trivial expressions is a superset of the set of constant expressions.
Therefore, when you query an output that has been set to be a constant
expression with ssGetOutputPortTrivialOutputExprInRTW
, it
returns True
. A constant expression is considered a trivial
expression because it is a direct memory access that can be repeated without
degrading the efficiency of the generated code.
Similarly, an output that has been configured to be a constant or trivial
expression returns True
when queried for its status as a
generic expression.
Acceptance or Denial of Requests for Input Expressions
A block can request that its output be represented in code as an expression. Such a request can be denied if the destination block cannot accept expressions at its input port. Furthermore, conditions independent of the requesting block and its destination blocks can prevent acceptance of expressions.
A block should not be configured to accept expressions at its input port under the following conditions:
The block must take the address of its input data. It is not possible to take the address of most types of input expressions.
The code generated for the block references the input more than once (for example, the Abs or Max blocks). This would lead to duplication of a potentially complex expression and a subsequent degradation of code efficiency.
If a block refuses to accept expressions at an input port, then a block that is connected to that input port is not permitted to output a generic or trivial expression.
A request to output a constant expression is not denied, because there is no performance penalty for a constant expression, and the software can take the parameter’s address.
S-Function API to Specify Input Expression Acceptance
The S-Function API provides macros that let you:
Specify whether a block input should accept nonconstant expressions (that is, trivial or generic expressions).
Query whether a block input accepts nonconstant expressions.
By default, block inputs do not accept nonconstant expressions.
You should call the macros in your mdlSetWorkWidths
function. The macros have these arguments:
SimStruct *S
: pointer to the block's SimStruct.int idx:
zero-based index of the input port.bool value:
pass in TRUE if the port accepts input expressions; otherwise pass in FALSE.
The macro available for specifying whether or not a block input should accept a nonconstant expression is:
void ssSetInputPortAcceptExprInRTW(SimStruct *S, int portIdx, bool value)
The corresponding macro available for querying the status set by
any prior calls to
ssSetInputPortAcceptExprInRTW
is:
bool ssGetInputPortAcceptExprInRTW(SimStruct *S, int portIdx)
Denial of Block Requests to Output Expressions
Even after a specific block requests that it be allowed to generate an output expression, that request can be denied for generic reasons. These reasons include, but are not limited to:
The output expression is nontrivial, and the output has multiple destinations.
The output expression is nonconstant, and the output is connected to at least one destination that does not accept expressions at its input port.
The output is a test point.
The output has been assigned an external storage class.
The output must be stored using global data (for example is an input to a merge block or a block with states).
The output signal is complex.
You do not need to consider these generic factors when deciding whether or not to utilize expression folding for a particular block. However, these rules can be helpful when you are examining generated code and analyzing cases where the expression folding optimization is suppressed.
Expression Folding in a TLC Block Implementation
To take advantage of expression folding, modify the TLC block implementation of an inlined S-Function such that it informs the Simulink engine whether it generates or accepts expressions at its
Input ports, as explained in S-Function API to Specify Input Expression Acceptance.
Output ports, as explained in Categories of Output Expressions.
This topic discusses required modifications to the TLC implementation.
Expression Folding Compliance
In the BlockInstanceSetup
function of your S-function,
register your block to be compliant with expression folding. Otherwise,
expression folding requested or allowed at the block's outputs or inputs will be
disabled, and temporary variables will be used.
To register expression folding compliance, call the TLC library function
LibBlockSetIsExpressionCompliant(block)
, which is
defined in
.
For example:matlabroot
/rtw/c/tlc/lib/utillib.tlc
%% Function: BlockInstanceSetup =========================================== %% %function BlockInstanceSetup(block, system) void %% %<LibBlockSetIsExpressionCompliant(block)> %% %endfunction
You can conditionally disable expression folding at the inputs and outputs of a block by making the call to this function conditionally.
If you override one of the TLC block implementations provided by the code generator with your own implementation, you should not make the preceding call until you have updated your implementation.
Output Expressions
The BlockOutputSignal
function is used to generate code
for a scalar output expression or one element of a nonscalar output expression.
If your block outputs an expression, you should add a
BlockOutputSignal
function. The prototype of the
BlockOutputSignal
is
%function BlockOutputSignal(block,system,portIdx,ucv,lcv,idx,retType) void
The arguments to BlockOutputSignal
are these:
block
: the record for the block for which an output expression is being generatedsystem
: the record for the system containing the blockportIdx
: zero-based index of the output port for which an expression is being generateducv
: user control variable defining the output element for which code is being generatedlcv
: loop control variable defining the output element for which code is being generatedidx
: signal index defining the output element for which code is being generatedretType
: character vector defining the type of signal access desired:"Signal"
specifies the contents or address of the output signal"SignalAddr"
specifies the address of the output signal
The BlockOutputSignal
function returns a character vector
for the output signal or address. The character vector should enforce the
precedence of the expression by using opening and terminating parentheses,
unless the expression consists of a function call. The address of an expression
can only be returned for a constant expression; it is the address of the
parameter whose memory is being accessed. The code implementing the
BlockOutputSignal
function for the Constant block
is:
%% Function: BlockOutputSignal ================================================= %% Abstract: %% Return the reference to the parameter. This function *may* %% be used by Simulink when optimizing the Block IO data structure. %% %function BlockOutputSignal(block,system,portIdx,ucv,lcv,idx,retType) void %switch retType %case "Signal" %return LibBlockParameter(Value,ucv,lcv,idx) %case "SignalAddr" %return LibBlockParameterAddr(Value,ucv,lcv,idx) %default %assign errTxt = "Unsupported return type: %<retType>" %<LibBlockReportError(block,errTxt)> %endswitch %endfunction
The code implementing the BlockOutputSignal
function for
the Relational Operator block is:
%% Function: BlockOutputSignal ================================================= %% Abstract: %% Return an output expression. This function *may* %% be used by Simulink when optimizing the Block IO data structure. %% %function BlockOutputSignal(block,system,portIdx,ucv,lcv,idx,retType) void %switch retType %case "Signal" %assign logicOperator = ParamSettings.Operator %if ISEQUAL(logicOperator, "~=") %assign op = "!=" elseif ISEQUAL(logicOperator, "==") %assign op = "==" %else %assign op = logicOperator %endif %assign u0 = LibBlockInputSignal(0, ucv, lcv, idx) %assign u1 = LibBlockInputSignal(1, ucv, lcv, idx) %return "(%<u0> %<op> %<u1>)" %default %assign errTxt = "Unsupported return type: %<retType>" %<LibBlockReportError(block,errTxt)> %endswitch %endfunction
Expression Folding for Blocks with Multiple Outputs
When a block has a single output, the Outputs
function in
the block's TLC file is called only if the output port is not an expression.
Otherwise, the BlockOutputSignal
function is called.
If a block has multiple outputs, the Outputs
function is
called if any output port is not an expression.
The Outputs
function should guard against generating code
for output ports that are expressions. This is achieved by guarding sections of
code corresponding to individual output ports with calls to
LibBlockOutputSignalIsExpr()
.
For example, consider an S-Function with two inputs and two outputs, where
The first output, y0, is equal to two times the first input.
The second output, y1, is equal to four times the second input.
The Outputs
and BlockOutputSignal
functions for the S-function are shown in this code excerpt:
%% Function: BlockOutputSignal ================================================= %% Abstract: %% Return an output expression. This function *may* %% be used by Simulink when optimizing the Block IO data structure. %% %function BlockOutputSignal(block,system,portIdx,ucv,lcv,idx,retType) void %switch retType %case "Signal" %assign u = LibBlockInputSignal(portIdx, ucv, lcv, idx) %case "Signal" %if portIdx == 0 %return "(2 * %<u>)" %elseif portIdx == 1 %return "(4 * %<u>)" %endif %default %assign errTxt = "Unsupported return type: %<retType>" %<LibBlockReportError(block,errTxt)> %endswitch %endfunction %% %% Function: Outputs ================================================= %% Abstract: %% Compute output signals of block %% %function Outputs(block,system) Output %assign rollVars = ["U", "Y"] %roll sigIdx = RollRegions, lcv = RollThreshold, block, "Roller", rollVars %assign u0 = LibBlockInputSignal(0, "", lcv, sigIdx) %assign u1 = LibBlockInputSignal(1, "", lcv, sigIdx) %assign y0 = LibBlockOutputSignal(0, "", lcv, sigIdx) %assign y1 = LibBlockOutputSignal(1, "", lcv, sigIdx) %if !LibBlockOutputSignalIsExpr(0) %<y0> = 2 * %<u0>; %endif %if !LibBlockOutputSignalIsExpr(1) %<y1> = 4 * %<u1>; %endif %endroll %endfunction
Comments for Blocks That Are Expression-Folding-Compliant
In the past, blocks preceded their outputs code with comments of the form
/* %<Type> Block: %<Name> */
When a block is expression-folding-compliant, the initial line shown above is
generated automatically. Do not include the comment as part of the block's TLC
implementation. Register additional information by using the
LibCacheBlockComment
function.
The LibCacheBlockComment
function takes a character
vector as an input, defining the body of the comment, except for the opening
header, the final newline of a single or multiline comment, and the closing
trailer.
The following TLC code illustrates registering a block comment. Note the use
of the function LibBlockParameterForComment
, which returns
a character vector, suitable for a block comment, specifying the value of the
block parameter.
%openfile commentBuf $c(*) Gain value: %<LibBlockParameterForComment(Gain)> %closefile commentBuf %<LibCacheBlockComment(block, commentBuf)>