C99 code generation
The CIF code generator can generate C99 code from a CIF specification. It is assumed the reader of this page is familiar with the general information of the CIF code generator tool. This page describes specific information applicable only to C99 code generation.
The aim of the C99 code generation tool is to provide an easy conversion from a CIF program to control of a real time system. It uses a simple integrator intended for updating running clocks (derivative should not change during a time step for best results). Also, it does not compute length of the next time step, but instead assumes being called regularly to update internal state and perform discrete steps if possible.
Supported specifications
The CIF code generator supports a subset of CIF specifications. Generation of C99 code does not impose additional restrictions. See the Supported specifications section of the CIF code generator page for more information.
Differences in output
When converting a real number to text, for example when printing a real value, the generated C99 code does not preserve trailing zeroes of real number fractions while using %g
. For example fmt("%.3g", 1.0)
will output 1
rather than 1.000
.
This may cause real number values to be printed with less digits precision than indicated in the CIF program.
Strings have an upper limit in length that is set during compilation. Longer strings are silently truncated to this length.
Options
The C99 code generator only uses the common options that apply to all target languages/platforms of the CIF code generator, it has no options of its own.
The code prefix that can be configured using a common option is used to prefix external names, making them unique for the generated code.
Generated files
C99 code generation leads to creation of seven files:
Generated file | Purpose |
---|---|
|
Runtime support code and library headers |
|
Runtime support code and library implementation |
|
Interface definition of the translated CIF program |
|
Implementation of the translated CIF program |
|
Compile script for Linux |
|
Example of external functions, allow compiling the generated code |
|
Short description of use, the interface, and compile options |
where <prefix>
is replaced by the value of the Code prefix option during code generation.
The aim is to provide a complete and compilable package, to minimize the effort in converting a CIF specification. Most files are static text (modulo the <prefix>
replacement). The two exceptions are both engine files that contain the C99 version of the CIF specification. The interface of these files is however fixed (modulo the <prefix>
replacement), and generated data types and operations on them have predictable names, reducing the chance of failed compilation when re-generating the files.
When modifying a generated file (such as the compile script or the example test code), keep in mind that running the code generator again will overwrite all files. Rename a file before modifying it to avoid loss of work.
Compilation of the generated code
The generated code is C99 code that can be compiled using a C99 C compiler, like gcc
:
gcc -Wall -std=c99 -DPRINT_OUTPUT=1 -DCHECK_RANGES=1 \
<prefix>_engine.c <prefix>_library.c <prefix>_test_code.c -lm
The single \
is not part of the command, it means the next line should be appended at the end.
The <prefix>
should be replaced by the value of the Code prefix option during code generation. The PRINT_OUTPUT
and CHECK_RANGES
macros enable support for the CIF print
statement and checking of integer range overlap, as defined by CIF. In general however, you may want to replace the test code file by your own code.
C99 compile-time options
Besides the PRINT_OUTPUT
and CHECK_RANGES
macros, the generated code has a few other compile-time options to customize its behavior. Below is the complete list.
Macro name | Effect |
---|---|
|
CIF has variable length strings, in the generated code they are converted to strings with a fixed upper limit. Default is 128 (including the terminator character). Set this macro for a different limit. Note that both the library and the engine use this macro. It is crucial that both files use the same value for this macro. |
|
When copying integers from one range to another, where the latter is only partially covered by the former, CIF checks whether the copied values do not violate the destination range. Since violating this property generally indicates a programming error, it is recommended to enable this macro. |
|
If set, the generated code calls |
|
If set, the CIF |
|
The CIF semantics state that execution of edges is instantaneous and time-less. The generated code loops to perform edges, until no edge can be executed any more. Unfortunately, such a loop is not time-less. To avoid requiring too much time, there is an upper limit of 1000 iterations. Set this macro to change the limit. |
|
The CIF language checks range conversions (if |
Executing the code
The engine files act as a library that implements the CIF program. To run the program, the code must be called regularly. The code has two entry points, one for initializing and performing the first steps, and one for handling a time delay.
The first entry point is:
<prefix>_EngineFirstStep(void)
This entry point initializes all the data, queries the values of the input variables if present, and performs execution of edges until blocked on the first time step or until hitting the MAX_NUM_EVENTS
limit. Since this resets the entire CIF program to its initial state, you should only call this when the system being controlled is also (re-)initialized.
The second entry point is more regularly called, after a period of time has passed:
<prefix>_EngineTimeStep(double delta)
By calling this entry point, you indicate that delta
(> 0) time units have passed. The code reads new values of the input variables, updates the continuous variables, and performs edges until all edges are blocked, or hitting the MAX_NUM_EVENTS
limit.
When the call of either entry point returns, you can query the value of the variables to use as input for other parts of the system.
Environment interface
Calling one of the above entry points causes the CIF program to be executed, up to the next time step (assuming the MAX_NUM_EVENTS
limit is high enough). During the execution, the code performs a number of callbacks to get information from the environment, to provide information about its actions, or to deliver output. In addition, after each call, variables may be inspected by the environment to get information about the decisions of the CIF program.
The externally provided callback functions that are being used are:
<prefix>_AssignInputVariables(void)
The CIF program requests new values for all its input variables. In your implementation of this function you should write values directly in the input variables. Input variables are assumed to be independent of other parts of the CIF program, in general you should not need other CIF program values to implement this function.
If EVENT_OUTPUT
is set during compilation, executed events are reported with:
<prefix>_InfoEvent(<prefix>_Event_ event, BoolType pre)
If the EVENT_OUTPUT
macro is set during compilation, the generated code reports with this call that event is about to (pre
is TRUE
), or has been (pre
is FALSE
) executed. In the pre-event call, the event can be executed, and all variables have their value before performing the updates of the involved edges. In the post-event call, all variables have their values updated as indicated in the updates of the edges.
Primary use of this call is to forward the decision to other parts of the controlled system.
Finally, if the PRINT_OUTPUT
macro is set during compilation, the generated code reports lines of output to be printed with the following function call:
<prefix>_PrintOutput(const char *line, const char *fname)
It denotes that text line
should be printed at file fname
. The primary uses of this call are to enable debugging the system, or to log relevant events. Note that fname
here can mean anything. It can mean a real file, but also a pseudo-device like :stdout
which is commonly used to print output to the C stdout
stream.
Data access
Each CIF constant is available as a variable that does not change after initialization. Each algebraic variable can be queried by calling a function with the same name as the variable. The value of continuous variables is available by accessing the equivalent variable in the generated code. The derivative of each continuous variable is available as function named by the variable, and the suffix deriv
. The model time is available as model_time
. There is no derivative function of time. Internal functions are available as well if required.
The engine header file lists the translated names. It should be easy to find the relevant entry, as the generated code also documents the original name.
Data types
The following table defines how CIF data types are converted in the C99 generated code.
CIF data type | C99 data type |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
A few notes:
The
...
in the C99...Type
name for arrays (fixed length lists) and tuples is the systematic type name of the array or tuple type.Due to preprocessing all enumerations are merged into one. The generated C99 code always has exactly one translated
enum
.The elementary data types (
BoolType
,IntType
,RealType
, andStringType
) are defined in the library header file.The
StringType
structure wraps achar data[MAX_STRING_SIZE]
array. Thestruct
allows copying arrays by value using an assignment statement.The array type (list with a fixed length) structure wraps a
<element-type> data[<size>]
array for the same reason. Copying an array by assignment is allowed.The tuple type is a structure with fields
_field0
,_field1
, and so on, where the type of each field in the structure matches with the type of the CIF tuple field.Boolean constants
true
andfalse
are namedTRUE
andFALSE
respectively.The
-0.0
value of doubles gets replaced by0.0
to void subtle equality problems.Strings get silently truncated to
MAX_STRING_SIZE - 1
characters. They are always terminated with a NUL character.Enumeration values get
_<prefix>_
added in front to make them unique.
Systematic type names
For tuple and list types, the type is converted to a name in a systematic way, making each type unique and predictable.
The conversion composes a name of a type from the type of its elements, and concatenates the result. The following conversion table applies:
CIF type | Systematic name |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
For example, type list[5] int
is converted to A5I
, and type tuple(list[5] int x; tuple(real a, b) z)
is converted to T2A5IT2RR
(concatenation of T2
with the field type names A5I
and T2RR
).
Runtime errors
The CIF language defines strict sets of allowed values for all data types, and performs checking of these values at run time. The generated C99 code follows that idea, and checks whether operations on data are safe. Most of these checks are performed with an assert
. Executed C code does not provide useful stack traces by default, and original line numbers of e.g. a CIF program have no meaning due to the linearization that is performed before code is generated. In addition, the generated code is quite readable and it is not too hard to understand what the code is doing at some C source line.