.. _legacy-code: =========================================== Using Legacy Code in Simulink and with QGen =========================================== Background ========== Launching legacy code in Simulink is achieved using S-function blocks and mex files. Mex file (MATLAB EXecutable, http://www.mathworks.se/help/matlab/matlab_external/introducing-mex-files.html) is a compiled binary format, proprietary to Mathworks. The legacy code written in C, C++, Fortran or Ada can be compiled to this format and then linked to a model through S-function blocks (called "Legacy Function" blocks). For executing simulation no information on original sources is required. In case a model containing Legacy S-function blocks is used for code generation in Real Time Workshop one needs to provide a special template file for each mex function containing the function prototype, reference to file containing function prototype etc. ``qgenc`` does not support this template format and relies only on the information contained in mdl and slx files. The only type of supported s-function is "Legacy function". There are two options to generate code from this block: * Encode all information about the external library in s-function parameters using the legacy_code tool and let ``qgenc`` generate the calls or * Generate a wrapper function using ``qgenc`` and write the actual call to the legacy function manually in this wrapper. Option A: specify library information in Simulink ================================================= This option assumes that the external library with legacy code is fully specified in S-function parameters. At minimum the information we need is the name of the header file of the external library and a function signature specifying the mapping to block ports and data types of each argument. The workflow consist of the steps as follows: * create S-function block using the legacy code tool (see instructions below) * generate code using QGen. The generated code will contain call to the function in external library * while compiling the code provide the legacy code module in include and lib paths Creating S-function blocks in Simulink -------------------------------------- There are several methods for creating Legacy Function blocks in Simulink. The only method, that stores all information required for generation is that using the Legacy Code Tool (http://www.mathworks.se/help/simulink/slref/legacy_code.html or http://www.mathworks.se/help/rtw/block-authoring-with-legacy-code-tool.html). Let us consider creating a block for function "double myFun (in1 double)" with specification in file my_module.h: .. image:: img/my_module_1.png .. code-block:: c #ifndef MYMODULE_H #define MYMODULE_H double myFun (double in1); #endif The first step is to create a datastructure introducing this function to the legacy code tool: .. code-block:: text % define name of the function in Simulink (and name of the mex file) % this name does not need to be the same as the referenced legacy function def.SFunctionName = ('myFun_sim') % define source file (body) def.SourceFiles = {'my_module.c'} % define header file (spec) % NB! This file is not required for successful generation of mex files % however, it is mandatory for linking the generated code to correct module def.HeaderFiles = {'my_module.h'} % define function prototype (see syntax of the prototype specification in the % next section) def.OutputFcnSpec = 'double y1 = myFun (double u1)' To generate a block Simulink is able to execute we run the legacy code tool .. code-block:: text % generate wrapper for the legacy code % this will create file 'myFun_sim.c' which is a wrapper for simulation legacy_code ('sfcn_cmex_generate', def) As a result the wrapper file appears: .. image:: img/my_module_2.png This needs to be compiled for Simulink .. code-block:: text % create the mex file (compile the wrapper and the legacy code) legacy_code ('compile', def) .. image:: img/my_module_3.png .. code-block:: text % generate Simulink block % this will create a new model with a block linked to the new mex file legacy_code ('slblock_generate', def) The last command in the template above will create a new model that contains the generated S-function block. .. image:: img/my_module_4.png Drag this block to the model where the S-function was required. .. image:: img/my_module_5.png The final model: .. image:: img/my_module_6.png After exporting the decoration file and generating code we can see that the code contains call to the myFun function: .. code-block:: c void legacy_demo_legacy_demo_comp (GAREAL In1, GAREAL *Out1) { GAREAL In1_out1; GAREAL myFun_sim_out1; /* Block legacy_demo/In1 */ In1_out1 = In1; /* End Block legacy_demo/In1 */ /* Block legacy_demo/myFun_sim */ myFun_sim_out1 = myFun (In1_out1); /* End Block legacy_demo/myFun_sim */ /* Block legacy_demo/Out1 */ *Out1 = myFun_sim_out1; /* End Block legacy_demo/Out1 */ } Function prototype syntax ------------------------- The goal of the function prototype is to define a mapping between the block interface and function arguments/return values. Special identifier patterns are used to denote block ports and mask parameters The syntax supported both by Simulink and ``qgenc`` contains the following elements: .. code-block:: text [ ] (, ... ) where: * ```` is a valid Simulink data type * ```` and ```` are references to inputs outputs or paramters in form * ``u`` -- input where in number of the input port * ``y`` -- output where is number of the output port * ``p`` -- parameter where is a parameter SParameter defined in MaskVariables * in case block input or output is an array then ```` may refer to its size: * ``size([u|y])`` -- length of the first dimension of input/output * ``size([u|y], 1)`` -- length of the first dimension of input/output * ``size([u|y], 2)`` -- length of the second dimension of input/output Option B: generate S-function wrappers ====================================== This option applies when the s-function block in not generated using the legacy code tool (or one did not provide the full prototype specification). When function prototype specification is not found in the block specification then ``qgenc`` will generate a module named _wrappers which contains a function for each generated block. The workflow is as follows: * Run ``qgenc`` on a Simulink model to generate code * ``qgenc`` will produce a module for each atomic subsystem + a special module containing function wrappers for each s-function with no prototype. * two files are generated for this special wrappers module: * ``_wrappers.[h|ads]`` -- the prototypes of wrapper functions * ``_wrappers.[c|adb].template`` -- empty function stubs for each wrapper function * the wrappers module will contain a function for each s-function block in form `` (, , ..., , , , ..., )`` * if this was the first time to generate code for this model then rename the file "_wrappers.[c|adb].template" to "_wrappers.[c|adb]" and write an appropriate function call in each wrapper function * in case of repeated code generation simply check that the function calls in the wrapper module generated earlier are not changed (the h/ads file is always overwritten, c/adb file remains inteact as the new file created by ``qgenc`` has ".template" suffix) * while compiling the code provide the legacy code module in include and lib paths Example ------- Let us consider a model mutlvect.mdl that calls a function mult_vect from library my_vect_ops multiplying a vector with constant. .. image:: img/my_vect_ops_1.png Library my_vect_ops ................... **my_vect_ops.h** .. code-block:: c #ifndef MY_VECT_OPS_H #define MY_VECT_OPS_H /** * Copies contents of vector to vector2 and multiplies each element * with multiplier. **/ void mult_vect (int *vector, int vector_len, int multiplier, int *vector2); #endif .. image:: img/my_vect_ops_3.png Generated wrapper functions ........................... The model contains a block named "sfun_mult_vect" with one input port of type int8[6], one input of type int8 and one output of type int8[6]. The wrappers generated by ``qgenc`` are as follows: **no_prototype_demo_wrappers.h** .. code-block:: c /* Copyright (C) Project P Consortium */ /* * @generated with GNAT Model Compiler 1.0w * Command line arguments: * -l c no_prototype_demo.mdl * --clean -t no_prototype_demo_types.txt */ #ifndef NO_PROTOTYPE_DEMO_WRAPPERS_H #define NO_PROTOTYPE_DEMO_WRAPPERS_H #include "qgen_types.h" extern void no_prototype_demo_wrappers_sfun_mult_vect (GAINT8 In1_out1[6], GAINT8 In2_out1, GAINT8 sfun_mult_vect_out1[6]); #endif /* @EOF */ **no_prototype_demo_wrappers.c.template** .. code-block:: c /* Copyright (C) Project P Consortium */ /* * @generated with GNAT Model Compiler 1.0w * Command line arguments: * -l c no_prototype_demo.mdl * --clean -t no_prototype_demo_types.txt */ #include "no_prototype_demo_wrappers.h" void no_prototype_demo_wrappers_sfun_mult_vect (GAINT8 In1_out1[6], GAINT8 In2_out1, GAINT8 sfun_mult_vect_out1[6]) { } /* @EOF */ Call to s-function .................. **no_prototype_demo.c** .. code-block:: c /* Copyright (C) Project P Consortium */ /* * @generated with GNAT Model Compiler 1.0w * Command line arguments: * -l c no_prototype_demo.mdl * --clean -t no_prototype_demo_types.txt */ #include "no_prototype_demo.h" void no_prototype_demo_no_prototype_demo_init (void) { } void no_prototype_demo_no_prototype_demo_comp (GAINT8 In1[6], GAINT8 In2, GAINT8 Out1[6]) { GAINT8 In1_out1[6]; GAINT8 In2_out1; GAINT8 sfun_mult_vect_out1[6]; /* Block no_prototype_demo/In1 */ for (GAUINT8 i = 0; i <= 5; i++) { In1_out1[i] = In1[i]; } /* End Block no_prototype_demo/In1 */ /* Block no_prototype_demo/In2 */ In2_out1 = In2; /* End Block no_prototype_demo/In2 */ /* Block no_prototype_demo/sfun_mult_vect */ (void) no_prototype_demo_wrappers_sfun_mult_vect (In1_out1, In2_out1, sfun_mult_vect_out1); /* End Block no_prototype_demo/sfun_mult_vect */ /* Block no_prototype_demo/Out1 */ for (GAUINT8 i_1 = 0; i_1 <= 5; i_1++) { Out1[i_1] = sfun_mult_vect_out1[i_1]; } /* End Block no_prototype_demo/Out1 */ } /* @EOF */ Manual code ........... The code written manually inside of the generated multvect_template stubs: **no_prototype_demo_wrappers.c** .. code-block:: c /* Copyright (C) Project P Consortium */ /* * @generated with GNAT Model Compiler 1.0w * Command line arguments: * -l c no_prototype_demo.mdl * --clean -t no_prototype_demo_types.txt */ #include "no_prototype_demo_wrappers.h" /* the legacy code specs */ #include "my_vect_ops.h" void no_prototype_demo_wrappers_sfun_mult_vect (GAINT8 In1_out1[6], GAINT8 In2_out1, GAINT8 sfun_mult_vect_out1[6]) { /* call external function. At this point the vector length is statically known, so we pass the length argument as hard-coded constant */ mult_vect (In1_out1, 6, In2_out1, sfun_mult_vect_out1); } /* @EOF */ From this point on, the no_prototype_demo_wrappers.c shall be made available at link time and it remains intact when new code is generated (unless the block interface changes). Compiling and linking the generated code ======================================== * The header file referenced in the HeaderFiles parameter shall be available at compile time. * The source file referenced in SourceFiles shall be available to the linker