Target Language Compiler | ![]() ![]() |
Inlining M-File S-Functions
All of the functionality of M-file S-functions can be inlined in the generated code. Writing a block target file for an M-file S-function is essentially identical to the process for a C MEX S-function.
Note that while you can fully inline an M-file S-function to achieve top performance - even with Simulink Accelerator - the MATLAB Math Library is not included with Real-Time Workshop, so any high-level MATLAB commands and functions you use in the M-file S-function must be written by hand in the block target file.
A quick example will illustrate the equivalence of C MEX and M-file S-functions for code generation. The M-file S-function timestwo.m
is equivalent to the C MEX S-function timestwo
. In fact, the TLC file for the C MEX S-function timestwo
will work for the M-file S-function timestwo.m
as well! Since TLC only requires the `root' name of the S-function and not its type, it is independent of the type of S-function. In the case of timestwo
, one line determines what the TLC file will be used for
%implements "timestwo" "C"
To try this out for yourself, copy file timestwo.m
from matlabroot
/toolbox/simulink/blocks/
to a temporary directory, then copy the file timestwo.tlc
from matlabroot
/toolbox/simulink/blocks/tlc_c/
to the same temporary directory. In MATLAB, cd
to the temporary directory and make a Simulink model with an S-function block that calls timestwo
. Since the MATLAB search path will find timestwo.m
in the current directory before finding the C MEX S-function timestwo
in the matlabpath
, Simulink will use the M-file S-function for simulation. Verify which S-function will be used by typing the MATLAB command
which timestwo
The answer you see will be the M-file S-function timestwo.m
in the temporary directory. Here is the sample model.
Upon generating code, you will find that the timestwo.tlc
file was used to inline the M-file S-function with code that looks like this (with an input signal width of 5 in this example).
/* S-Function Block: <Root>/m-file S-Function */ /* Multiply input by two */ { int_T i1; const real_T *u0 = &rtB.Gain[0]; real_T *y0 = &rtB.m_file_S_Function[0]; for (i1=0; i1 < 5; i1++) { y0[i1] = u0[i1] * 2.0; } }
As expected, each of the inputs, u0[i1]
, is multiplied by 2.0 to form the output value. The Outputs
method in the block target file used to generate this code was
%function Outputs(block, system) Output /* %<Type> Block: %<Name> */ %% /* Multiply input by two */ %assign rollVars = ["U", "Y"] %roll idx = RollRegions, lcv = RollThreshold, block, "Roller", rollVars %<LibBlockOutputSignal(0, "", lcv, idx)> = \ %<LibBlockInputSignal(0, "", lcv, idx)> * 2.0; %endroll %endfunction
Alter these temporary copies of the M-file S-function and the TLC file to see how they interact -- start out by just changing the comments in the TLC file and see it show up in the generated code, then work up to algorithmic changes.
Inlining Fortran (FMEX) S-Functions
The capabilities of Fortran MEX S-functions can be fully inlined using a TLC block target file. With a simple F MEX S-function version of the ubiquitous "timestwo" function, this interface can be illustrated. Here is the sample Fortran S-function code
C C FTIMESTWO.FOR C $ Revision: 1.1$ C C A sample FORTRAN representation of a C timestwo S-function. C Copyright 1990-2000 The MathWorks, Inc. C C===================================================== C Function: SIZES C C Abstract: C Set the size vector. C C SIZES returns a vector which determines model C characteristics. This vector contains the C sizes of the state vector and other C parameters. More precisely, C SIZE(1) number of continuous states C SIZE(2) number of discrete states C SIZE(3) number of outputs C SIZE(4) number of inputs C SIZE(5) number of discontinuous roots in C the system C SIZE(6) set to 1 if the system has direct C feedthrough of its inputs, C otherwise 0 C C===================================================== C SUBROUTINE SIZES(SIZE) C .. Array arguments .. INTEGER*4 SIZE(*) C .. Parameters .. INTEGER*4 NSIZES PARAMETER (NSIZES=6) SIZE(1) = 0 SIZE(2) = 0 SIZE(3) = 1 SIZE(4) = 1 SIZE(5) = 0 SIZE(6) = 1 RETURN END C C===================================================== C C Function: OUTPUT C C Abstract: C Perform output calculations for continuous C signals. C C===================================================== C .. Parameters .. SUBROUTINE OUTPUT(T, X, U, Y) REAL*8 T REAL*8 X(*), U(*), Y(*) Y(1) = U(1) * 2.0 RETURN END C C===================================================== C C Stubs for unused functions. C C===================================================== SUBROUTINE INITCOND(X0) REAL*8 X0(*) C --- Nothing to do. RETURN END SUBROUTINE DERIVS(T, X, U, DX) REAL*8 T, X(*), U(*), DX(*) C --- Nothing to do. RETURN END SUBROUTINE DSTATES(T, X, U, XNEW) REAL*8 T, X(*), U(*), XNEW(*) C --- Nothing to do. RETURN END SUBROUTINE DOUTPUT(T, X, U, Y) REAL*8 T, X(*), U(*), Y(*) C --- Nothing to do. RETURN END SUBROUTINE TSAMPL(T, X, U, TS, OFFSET) REAL*8 T,TS,OFFSET,X(*),U(*) C --- Nothing to do. RETURN END SUBROUTINE SINGUL(T, X, U, SING) REAL*8 T, X(*), U(*), SING(*) C --- Nothing to do. RETURN END
Copy the above code into file ftimestwo.for
in a convenient working directory.
Putting this into an S-function block in a simple model will illustrate the interface for inlining the S-function. Once your Fortran MEX environment is set up, prepare the code for use by compiling the S-function in a working directory along with the file simulink.for
from matlabroot
/simulink/src/
. This is done with the mex
command at the MATLAB command prompt.
mex -fortran ftimestwo.for simulink.for
And now reference this block from a simple Simulink model set with a fixed step solver and the grt
target.
The TLC code needed to inline this block is a modified form of the now familiar timestwo.tlc
. In your working directory, create a file named ftimestwo.tlc
and put this code into it.
%implements "ftimestwo" "C" %function Outputs(block, system) Output /* %<Type> Block: %<Name> */ %% /* Multiply input by two */ %assign rollVars = ["U", "Y"] %roll idx = RollRegions, lcv = RollThreshold, block, ... "Roller", rollVars %<LibBlockOutputSignal(0, "", lcv, idx)> = \ %<LibBlockInputSignal(0, "", lcv, idx)> * 2.0; %endroll %endfunction
Now you can generate code for the ftimestwo
Fortran MEX S-function. The resulting code fragment specific to ftimestwo
is
/* S-Function Block: <Root>/F-MEX S-Function */ /* Multiply input by two */ rtB.F_MEX_S_Function = rtB.Gain * 2.0;
Inlining Ada-MEX S-Functions
Like all the other S-functions, Ada MEX S-functions directly support inlining in the generated code. As an example, copy the times_two
Ada MEX S-function source code from directory matlabroot
/simulink/ada/examples/times_two/
to a temporary directory -- make sure to get both the times_two.adb
and times_two.ads
files. Once your Ada MEX environment is set up, compile times_two
from the MATLAB command line with
mex -ada times_two.adb
which makes a MEX file named ada_times_two.
mexext
. Use this MEX S-function in a model the same way as other S-functions.
To inline this function in C code, copy the timestwo.tlc
file from the previous M-file S-function example into the temporary directory and name it ada_times_two.tlc
. In the editor, modify the %implements
line to match the name of the S-function, in this case it should read
%implements "ada_times_two" "C"
After ensuring your model is set to use a fixed-step solver, generate C code for this model. The Ada Mex S-function code looks the same as it does for all the other S-function implementations
/* S-Function Block: <Root>/Ada-MEX S-Function */ /* Multiply input by two */ { int_T i1; const real_T *u0 = &rtb_temp1[0]; real_T *y0 = &rtb_temp1[0]; for (i1=0; i1 < 5; i1++) { y0[i1] = u0[i1] * 2.0; } }
If, as is likely, you wish to generate inlined Ada code for your model and its Ada MEX S-function using the Real-Time Workshop Ada Coder, you use a different TLC file -- one that generates Ada code. To get started with this, copy the timestwo.tlc
file from matlabroot
/toolbox/simulink/blocks/tlc_ada/
into the temporary directory and name it ada_times_two.tlc
. Once again, edit the %implements
line to read
%implements "ada_times_two" "Ada"
So the minimum necessary contents of the TLC file look like this.
%implements "ada_times_two" "Ada" %function Outputs(block, system) Output -- %<Type> Block: %<Name> %% -- Multiply input by two %assign rollVars = ["U", "Y"] %roll idx = RollRegions, lcv = RollThreshold, block, "Roller", rollVars %<LibBlockOutputSignal(0, "", lcv, idx)> := \ %<LibBlockInputSignal(0, "", lcv, idx)> * 2.0; %endroll %endfunction
Now select Ada Simulation Target for GNAT via the Tools -> Real-Time Workshop -> Options... menu using the Select... button. This target uses the rt_ada_sim.tlc
system target file and does not support blocks with continuous states, so it will be necessary to alter the sine wave source block to have a discrete sample time (choose it to be the same as the value chosen for the fixed-step solver's time step, for instance).
Generate code with the Build button. The model
.ads
file that is generated will contain this inlined code for the ada_times_two
S-function.
-- S-Function Block: <Root>/Ada-MEX S-Function -- Multiply input by two for I1 in 0 .. 4 loop RT_B_Temp1(I1) := RT_B_Temp1(I1) * 2.0; end loop;
Notice that the TLC code is relatively independent of the target language in use (C or Ada) -- for instance, the %roll
construct will create the correct C or Ada loop code based solely on whether the block target file implements "C" or "Ada". TLC constructs themselves are meant to be language-neutral; the only part that is language-specific are the operators, comments, and function name differences in the standard libraries.
TLC Coding Conventions
These guidelines help ensure that the programming style in each target file is consistent, and hence, more easily modifiable.
Begin Identifiers with Uppercase Letters
All identifiers in the Real-Time Workshop file begin with an uppercase letter. For example,
NumModelInputs 1 NumModelOutputs 2 NumNonVirtBlocksInModel 42 DirectFeedthrough yes NumContStates 10
Block records that contain a Name
identifier should start the name with an uppercase letter since the Name
identifier is often promoted into the parent scope. For example, a block may contain
Block { : : RWork [4, 0] : NumRWorkDefines 4 RWorkDefine { Name "TimeStampA" Width 1 StartIndex 0 } }
Since the Name
identifier within the RWorkDefine
record is promoted to PrevT
in its parent scope, it must start with an uppercase letter. The promotion of the Name
identifier into the parent block scope is currently done for the Parameter
, RWorkDefine
, IWorkDefine
, and PWorkDefine
block records.
The Target Language Compiler assignment directive (%assign
) generates a warning if you assign a value to an "unqualified" Real-Time Workshop identifier. For example,
%assign TID = 1
produces an error because TID
identifier is not qualified by Block
. However, a "qualified" assignment does not generate a warning.
%assign Block.TID = 1
does not generate a warning because the Target Language Compiler assumes the programmer is intentionally modifying an identifier since the assignment contains a qualifier.
Begin Global Variable Assignments with Uppercase Letters
Global TLC variable assignments should start with uppercase letters. A global variable is any variable declared in a system target file (grt.tlc
, mdlwide.tlc
, mdlhdr.tlc
, mdlbody.tlc
, mdlreg.tlc
, or mdlparam.tlc
), or within a function that uses the ::
operator. In some sense, global assignments have the same scope as Real-Time Workshop variables. An example of a global TLC variable defined in mdlwide.tlc
is
%assign InlineParameters = 1
An example of a global reference in a function is
%function foo() void %assign ::GlobalIdx = ::GlobalIdx + 1 %endfunction
Begin Local Variable Assignments with Lowercase Letters
Local TLC variable assignments should start with lowercase letters. A local TLC variable is a variable assigned inside a function. For example,
%assign numBlockStates = ContStates[0]
Begin Functions Declared in block.tlc files with Fcn
When you declare a function inside a block.tlc
file, it should start with Fcn
. For example,
%function FcnMyBlockFunc(...)
Note Functions declared inside a system file are global; functions declared inside a block file are local. |
Do Not Hard Code Variables Defined in commonsetup.tlc
Since the Real-Time Workshop tracks use of variables and generates code based on usage, you should use access routines instead of directly using a variable. For example, you should not use the following in your TLC file.
x = %<tInf>;
x = %<LibRealNonFinite(inf)>;
Similarly, instead of using %<tTID>
, use %<LibTID()>
. For a complete list of functions, see Chapter 9, TLC Function Library Reference.
All Real-Time Workshop global variables start with rt
and all Real-Time Workshop global functions start with rt_
.
Avoid naming global variables in your run-time interface modules that start with rt
or rt_
since they may conflict with Real-Time Workshop global variables and functions. These TLC variables are declared in commonsetup.tlc
.
This convention creates consistent variables throughout the target files. For example, the Gain block contains the following Outputs
function.
constant.tlc
.
sysIdx
and blkIdx
for system index
and block index, respectively.
rollVars
when using the %roll
construct.
sigIdx
and lcv
when looping
over RollRegions
and xidx
and xlcv
when looping over the states.
Example: Output
function in gain.tlc
%roll sigIdx = RollRegions, lcv = RollThreshold, ... block, "Roller", rollVars
Example: InitializeConditions
function in linblock.tlc
%roll xidx = [0:nStates-1], xlcv = RollThreshold,... block, "Roller", rollVars
Conditional Inclusion in Library Files
The Target Language Compiler function library files are conditionally included via guard code so that they may be referenced via %include
multiple times without worrying if they have previously been included. It is recommended that you follow this same practice for any TLC library files that you yourself create.
The convention is to use a variable with the same name as the base filename, uppercased and with underscores attached at both ends. So, a file named customlib.tlc
should have the variable _CUSTOMLIB_
guarding it.
As an example, the main Target Language Compiler function library, funclib.tlc
, contains this TLC code to prevent multiple inclusion.
%if EXISTS("_FUNCLIB_") == 0 %assign _FUNCLIB_ = 1 . . . %endif %% _FUNCLIB_
![]() | A Complete Example | Block Target File Methods | ![]() |