Generate Reentrant Code from Simulink Function Blocks
If you are using Embedded Coder®, you can generate reusable, reentrant code by representing an algorithm as a scoped Simulink Function block. Examples of when to generate reentrant code from Simulink Function blocks are when a function shares state between function callers within a model or for client/server applications. You can generate code that is highly modularized by using multiple instances of a shared Simulink Function block in an export-function model. The code generator produces function code and associates each use or call to the function with instance-specific data. The scope of the function depends on whether you place the function at the root level of model or in a subsystem.
The code generator produces reentrant function code when you configure a:
Top model with model configuration parameter Code interface packaging set to
Reusable function
orC++ class
(C++ only).Referenced model with Total number of instances allowed per top model set to
Multiple
.
Call a function represented by a scoped Simulink Function block from one level above, at the same level, or from a level below the level of the function definition. You can scope a function in an atomic or nonvirtual subsystem, but function call accessibility is limited to the same level or below of the hierarchy. The function name does not have to be unique.
Identify Requirements
Before and while you design your model, consider:
How many instances of each function are required?
Do you need to restrict call sites for a function to the model containing the function definition?
Do you need for a function to interface with signals in the local environment, but keep those signals hidden from callers?
Do functions need to communicate directly with each other?
Do functions need to connect to external I/O?
Do you need to log function output?
Create Model
Use the example model component ClientServerCommunication
to see how to use Simulink Function blocks to generate reentrant C code. Use example model CppComponent
if you prefer to generate C++ code. Open the model and examine the model hierarchy.
The top level of the model includes a function-call subsystem and two instances of a referenced model.
The referenced model, DiscreteIntegratorFunction
, consists of a Simulink Function block that defines function multiinstfunc
and subsystem subsys_calc
.
The subsystem consists of a Simulink Function block. This use of a Simulink Function block shows how you can limit the scope of the function that the block defines to the model that contains the subsystem. The code generator produces function code for func_calc
and associates each call to the function with instance-specific data. The data includes states, such as data stored in memory.
The Simulink Function block that defines function multiinstfunc
uses a Function Caller block to invoke function func_calc
. That Simulink Function block also shows that it can interface to signals in the local environment of the block through Inport and Outport blocks.
At the top level of model ClientServerCommunication
, the function-call subsystem uses Function Caller blocks to invoke the two instances of function multiinstfunc
. The code generator produces function code and associates each call with instance-specific data.
Configure Model and Model Elements
Configure Simulink Function Blocks
Configure the Simulink Function blocks by setting parameters for the function Trigger Port block. For the code generator to produce reentrant code from Simulink Function blocks:
Configure block instances with the same function name.
Set block parameter Function visibility to
scoped
.
In this example, the function name for the Simulink Function blocks in the two instances of the referenced model DiscreteIntegratorFunction
is specified as multiinstfunc
.
Configure Function Callers
Configure blocks to call the Simulink Function block. You can call a Simulink Function block from a Function Caller block, Stateflow® Chart block, or MATLAB Function block. For this example, use Function Caller blocks. For each of the blocks, set a value for the block parameter Function prototype. Start typing a prototype. For example, type y
. Prototype options based on function definitions in the model appear in a selection list. Select the prototype that corresponds to each function call.
For this example, the prototypes are configured as follows:
In the function-call subsystem, the prototypes for the function callers are configured as
y = Instance1.multiinstfunc(u)
andy = Instance2.multiinstfunc(u).
The Instancen prefix identifies each function invocation uniquely and associates the invocation with its own data set.The function caller in function
multiinstfunc
is configured with the prototypey = subsys_calc.func_calc(u)
. The prefixsubsys_calc
identifies the subsystem that contains the function definition.
For this example, the input and output argument specifications and sample time retain default settings.
Subsystem Configuration
Configuration changes are not required for the subsystem in the example model. When you include a Simulink Function block in a subsystem, the code generator
Scopes the function to the model that includes the subsystem.
Treats the subsystem as an atomic unit.
Configure Referenced Model
Configure the referenced model that includes a Simulink Function block:
In the Block Parameters dialog box, set parameter Model name to the referenced model file name. For this example, the model name is
DiscreteIntegratorFunction.slx
.Set model configuration parameter Total number of instances allowed per top model to
Multiple
.To generate a C++ class interface for the reference model, set model configuration parameter Language to
C++
and parameter Code interface packaging toC++ class
.
Optionally, you can customize model entry-point function interfaces. You can specify entry-point function names. For the execution (step) entry-point function you can configure the function name and arguments. Custom entry-point function interfaces can minimize changes to existing external code that you integrate with the generated code. The example uses the default function interfaces. See Configure Entry-Point Function Interfaces for Simulink Function and Function Caller Blocks.
Configure Top Model
Configure the top model for a model component. If you want the model component (that is, the top model) to be reusable, set model configuration parameter Code interface packaging to Reusable
function. If you are using Embedded Coder® and generating C++ code, you can set this parameter to C++ class
. In either case, also:
Set model configuration parameter Multi-instance code error diagnostic to
Error
.Set model configuration parameter Pass root-level I/O as to
Part of model data structure
.
Optionally, you can customize model entry-point function interfaces. You can specify entry-point function names. For the execution (step) entry-point function you can configure the function name and arguments. Custom entry-point function interfaces can minimize changes to existing external code that you integrate with the generated code. The example uses the default function interfaces. See Configure Entry-Point Function Interfaces for Simulink Function and Function Caller Blocks.
Generate and Inspect C Code
Generate C code for the model.
Function Code for Multi-Instance Simulink Function Block
When you place a scoped Simulink Function block in a referenced model that you use multiple times in another model, the code generator places the function code in the model.c file for the referenced model. For this example, the code generator places function code for multiinstfunc
in slprj/ert/DiscreteIntegratorFunction/DiscreteIntegratorFunction.c
.
real_T DiscreteIntegratorFunction_multiinstfunc(DiscreteIntegratorFunc_RT_MODEL * const const DiscreteIntegratorFunctionrtM, const real_T rtu_u) { real_T rty_y_0; real_T rtb_TmpLatchAtIn2Outport1; real_T rtb_TmpLatchAtInOutport1; rtb_TmpLatchAtInOutport1 = *DiscreteIntegratorFunctionrtM->DiscreteIntegratorFurtextInport.rtu_In1; rtb_TmpLatchAtIn2Outport1 = *DiscreteIntegratorFunctionrtM->DiscreteIntegratorFurtextInport.rtu_In2; *DiscreteIntegratorFunctionrtM->DiscreteIntegratorFrtextOutport.rty_Out2 = 2.0 * rtb_TmpLatchAtInOutport1; DiscreteIntegratorFun_func_calc(DiscreteIntegratorFunctionrtM, rtb_TmpLatchAtIn2Outport1, DiscreteIntegratorFunctionrtM->DiscreteIntegratorFrtextOutport.rty_Out1); rty_y_0 = DiscreteIntegratorFunctionrtM->dwork.DiscreteIntegrator_DSTATE; DiscreteIntegratorFunctionrtM->dwork.DiscreteIntegrator_DSTATE += 0.1 * rtu_u; return rty_y_0; }
Function Code for a Simulink Function Block Defined in a Subsystem
The code generator places the function code for a Simulink Function block that you define in a subsystem in the model.c file for the model that contains the subsystem. For this example, the code generator places the function code for func_calc in slprj/ert/DiscreteIntegratorFunction/DiscreteIntegratorFunction.c
.
void DiscreteIntegratorFun_func_calc(DiscreteIntegratorFunc_RT_MODEL * const DiscreteIntegratorFunctionrtM, real_T rtu_u, real_T *rty_y) { DiscreteIntegratorFunctionrtM->dwork.calcMem = DiscreteIntegratorFunctionrtM->dwork.UnitDelay_DSTATE; *rty_y = DiscreteIntegratorFunctionrtM->dwork.calcMem; DiscreteIntegratorFunctionrtM->dwork.UnitDelay_DSTATE = rtu_u * 0.07; }
Structure that Stores Multi-Instance Data for Reusable Functions
The code generator uses a structure similar to the real-time model (RT_MODEL) data structure to store the multi-instance data associated with a reusable function. The code generator defines the structure in slprj/ert/DiscreteIntegratorFunction/DiscreteIntegratorFunction.h
.
typedef struct DiscreteIntegratorFunct_tag_RTM DiscreteIntegratorFunc_RT_MODEL;
Initialization Code for Multi-Instance Referenced Model
For each instance of a referenced model that includes the same scoped Simulink Function block, the code generator produces initialization and startup function code. A single copy of the initialization code is defined in slprj/ert/DiscreteIntegratorFunction/DiscreteIntegratorFunction.c
.
void DiscreteIntegratorFunctio_Start(DiscreteIntegratorFunc_RT_MODEL *const DiscreteIntegratorFunctionrtM, const real_T *rtu_In1, const real_T *rtu_In2, real_T *rty_Out2, real_T *rty_Out1) { DiscreteIntegratorFunctionrtM->DiscreteIntegratorFurtextInport.rtu_In1 = rtu_In1; DiscreteIntegratorFunctionrtM->DiscreteIntegratorFurtextInport.rtu_In2 = rtu_In2; DiscreteIntegratorFunctionrtM->DiscreteIntegratorFrtextOutport.rty_Out2 = rty_Out2; DiscreteIntegratorFunctionrtM->DiscreteIntegratorFrtextOutport.rty_Out1 = rty_Out1; }
void DiscreteIntegratorFu_initialize(const char_T **rt_errorStatus, DiscreteIntegratorFunc_RT_MODEL *const DiscreteIntegratorFunctionrtM) { { rtmSetErrorStatusPointer(DiscreteIntegratorFunctionrtM, rt_errorStatus); } }
The initialization code is called for each instance of a referenced model that contains the same Simulink Function block. That code is in file ClientServerCommunication.c
in the build folder.
... DiscreteIntegratorFu_initialize(rtmGetErrorStatusPointer(rtM), (&(rtM->Instance1))); DiscreteIntegratorFu_initialize(rtmGetErrorStatusPointer(rtM), (&(rtM->Instance2))); DiscreteIntegratorFunctio_Start((&(rtM->Instance1)), &rtU->In2, &rtU->In3, &rtY->Out2, &rtY->Out3); DiscreteIntegratorFunctio_Start((&(rtM->Instance2)), &rtU->In4, &rtU->In5, &rtY->Out4, &rtY->Out5); ...
Top Model Entry-Point Function Declarations
The model header file ClientServerCommunication.h
includes extern declarations for top model initialize, terminate, and execution (run) entry-point functions.
extern void ClientServerCommunication_initialize(RT_MODEL *const rtM); extern void Run(RT_MODEL *const rtM);
Reference Model Entry-Point Function Declarations
Header file slprj/ert/DiscreteIntegratorFunction/DiscreteIntegratorFunction.h
includes extern declarations for reference model entry-point functions.
extern void DiscreteIntegratorFu_initialize(const char_T **rt_errorStatus, DiscreteIntegratorFunc_RT_MODEL *const DiscreteIntegratorFunctionrtM); extern real_T DiscreteIntegratorFunction_multiinstfunc (DiscreteIntegratorFunc_RT_MODEL * const DiscreteIntegratorFunctionrtM, const real_T rtu_u); extern void DiscreteIntegratorFunctio_Start(DiscreteIntegratorFunc_RT_MODEL * const DiscreteIntegratorFunctionrtM, const real_T *rtu_In1, const real_T *rtu_In2, real_T *rty_Out2, real_T *rty_Out1);
Generate and Inspect C++ Code
If you are using Embedded Coder, you can generate a C++ class interface for the model code. For this example, use the model CppComponent.
Function C++ Code for Multi-Instance Simulink Function Block
When you place a scoped Simulink Function block in a referenced model that you use multiple times in another model, the code generator places the function code in the model.cpp file for the referenced model. For this example, the code generator places function code for multiinstfunc
in slprj/ert/FuncDintegCPP/FuncDintegCPP.cpp
.
void FuncDintegCPP::multiinstfunc(const real_T rtu_u, real_T *rty_y) { real_T rtb_TmpLatchAtIn2Outport1; real_T rtb_TmpLatchAtInOutport1; rtb_TmpLatchAtInOutport1 = *FuncDintegCPPrtrtu_In1; rtb_TmpLatchAtIn2Outport1 = *FuncDintegCPPrtrtu_In2; *FuncDintegCPPrtrty_Out2 = 2.0 * rtb_TmpLatchAtInOutport1; FuncDintegCPP_func_calc(rtb_TmpLatchAtIn2Outport1, FuncDintegCPPrtrty_Out1); *rty_y = FuncDintegCPPrtDW.DiscreteIntegrator_DSTATE; FuncDintegCPPrtDW.DiscreteIntegrator_DSTATE += 0.1 * rtu_u; }
Function Code for a Simulink Function Block Defined in a Subsystem
The code generator places the function code for a Simulink Function block that you define in a subsystem in model.cpp for the model that contains the subsystem. For this example, the code generator places the function code for func_calc
in slprj/ert/FuncDintegCPP/FuncDintegCPP.cpp
.
void FuncDintegCPP::FuncDintegCPP_func_calc(real_T rtu_u, real_T *rty_y) { FuncDintegCPPrtDW.calcMem = FuncDintegCPPrtDW.UnitDelay_DSTATE; *rty_y = FuncDintegCPPrtDW.calcMem; FuncDintegCPPrtDW.UnitDelay_DSTATE = rtu_u * 0.07; }
Structure that Stores Multi-Instance Data for Reusable Functions
The code generator uses a structure similar to the real-time model (RT_MODEL) data structure to store the multi-instance data associated with a reusable function. The code generator defines the structure in slprj/ert/FuncDintegCPP/FuncDintegCPP.h
.
typedef struct FuncDintegCPP_tag_RTM FuncDintegCPP_RT_MODEL;
Initialization Code for Multi-Instance Referenced Model
For each instance of a referenced model that includes the same scoped Simulink Function block, the code generator produces initialization and startup function code. A single copy of the initialization code is defined in slprj/ert/FuncDintegCPP/FuncDintegCPP.cpp
.
void FuncDintegCPP::start(const real_T *rtu_In1, const real_T *rtu_In2, real_T *rty_Out2, real_T *rty_Out1) { FuncDintegCPPrtrtu_In1 = rtu_In1; FuncDintegCPPrtrtu_In2 = rtu_In2; FuncDintegCPPrtrty_Out2 = rty_Out2; FuncDintegCPPrtrty_Out1 = rty_Out1; } ...
The initialization code is called for each instance of a referenced model that contains the same Simulink Function block. That code is in file CppComponent.cpp
in the build folder.
void CppComponent::initialize() { Instance1MDLOBJ0.setErrorStatusPointer(rtmGetErrorStatusPointer((&rtM))); Instance2MDLOBJ1.setErrorStatusPointer(rtmGetErrorStatusPointer((&rtM))); Instance1MDLOBJ0.start(&rtU.In2, &rtU.In3, &rtY.Out2, &rtY.Out3); Instance2MDLOBJ1.start(&rtU.In4, &rtU.In5, &rtY.Out4, &rtY.Out5); }
Top Model Class Declaration
The model header file CppComponent.h
includes the class declaration for the top model.
class CppComponent { public: RT_MODEL * getRTM(); ExtU rtU; ExtY rtY; void initialize(); void Run(); // Constructor CppComponent(); // Destructor ~CppComponent(); private: FuncDintegCPP Instance1MDLOBJ0; FuncDintegCPP Instance2MDLOBJ1; RT_MODEL rtM; };
Reference Model Class Declaration
Header file slprj/ert/FuncDintegCPP/FuncDintegCPP.h
includes the class declaration for the reference model.
class FuncDintegCPP { public: void start(const real_T *rtu_In1, const real_T *rtu_In2, real_T *rty_Out2, real_T *rty_Out1); void multiinstfunc(const real_T rtu_u, real_T *rty_y); FuncDintegCPP_RT_MODEL * getRTM(); void setErrorStatusPointer(const char_T **rt_errorStatus); // Constructor FuncDintegCPP(); // Destructor ~FuncDintegCPP(); private: FuncDintegCPP_DW FuncDintegCPPrtDW; const real_T *FuncDintegCPPrtrtu_In1;// '<Root>/In1' const real_T *FuncDintegCPPrtrtu_In2;// '<Root>/In2' real_T *FuncDintegCPPrtrty_Out2; // '<Root>/Out2' real_T *FuncDintegCPPrtrty_Out1; // '<Root>/Out1' void FuncDintegCPP_func_calc(real_T rtu_u, real_T *rty_y); FuncDintegCPP_RT_MODEL FuncDintegCPPrtM; };
Limitations
These code generation limitations apply to export-function models that include multiple instances of a Simulink Function block.
You must set the model configuration parameter Pass root-level I/O as to Part of model data structure
.
Generated code is compatible with single-threaded execution only. To avoid race conditions for shared signal data, invoke instances of the function code from the same execution thread.
You cannot:
Generate code if the export-function model includes model variants.
Enable the external mode data exchange interface.