8. Using Legacy Code in Simulink and with QGen¶
8.1. 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.
8.2. 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
8.2.1. 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:
#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:
% 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
% 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:
This needs to be compiled for Simulink
% create the mex file (compile the wrapper and the legacy code)
legacy_code ('compile', def)
% 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.
Drag this block to the model where the S-function was required.
The final model:
After exporting the decoration file and generating code we can see that the code contains call to the myFun function:
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 */
}
8.2.2. 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:
[<return datatype> <return var>] <function name> (<arg1>, <arg2> ... <argN>)
where:
<return datatype>
is a valid Simulink data type<return var>
and<argX>
are references to inputs outputs or paramters in form
u<no>
– input <no> where <no> in number of the input porty<no>
– output <no> where <no> is number of the output portp<no>
– parameter <no> where <no> is a parameter SParameter<no> defined in MaskVariables- in case block input or output is an array then
<argX>
may refer to its size:
size([u|y]<no>)
– length of the first dimension of input/output <no>size([u|y]<no>, 1)
– length of the first dimension of input/output <no>size([u|y]<no>, 2)
– length of the second dimension of input/output <no>
8.3. 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 <modelname>_wrappers which contains a
function for each generated block. The workflow is as follows:
Run
qgenc
on a Simulink model to generate codeqgenc
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:
<modelname>_wrappers.[h|ads]
– the prototypes of wrapper functions<modelname>_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
<s-function_name> (<inport 1>, <inport 2>, ..., <inport N>, <outport1>, <outport 2>, ..., <outport M>)
if this was the first time to generate code for this model then rename the file “<modelname>_wrappers.[c|adb].template” to “<modelname>_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
8.3.1. Example¶
Let us consider a model mutlvect.mdl that calls a function mult_vect from library my_vect_ops multiplying a vector with constant.
8.3.1.1. Library my_vect_ops¶
my_vect_ops.h
#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
8.3.1.2. 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
/* 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
/* 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 */
8.3.1.3. Call to s-function¶
no_prototype_demo.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 */
8.3.1.4. Manual code¶
The code written manually inside of the generated multvect_template stubs:
no_prototype_demo_wrappers.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).
8.4. 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