Both for the rapid accelerator and the target code generation some specials are necessary. Because: The DataStruct…​Inspc SFBlocks contains parametrized data, which should be presented by generated struct with its access routines.

There are two important things:

  • Generating C-header and C-files for the parametrised data struct

  • specific tlc header to correctly insert the access to data in the generated code from the model.

See following chapters.

1. Generated code of DataStructMng_Inspc-instances

The DataStructMng_Inspc has a parameter fileDir - genSource. This parameter should be set with a variable. If the model runs in some simulation situations, that variable should be set with an empty string ''. Then no code generation is done. For the last simulation run before code generation that variable should be set with the file path to the directory where generated files are stored. Then the generation is done. See as example the _init.m mat script:

 InspcCtrl_init.m:

 %dstPathGenDataStruct  '';   %%left empty if no code generation is used yet.
 dstPathGenDataStruct  'test/Smlk/+InspcCtrl/genSrcInspc';

The last directory of this path have to be genSrcInspc like shown here. This directory is used in the generated source in the include statement, see below.

ParamDialgDataStructHeader

Image: Parameter dialog example InspcCtrl

In the headerfile a struct will be created with the type and parametrized name constellation of the instance in the model- The file looks like (example `…​/genSrcInspc/)

struct MeasPrep_ProgrSimpleExmplA_t;
struct PIDf_Ctrl_emC_t;
typedef struct Controller_ObjMod_t {
  union { ObjectJc object; } base;
  float W[2];
  float xs[2];
  float yctrl[2];
  float Y[2];

  /**...Comment.
   * @acclevel=22.
   */
  struct MeasPrep_ProgrSimpleExmplA_t * xMeas;

  /**...Comment.
   * @acclevel=22.
   */
  struct PIDf_Ctrl_emC_t * pid2;
} Controller_ObjMod_s;

extern_C ClassJc const refl_Controller_ObjMod;

The struct is based on ObjectJc which is necessary for ObjectOrientation for type check and for reflection access (Inspector). In a poor target software constellation this base structure can be reduced to 1 ` int32` variable for an ident number and the index of the type. This is enough for the Inspector-Target-Proxy concept (see [[InspcTargetProxy]) or for low level debug.

The data names come from the parameter, see image above. The types of the first 4 inputs come from the connected input signals from the model. The access rights are for the Inspector access via Reflection generation.

The two last elements in this struct comes from inputs with given type, which are handle in the model. In the struct for target code generation the type qualified memory addresses of the handles are stored. The access level is written in comment, because it is used for the generation of the reflection files for the Inspector access.

The headerfile contains also the prototype for the constructor and prototypes for the DataStructMng_Inspc S-function and the forward declaration for the reflection type:

extern_C ClassJc const refl_Controller_ObjMod;

void ctor_Controller_ObjMod(Controller_ObjMod_s* thiz);

/**Write data to DataStruct.
 * Note: The Simulink coder cannot decide whether it is a scalar, vector or pointer
 * Therefore all arguments are unique given as address (pointer)
 */
bool init__Controller_ObjMod(Controller_ObjMod_s* thiz, uint32 const* xMeas, uint32 const* pid2);

void step__Controller_ObjMod(Controller_ObjMod_s* thiz, float const* W, float const* xs, float const* yctrl, float const* Y);

Because the code generation for the target system cannot distinguish on this position between arrays or scalar, it uses always the reference to the input or output variable. Using ` memcpy` with the known array size in the structure is always fast, because ` memcpy` is usual optimized by the compiler for the fastest possible access.

Getter and setter routines for all Access…​Inspc S-Functions are generated till now (2021-03), but not used yet. The reason is: More flexibility in access, hence a direct access to the data is generated by the coder. The getter and setter should be enhanced with selection of array elements. This may be done still in future. But on the other hand the generated code is more simple with immediately access.

1.1. Code for set_DataStruct_Inspc

  1. it is TODO

From another example header ParameterOptimizer_ObjMod.h:

typedef struct {
  ....
  float tune_fY12;
  struct Par_PID_Ctrl_t * par_PID;
} ParameterOptimizer_ObjMod_s;
inline void get_par_PID_ParameterOptimizer_ObjMod(ParameterOptimizer_ObjMod_s* thiz, uint32* par_PID) {
  *par_PID  HANDLE_Handle2Ptr(thiz->par_PID);
}

Out DataStruct Inspc Tinit par PID

The get_par_…​(…​) is the access with the right shown FBlock: The type in the model and in the generated source is uint32, a handle. The meaning of the data is a reference (address, pointer value). In the struct the exact typed reference is stored how it is given in the appropriate DataStructInspc definition block where the type of the handle/reference is parametrized. The access converts the address value in the struct to the handle value. For a 32-bit target system it is an empty macro, only a type casting.


InitDataStruct par PID

This is the appropriate InitDataStruct_Inspc FBlock, which’s parameter defines the type. The type is tested in the Simulink run mode by reflection check, used for code generation and used in the target software. The variable of the code generated code are of type uint32. The HANDLE_Handle2Ptr is a simple macro which contains only a type cast if the target system has 32-bit-addresses. The address is stored immediately in the uint32 variable of the model. On low-level-debugging (single step) in the target code the correct type can be evaluated for showing the content in the debugger from the struct variable, that is explicit helpfull. The type is used for Inspector access on the target too. The reflection generation is done from this generated header file.

set DataStruct Example

inline void set_tune_fY12_ParameterOptimizer_ObjMod
  (ParameterOptimizer_ObjMod_s* thiz, float* tune_fY12) {
  thiz->tune_fY12  *tune_fY12;
}

The generated code for the right FBlock is shown above. It is adequate the get routine. direction is reverse. For vector assignments a memcpy is used too.


DataStruct forSet ParamDlg

The special provocation for the set FBlocks is: The associated DataStruct_Inspc FBlock must not set this value in any step time, only in the Tinit time as initial value. This is done via the 100 value of access rights in the parameter dialog. The 107 means additionally read only access.


2. tlc-file adaption

Some manually adapted tlc files are contained in …​/lib/+mkSfn/tlc_src/[xx].tlc and copied from there with the mex compilation to the adequate mex/tlc_c directory. The automatic generated tlc files (see [[Topic:.Smlk_de.C_ObjO.genSfH.]) are stored as …​/lib/+genSfn/Inspc/[xx].tlc

2.1. Initialization of the DataStructMng_Inspc in generated code

The manual adapted tlc file for DataStructMng_Inspc ` step_DataStructMng_Inspc_SfH.tlc` contains (shortened):

%function Start(block, system) Output
  %if (EXISTS(SFcnParamSettings.pinInherit))
    //
    //The %<SFcnParamSettings.typeData_param> is a super DataStructMng without own data.
    //
  %else
  {
    //jzTc: malloc for %<Type> %<ParamSettings.FunctionName>: %<Name>
    //create data for SFBlock= %<LibGetFormattedBlockPath(block)>,
    uint32 hthiz;
    %<SFcnParamSettings.typeData_param>_s* thiz =
      (%<SFcnParamSettings.typeData_param>_s*)
      malloc(sizeof(%<SFcnParamSettings.typeData_param>_s));
    memset(thiz, 0, sizeof(%<SFcnParamSettings.typeData_param>_s));
    //set the length in ObjectJc for test.
    CTOR_ObjectJc((ObjectJc*)thiz, thiz, sizeof(%<SFcnParamSettings.typeData_param>_s), refl_%<SFcnParamSettings.typeData_param>, 0);
    //NOTE: for 64 bit accelerator mode: register the pointer.
    //Set hthiz. For 32-bit target it is a simple macro.
    hthiz = registerPtr_Handle2Ptr(thiz, "%<SFcnParamSettings.typeData_param>");
    %<LibBlockDWork(thiz_h, "", "", "0")> = hthiz;  //Store thiz as uint32
    //call the init function ctor: ctor_%<SFcnParamSettings.typeData_param> init: init_%<SFcnParamSettings.typeData_param>
    ctor_%<SFcnParamSettings.typeData_param>(thiz);
    //out2: set the handle for the chain and super immediately after ctor.
    %<LibBlockOutputSignal(1, "", "", 0)> = hthiz;
  }
  %endif
%endfunction

This is the initializing (start-) operation for the code generation both for target and accelerator. The tlc generation (Mathworks-specific) use this as template. %<NAME> are outputted from the content of a NAME, comming from parameter or from model properties. To set the parameter the operation mdlRTW(…​) is a S-Function wrapper is essential. All %<SFcnParamSettings…​> comes from there.

The %<LibBlockOutputSignal(1, "", "", 0)> is the signal in the generated code (a variable) from output 1. The %<LibBlockDWork(thiz_h, "", "", "0")> is that variable in the generated code, which is assigned to the DWork-Pointer with name thiz_h

2.2. Update routine

%% Function: Update ============================================
%%
%% Note: The update function is used by the rapid accelerator
%%       or if the generated code should run in only one thread. Depending on code generation options.
%function Update(block, system) Output
  if(%<LibIsSFcnSampleHit("OutputPortIdx0")>) {
    %<doUpdateTinit(block, system)>
  } else if(%<LibIsSFcnSampleHit("InputPortIdx0")>) {
    %<doUpdateTstep(block, system)>
  }
%endfunction


%function UpdateForTID(block, system, tid) Output
  %assign tidInit = LibGetGlobalTIDFromLocalSFcnTID("OutputPortIdx0")    %%The TID for step_, the first step input port.
  %assign tidStep = LibGetGlobalTIDFromLocalSFcnTID("InputPortIdx0")    %%The TID for step_, the first step input port.
  %if tid == tidInit
    %<doUpdateTinit(block, system)>
  %elseif tid == tidStep
    %<doUpdateTstep(block, system)>
  %endif
%endfunction %% Update

Generally there are two update operations (same for output) in the tlc file. The Update(…​) will be executed for code generation if only one step routine should be generated. It depends on the settings on the code generator. The rapid accelerator may be set other than the target coder.

If more as one step routines are generated, the code generation uses the UpdateForTID(…​) routine. The %if tid == …​ is not generated in code, it is to control the code generation itself. It means, only the parts after %if till %else or %endif take place in the generated code. Other than in Update(…​) where the if(…​) is part of code (written without % before %if

The %<doUpdateTinit(block, system)> is a sub routine call for execution of code generation, not generated in the code. This tlc-sub-routine contains (shortened):

%function doUpdateTinit(block, system) Output
{
    //jzTc: update for Sfunction %<ParamSettings.FunctionName>: %<Name> %if(EXISTS(SFcnParamSettings.pinInherit))
      ...
    %else
      %assign hthiz = LibBlockDWork(thiz_h, "", "", "0")
    %endif
    uint32 hthiz = %<hthiz>;
    %<SFcnParamSettings.typeData_param>_s* thiz = PTR_Handle2Ptr(hthiz, %<SFcnParamSettings.typeData_param>_s);
    if(thiz !=null) { //it is possible that thiz is not set yet
        ....
        if(init_%<subName>_%<SFcnParamSettings.typeData_param>(thiz
          %<genInport(block, system, SFcnParamSettings.mInputInit , 0)>
        ) ){
          %<LibBlockOutputSignal(0, "", "", 0)> = hthiz;  //out1: set the handle
        } else {
          clrInitDone(); //repeat initialization till thiz is set.
        } //init
      } //init
    } else {
      clrInitDone(); //repeat initialization till thiz is set.
    }//thiz !=null
  }
%endfunction

Here the init_…​(…​) routine from the adequate data struct is called. If this routine returns false, clrInitDone() is called on the generated code. This routine is global, resets an initDone variable and forces repeating initialization. It is exactly that behaviour which is described in core modules in target language, chapter Initialization of aggregations.

2.3. Access to data - get_DataStruct_Inspc

The S-Function of the lib-FBlock libVishia_Inspc/get_DataStruct_Inspc is get_Access_DataStruct_Inspc_SfH. The get_Access_DataStruct_Inspc_SfH.tlc contains the following code (shorten):

%function doStepOutput(block, system) Output
  //SFBlock= %<LibGetFormattedBlockPath(block)>, code determined from parameter:
  %if(!WHITE_SPACE(block.SFcnParamSettings.typeDataIn_param))   (1)
    %assign typeData = block.SFcnParamSettings.typeDataIn_param (2)
    //  typeData_param is given: %<typeData>  (5)
  %else
    %assign srcBlock = LibBlockSrcSignalBlock(0, 0)             (3)
    %assign sys1 = srcBlock[0]
    %assign blk1 = srcBlock[1]
    %assign block1 = CompiledModel.System[sys1].Block[blk1]
    %with block1                                                (4)
      %assign typeData = SFcnParamSettings.typeData_param (4)
    %endwith
    //  srcBlock Type= %<TYPE(srcBlock)>, typesrc = %<typeData> (5)
    //  The input comes from %<LibGetFormattedBlockPath(block1)>, there typeData_param used.
  %endif
  {                                                             (6)
    %<typeData>_s* pData = PTR_Handle2Ptr(%<LibBlockInputSignal(0, "", "", 0)>, %<typeData>_s); (7)
    %if(WHITE_SPACE(SFcnParamSettings.datapath_param))          (8)
    //it was a inline function, fast not able to use for vector element addressing (9)
    //get_%<SFcnParamSettings.name_param>_%<typeData>(pData, %<LibBlockOutputSignalAddr(0, "", "", 0)>);
      %assign width = %<LibBlockOutputSignalWidth(0)>           (10)
	    %if width == 1                                          (11)
    %<LibBlockOutputSignal(0, "", "", 0)> = pData->%<SFcnParamSettings.name_param>;  //jzTc: scalar assignment
      %else
    //Info: Dimensions:%<LibBlockOutputSignalDimensions(0)>     #<12> NumDimensions:%<LibBlockOutputSignalNumDimensions(0)> Width:%<width>
    //Note: pData->%<SFcnParamSettings.name_param> designates a numeric array or a part of the array.
    //      In C/++ it is an address per access, not a left value, but the intrinsic array	for sizeof.
    //      The & before the src is not necessery but admissible. It is necessary if src is a struct.
    int sizecpy = sizeof(pData->%<SFcnParamSettings.name_param>);  //note: use sizeof(src), should be tested in model
    void* src = &pData->%<SFcnParamSettings.name_param>;        (13)
    memcpy( %<LibBlockOutputSignalAddr(0, "", "", 0)>, src, sizecpy);
	    %endif
	%else                                                       (14)
    //Access with given path to deeper instances:
    %<LibBlockOutputSignal(0, "", "", 0)> = pData->%<SFcnParamSettings.datapath_param>->%<SFcnParamSettings.name_param>;
    %endif
  }
%endfunction
  • 1) It is possible that the type of the ,,struct,, to access is given as parameter, stored in ,,typeDataIn_param,,, see chapter Parameter of FBlock get_DataStruct_Inspc. This type is necessary for {cp} language as pointer type.

  • 2) Then the type as tlc-Variable ,,typeData,, is assigned from the parameter.

  • 3) It it isn’t so (else), then the type should be able to get immediately from the connected FBlock on input 0. This is either a DataStructMng_Inspc FBlock, checkCast_DataStruct_Inspc, getData_DataStruct_Inspc or get_DataStruct_Inspc itself, from the type of the output data. The tlc-operation ,,LibBlockSrcSignalBlock(0, 0),, gets the necessary references and ,,CompiledModel.System[sys1].Block[blk1],, the FBlock description from the graphical model.

  • 4) Now the tlc-variable ,,typeData,, is filled with the given parameter value of this FBlock.

  • 5) The generated source code is completed with some comments about the situation. To make it obviously what’s happen in the model.

  • 6) Now the ,,typeData,, is known for tlc-Code generation and can be used.

  • 7) The %<typeData>_s* is the pointer type of the input signal, which comes from that source block. pData is the pointer to the data gotten from the inport handle. The PTR_Handle2Ptr(…​) converts the handle value on the input to the really pointer. It is the same value (simple macro) for a 32-bit-target system because the handle value on input is the pointer. The macro is important for Accelarator mode which runs in a 64-bit-Environment.

  • 8) The tlc-,,%if(WHITE_SPACE(…​,, checks whether this parameter is given. If it is empty, the next lines are valid, without ,,datapath_param,,, as immediately access to the data.

  • 9) It is the old, yet commented variant which does not regard access to vector elements. It may be useable, but with enhancement with the indices.

  • 10) The ,,LibBlockOutputSignalWidth(0),, is the number of elements of a 1-dimensional vector. It is 1 if the access is done to a scalar, it is also 1 if the access is done to a vector element.

  • 11) Then the ,,pData→%<SFcnParamSettings.name_param>,, is done immediately with the given ,,name_param,, which may contain ,,…​[1],, for an array access. ,,%<LibBlockOutputSignal(0, "", "", 0)>,, descripes the code to access immediately the output as left value.

  • 12) If the ,,LibBlockOutputSignalWidth(0),, is not 1, it is an assignment to a vector. Nevertheless the {cp} language does not accept a vector as left value, it is only a pointer. Hence an assignment as to a ,,struct,, variable cannot be done.

  • 13) Instead, a ,,memcpy,, should be used with the known ,,sizeof(…​),, and addresses. LibBlockOutputSignalAddr(0, "", "", 0) is the destination variable address.

  • 14) The ,,%else,, is regarded to the 8) ,,%if(WHITE_SPACE(…​,,. It is the variant with ,,datapath_param,,.

  • 15) The datapath is added between ,,→datapath→,,. The ,,name_param,, is used as given. It means, the access to an array is not possible, only to elements (because the property of {cp} to present an array as pointer, not intrinsic as instance). The same effort may be necessary as in 10) till 13) if it is need. In this version the access with path is only possible to elements, (or whole ,,struct,, data).

2.4. Write data - set_DataStruct_Inspc

Adequate it is for the set_DatatStructMng_Inspc_SfH.tlc:

%function doStepUpdate(block, system) Output
  %assign srcBlock = LibBlockSrcSignalBlock(1, 0)
  %assign sys1 = srcBlock[0]
  %assign blk1 = srcBlock[1]
  %assign block1 = CompiledModel.System[sys1].Block[blk1]
  %with block1
    %assign typeData = SFcnParamSettings.typeData_param
  %endwith
  {
    //SFBlock= %<LibGetFormattedBlockPath(block)>, Type= set_Access_DataStruct_Inspc_SfH, code determined from parameter:
    //srcBlock Type= %<TYPE(srcBlock)>, typesrc = %<typeData>
    %<typeData>_s* pData = PTR_Handle2Ptr(%<LibBlockInputSignal(1, "", "", 0)>, %<typeData>_s);
    set_%<SFcnParamSettings.name_param>_%<typeData>(pData, %<LibBlockInputSignalAddr(0, "", "", 0)>);    //it is a inline function, fast.
  }
%endfunction

This code generation rule generates the ,,set_…​,, routine for the generated ,,struct,, of the DataStructMng_Inspc-FBlock. It does not support yet (2021-03) the access to elements if a vector is given, it does not support a given ,,(StructType),,. It means the ,,StructType,, is always gotten from the connected FBlock in input. This can be enhanced in future. Yet it is a small restriction to the model.

2.5. Call and Trigger Function

It is TODO yet. It is tested yet only without code generation on some examples.