Main Content

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:

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.

top_model_gen_reentrant_code_simFuncBlk.png

The referenced model, DiscreteIntegratorFunction, consists of a Simulink Function block that defines function multiinstfunc and subsystem subsys_calc.

dinteg_expansion.png

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.

func_calc_expansion.png

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.

multiinstfunc_expansion.png

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.

functioncallsubsys_expansion.png

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.

multiinstfunc_triggerport.png

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.

functioncaller_prototype.png

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) and y = 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 prototype y = subsys_calc.func_calc(u). The prefix subsys_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 to C++ 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.

reentrantCodeSimFuncBlk_top_cppmodel2.png

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.

Related Topics