C C S - F U N C T I O N S & F O R T R A N C ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C C ... Techniques to get your Fortran code to talk to Simulink C C 1 2 3 4 5 6 7 X C23456-89012345678901234567890123456789012345678901234567890123456789012 C C C Copyright 1990-2000 The MathWorks, Inc. C C $Revision: 1.2 $ C ---------------------------- Contents ------------------------------ I) Incorporating Fortran code into Your Simulink Models - Two Ways 1) Overview - Why Fortran? - Differences between Level 1 and Level 2 S-functions 2) Pure Fortran: Level 1 F-MEX S-functions - The Fortran MEX template file - An example - Inline code generation with the example 3) Gateway: Level 2 C-MEX Gateway to Fortran Code - Gateway Template file - The Nuts and Bolts of Interfacing C and Fortran o) Mex Environment o) Techniques and Resources for Interfacing C and Fortran o) Compiler Compatibility o) Symbol decorations cause runtime errors o) Read the Fortran compiler manual o) Try Using CFortran o) If all else fails, use utilities... o) Obtaining a Fortran Compiler - Constructing the Gateway o) Where to "wire up" the Pieces o) SUBROUTINE vs. PROGRAM o) Arguments to a SUBROUTINE o) Arguments to a FUNCTION o) Interfacing to COMMON blocks o) An Example II) Examining Legacy Code for Pitfalls o) Find the States o) Sample Times o) Use Flints If Needed o) Considerations for Real-Time ------------------------------- Body ------------------------------- I) Incorporating Fortran code into Your Simulink Models - Two Ways ================================================================== 1) Overview ----------- There are two main strategies to executing Fortran code from Simulink. One is from a Level 1 Fortran-MEX (F-MEX) S-function, the other is from a Level 2 C-MEX Gateway S-function. Each has its advantages and both can be incorporated into code generated by the Real-Time Workshop if you so choose. Why Fortran? ------------ As programming languages go, most scientists and engineers have more exposure to Fortran for numerical problem solving than with C language programming. There is a large and growing body of Fortran code at companies and institutions as well as public domain codes that solve nontrivial problems that is not available in C. For existing codes, translation of Fortran to C is possible using f2c, but this adds an extra step in code maintenance and introduces unnecessary modifications to the code that can lead to suboptimal performance. Translation is also another source of errors in the modeling process. So when Simulink cannot provide an elegant solution to a problem and custom programming is needed, Fortran is a viable option. Typical scenarios considered are: - programming new code as a general tool to be used in an S-function (and potentially other environments besides Simulink) - adapting or importing existing code for use in Simulink via an S-function Differences between Level 1 and Level 2 S-functions --------------------------------------------------- The original S-function interface has been dubbed "Level 1". As the capabilities of Simulink grew over the years, the S-function API was rearchitected into the more extensible "Level 2" API. This allows S-functions to have all the capabilities of a full Simulink model (except automatic algebraic loop identification and solving) and to grow as Simulink grows. Only the C-MEX and Ada-MEX interfaces have Level 2 support. 2) Pure Fortran: Level 1 F-MEX S-functions ------------------------------------------- The Fortran MEX template file ----------------------------- A template file for Fortran MEX S-functions is located at /simulink/src/sfuntmpl_fortran.for. To use it, first copy it to another file name, then edit it to perform the operations you need. Compile it using the mex command and the file is ready to use. The template file compiles as-is and merely copies the input to the output. An example ---------- The example file /simulink/src/sfun_timestwo_for.c implements an S-function that multiplies its input by two. A Level 1 S-function's input/output is limited to using the REAL*8 datatype, a.k.a. DOUBLE PRECISION, which is equivalent to a double in C. Of course, the internal calculations can use whatever datatypes you need. To try and use this S-function as a pre-built MEX file, open the model sfcndemo_timestwo_for.mdl by typing >> sfcndemo_timestwo_for at the MATLAB prompt and then run the model. Inline code generation with the example --------------------------------------- Real-Time Workshop users can use a sample block target file for sfun_timestwo_for.mex to generate code for sfcndemo_timestwo_for. If you want to see how to inline your own Fortran MEX file, see the example at /toolbox/simulink/blocks/ tlc_c/sfun_timestwo_for.tlc and read the "Target Language Compiler" user's manual from Real-Time Workshop. 3) Gateway: Level 2 C-MEX Gateway to Fortran Code ------------------------------------------------- To use the features of a Level 2 S-function with Fortran code, it is necessary to write a skeleton S-function in C that has code for interfacing to Simulink and also calls your Fortran code. Using the C-MEX S-function as a Gateway is quite simple if you are writing the Fortran code from scratch. If instead your Fortran code already exists as a standalone simulation, there is some work to be done to identify parts of the code that need to be registered with Simulink, such as continuous states if you are using variable step solvers or static variables if you want to have multiple copies of the S-function in a Simulink model. Template File ------------- The file /simulink/src/sfungate.c is a C-MEX template file for calling into a Fortran subroutine. It will work with simple Fortran subroutine if you modify the Fortran subroutine name in the code. The Nuts and Bolts of Interfacing C and Fortran ----------------------------------------------- o) Mex Environment Remember that 'mex -setup' needs to find both the C and the Fortran compilers. If you install or change compilers it is necessary to run 'mex -setup' after installation or reconfiguration of compilers. Test out the installation and setup using sample MEX files from MATLAB's C and Fortran MEX examples in /extern/examples/mex as well as Simulink's examples, which are located in /simulink/src. o) Techniques and Resources for interfacing C and Fortran o) Compiler Compatibility Your C and Fortran compilers need to use the same object format. If you use the compilers explicitly supported by the mex command this is not a problem. When using the C gateway to Fortran, it is possible to use Fortran compilers not supported by the mex command, but only if the object file format is compatible with the C compiler format. Common object formats include ELF and COFF. An additional consideration is nonstandard stack frame cleanup. All C compilers on MATLAB-supported platforms have the caller clean up the stack after the callee has returned. Some Fortran compilers have the callee clean up the stack pointer before returning to the caller. Linking Fortran object files to C in this situation results in a corrupted stack frame and program crashes. A workaround must then be found in this situation if you are to be successful. The Compaq Visual Fortran (a.k.a. Digital Fortran) 6.0+ has a command line option to force caller cleanup of the stack. This is done with the /iface:cref flag. If you are using mex to do the compilation of Fortran modules using Digital Fortran, it is necessary to append this option to the normal Fortran compiler flags that mex uses. This is done with the following syntax for Digital Fortran 6.0: >> mex -v COMPFLAGS#"$COMPFLAGS /iface:cref" -c my_fortran_sub.for \ -f ..\..\bin\win32\mexopts\df60opts.bat o) Symbol decorations can cause runtime errors For example, g77 will decorate subroutine names with a trailing underscore "_" and change all letters to lower case when in its default configuration, so that 'MySubroutine' becomes 'mysubroutine_'. You can either recognize this and adjust the C function prototype or you can possibly alter the Fortran compiler's name decoration policy via command line switches, though this varies from one compiler to the next. The Compaq Visual (Digital) Fortran compiler on Windows is slightly different. It will change all symbol names to UPPER case and does not append a trailing underscore, so that 'MySubroutine' becomes 'MYSUBROUTINE'. This behavior can be altered somewhat using compiler options. o) Fortran math library symbols may not match C math library symbols. For example A^B in Fortran will call library function pow_dd, which is not in the C math library. In these cases, you must tell mex to explicitly link in the Fortran math library. For gcc environments, these routines are usually found in /usr/local/lib/libf2c.a, /usr/lib/libf2c.a or equivalent. The mex command becomes: % mex -L/usr/local/lib -lf2c Unix note: the -lf2c option follows the conventional unix library linking syntax where '-l' is the library option itself and 'f2c' is the unique part of the library file's name, libf2c.a. Be sure to use the -L option for the library search path since -I is only followed while searching for include files. The f2c package can be obtained for Windows and Unix environments from the Internet. The file libf2c.a is usually part of g77 distributions, or else the file is not needed as the symbols match. In obscure cases it must be installed separately, but even this is not difficult once the need for it is identified. Windows note: since the Fortran and C environments are not the same under Windows as they are on unix, it is necessary to work a little harder to get the gateway C to link with the Fortran subroutine. Here is a sample mex command for linking Digital Fortran objects to C under MS Visual C 6.0: >> mex -v LINKFLAGS#"$LINKFLAGS dfor.lib dfconsol.lib dfport.lib /LIBPATH:$DF_ROOT\DF98\LIB" mysfun_gateway.c my_fortran_sub.obj ... all on one line. This example assumes that DF_ROOT is the root directory for the installation of the Fortran compiler and that its library files dfor.lib, dfconsol.lib, and dfport.lib are located in subdirectory DF98\LIB under DF_ROOT. DF_ROOT will be automatically configured using: >> mex -setup when it finds your Fortran compiler. For Digital Fortran 6.0, the default installation location (what mex calls DF_ROOT) is: C:\Program Files\Microsoft Visual Studio Once mex -setup completes, the enviroment variable DF_ROOT will be available and is used by the mex options files \bin\win32\mexopts\df60opts.bat and df50opts.bat. o) Make sure you read the Fortran compiler manual about altering symbol decoration policies. The manual just might have the information you need! o) Try Using CFortran to Automatically Create an Interface CFortran is a tool for automated interface generation between C and Fortran modules, in either direction. Search the web for 'cfortran' or visit (Q3, 2000): http://www-zeus.desy.de/~burow/cfortran/ for downloading. o) If all else fails, use utilities such as 'od' (octal dump) to get the symbol names. Example: 'od -s 2 ') to view strings and symbols in binary (.obj) files: the symbol name must be contained *somewhere* in the object file, it is possible to use a brute force approach! These binary utilities can be had for Windows as well. MKS is one company that has commercial versions of powerful unix utilities, though most can also be had for free on the web. 'hexdump' is another common program for viewing binary files. As an example, here is the output of 'od -s 2 sfun_atmos_for.o' on linux: 0000115 EÈÙ 0000136 EÈÙ 0000271 EȺ 0000467 ÇEÈ@ 0000530 ÇEÈ 0000575 EÈÙEäØ5@ 0001267 Cf¦VC-ò:C 0001323 :|.-:8Æ#8ýKw6 0001353 ?333@ 0001364 333À 0001414 01.01 0001425 GCC: (GNU) egcs-2.91.66 19990314/Linux 0001522 .symtab 0001532 .strtab 0001542 .shstrtab 0001554 .text 0001562 .rel.text 0001574 .data 0001602 .bss 0001607 .note 0001615 .comment 0003071 sfun_atmos_for.for 0003101 gcc2_compiled. 0003120 rearth.0 0003131 gmr.1 0003137 htab.2 0003146 ttab.3 0003155 ptab.4 0003164 gtab.5 0003173 atmos_ 0003207 exp 0003213 pow_dd One can quickly see that 'Atmos' has been changed to 'atmos_' and the latter is what the C program must call to be successful. o) Obtaining a Fortran Compiler On Windows using Visual C/C++ with Fortran is best done with Digital Fortran and perhaps Absoft, Lahey or other third-party compilers. See Compaq (www.compaq.com) and search for fortran or see the Absoft site (www.absoft.com) for Windows, linux, and Sun compilers. Also see Lahey (www.lahey.com) for more choices in Windows Fortran compilers. For Sun (Solaris) and other commercial unix platforms, one can purchase the computer vendor's Fortran compiler, a third-party Fortran such as Absoft, or even use the Gnu Fortran port for that platform (if available). As long as the compiler can output the same object (.o) format as the platform's C compiler with the same calling conventions for argument passing and stack cleanup policies, the Fortran compiler will work with the gateway C-MEX S-function technique. On linux, one can compile Fortran source and mex using g77 and gcc (the defaults in 'mex -setup' for linux). Here is one example session that prepares a Fortran subroutine and its gateway C-MEX S-function for use: % g77 -c sfun_atmos_sub.for -o sfun_atmos_sub.o % mex -lf2c sfun_atmos.c sfun_atmos_sub.o This can also be done in one step using mex: >> mex -lf2c sfun_atmos.c sfun_atmos_sub.for Gnu Fortran (g77) can be obtained free for several platforms from many download sites, including http://www.redhat.com in the download area. A useful keyword on search engines is 'g77'. Constructing the Gateway ------------------------ o) Where to "wire up" the Pieces The mdlInitializeSizes() and mdlInitializeSampleTimes() methods are coded in C. It is unlikely that you will need to call Fortran routines from these S-function methods. In the simplest case, the Fortran is called from mdlOutputs(). o) Simple Case The Fortran code must at least be callable in a "one step at a time" fashion. If the code doesn't have any states, it can be called from mdlOutputs() and no mdlDerivatives() or mdlUpdate() methods are required. o) Code With States If the code has states, you must decide if the Fortran code can support a variable step solver or not. For fixed-step solver only support, the C gateway consists of a call to the Fortran code from mdlUpdate() and outputs are cached in an S-function DWork vector so that subsequent calls by Simulink into mdlOutputs() will work properly and the Fortran code won't be called until the next invocation of mdlUpdate(). In this case, the states in the code can be stored however you like, typically in the work vector or as discrete states in Simulink. If instead the code needs to have continuous time states with support for variable step solvers, the states must be registered and stored with Simulink as doubles. This is done in mdlInitializeSizes() (registering states), then the states are retrieved and sent to the Fortran code whenever you need to execute it. In addition, the main body of code has to be separable into a call form that can be used by mdlDerivatives() to get derivatives for the state integration and also by the mdlOutputs() and mdlUpdate() methods as appropriate. o) Setup Code If there is a lengthy setup calculation, it is best to make this part of the code separable from the "one step at a time" code and call it from mdlStart(). This can either be a separate SUBROUTINE called from mdlStart() that communicates with the rest of the code through COMMON blocks or argument I/O, or it can be part of the same piece of Fortran code that is isolated by an IF-THEN-ELSE construct. This construct can be triggered by one of the input arguments that tells the code if it is to either perform the setup calculations or the "one step" calculations. o) SUBROUTINE vs. PROGRAM In order to be able to call Fortran from Simulink directly without having to launch processes, etc., it is necessary to convert a Fortran PROGRAM into a SUBROUTINE. This consists of three steps - the first is trivial, the second and third can take a bit of examination. 1) Change the line PROGRAM to SUBROUTINE . Now you can call it from C using C function syntax. 2) Identify variables that need to be inputs and outputs and put them in the SUBROUTINE argument list or in a COMMON block. It is customary to strip out all hard-coded cases and output dumps. In the Simulink environment, you want to convert inputs and outputs into block I/O. 3) If you are converting a stand-alone simulation to work inside of Simulink, identify the "main loop" of time integration and remove both the loop and, if you want Simulink to integrate continuous states, remove any time integration code. Leave time integrations in the code if you intend to make a discrete time (sampled) S-function. o) Arguments to a SUBROUTINE Most Fortran compilers generate SUBROUTINE code that passes arguments "by reference". This means that the C code calling the Fortran code must use only pointers in the argument list. PROGRAM ... becomes: SUBROUTINE somename( U, X, Y ) A SUBROUTINE never has a return value. I/O is achieved by using some of the arguments for input, the rest for output. o) Arguments to a FUNCTION A FUNCTION has a scalar return value passed by value, so a calling C program should expect this. The argument list is passed by reference (i.e., pointers) as in the SUBROUTINE. If the result of a calculation is an array, then a subroutine should be used as a FUNCTION cannot return an array. o) Interfacing to COMMON blocks While there are several ways for Fortran COMMON blocks to be visible to C code, it is often recommended to use an input/output argument list to a SUBROUTINE or FUNCTION. If the Fortran code has already been written and uses COMMON blocks, it is a simple matter to write a small SUBROUTINE that has an input/output argument list and copies data into and out of the COMMON block. The procedure for copying in and out of the COMMON block begins with a write of the inputs to the COMMON block before calling the existing SUBROUTINE. The SUBROUTINE is called, then the output values are read out of the COMMON block and copied into the output variables just before returning. o) How-To: An Example C-MEX S-function Calling Fortran Code The subroutine 'Atmos' is in file sfun_atmos_sub.for. It is compiled on linux with the command: % g77 -c sfun_atmos_sub.for -o sfun_atmos_sub.o and on Windows with this command: >> mex -v COMPFLAGS#"$COMPFLAGS /iface:cref" -c sfun_atmos_sub.for \ -f ..\..\bin\win32\mexopts\df60opts.bat The gateway C-MEX S-function is sfun_atmos.c, which is built using the command: % mex sfun_atmos.c sfun_atmos_sub.o on unix and on Windows with (all on one line): >> mex -v LINKFLAGS#"$LINKFLAGS dfor.lib dfconsol.lib dfport.lib /LIBPATH:$DF_ROOT\DF98\LIB" sfun_atmos.c sfun_atmos_sub.obj On some systems where the C and Fortran compiler were installed separately (or aren't aware of each other), you may need to reference the library libf2c.a (unix). To do this, the mex command looks like: % mex -lf2c sfun_atmos.c sfun_atmos_sub.o Unix only: if the libf2c.a isn't on the library path, you need to add it the path to the mex process explicitly with the -L command, for instance: % mex -L/usr/lib/ -lf2c sfun_atmos.c sfun_atmos_sub.o This sample is prebuilt and is on the MATLAB search path already, so you can see it working by opening the sample model sfcndemo_atmos.mdl. Just type: >> sfcndemo_atmos at the command prompt, or to get all the S-function demos for Simulink, type 'sfcndemos' at the MATLAB prompt. You can then examine the source code for this S-Function demo and recompile it to prove the steps on your machine. II) Examining Legacy Code for Pitfalls ====================================== o) Find the States If a variable step solver is being used, it is critical that all continuous states are identified in the code and put into Simulink's state vector for integration instead of being integrated by the Fortran code. Likewise, all derivative calculations must be made available separately to be called from the mdlDerivatives() method in the S-function. Without these steps, any Fortran code with continuous states will not be compatible with variable step solvers if the S-function is registered as a continuous block with continuous states. Telltale signs of implicit advancement are incremented variables such as M=M+1 or X=X+0.05. If the code is laced with these constructs and you determine that it is impractical to recode the source to not "ratchet forward", you may need to try another approach using fixed step solvers. If it is impractical to find all the implicit states and to separate out the derivative calculations for Simulink, another approach can be used, but you are limited to using fixed step solvers. The technique here is to call the Fortran code from the mdlUpdate() method so the Fortran code is only executed once per Simulink major integration step. Any block outputs must be cached in a work vector so that mdlOutputs() can be called as often as needed and output the values from the work vector instead of calling the Fortran routine again (which would cause it to inadvertently advance time). o) Sample Times Be sure if the code has an implicit step size in its algorithm, coefficients, etc., that you register the proper discrete sample time in the mdlInitializeSampleTimes() S-function method and only change the block's output values from the mdlUpdate() method. o) Multiple Instances If you plan on having multiple copies of this S-function used in one Simulink model, it is necessary to allocate storage for each copy of the S-function in the model. The recommended approach is to use DWork vectors, see /simulink/include/simstruc.h and /simulink/src/sfuntmpl.doc for details on allocating datatyped work vectors. o) Use Flints If Needed Use flints (floating-point ints) to keep track of time. Flints (for IEEE-754 floating point numerics) have the useful property of not accumulating roundoff error when adding and subtracting flints. Using flint variables in DOUBLE PRECISION storage (with integer values!) avoids roundoff error accumulation that would accumulate when floating point numbers are added together thousands of times: DOUBLE PRECISION F : : F = F + 1.0 TIME = 0.003 * F This technique avoids a common pitfall in simulations. o) Considerations for Real-Time Since very few Fortran applications are used in a real-time environment, it is more common to come across simulation code that is incompatible with a real-time environment. Common failures include unbounded (or large) iterations and sporadic but time-intensive side calculations. These must be dealt with directly if there is to be any hope of running in real-time. Conversely, it is still perfectly good practice to have iterative or sporadic calculations if the generated code is not being used for a real-time application. [EOF]