Code size considerations

Contrary to popular belief, automatic code generation from design models does not automatically lead to a huge code size overhead. Code generators are constantly getting better and compiler optimization technologies have evolved tremendously over the last decade.

Here we take a brief look at the fixed code size cost of using IAR visualSTATE®.

Different code generation options

visualSTATE can generate two formats of code: One of these formats is a table-based format that is extremely compact, but requires a small execution engine. The next section discusses the code size costs of using that code format.

visualSTATE can also generate a human-readable code format that uses traditional switch and if statements to implement the state machine logic. This format is useful if you need an insight into how the code generator transforms the model into code, for example if you are working on a high-integrity application. However, the human-readable code is not as compact. It is basically a straight translation from the model to C code without any attempts to create 'smart' code - the intention is rather that it shall be easy to map the generated code back to the model.

The code size costs associated with human-readable code are more dependent on the model than in the case with table-based code. This is because things like guard expressions, assignments and calls to action functions are coded and executed 'inline' in the generated code, instead of indirectly by way of a table lookup as with the table-based code. For that reason it is difficult to give any precise numbers for what code sizes to expect, but the following rules of thumb hold true:

The code size is mainly dependent on the number of transitions. A transition that is only triggered by an event, but with no guard conditions, action expressions or functions, is cheap. Adding a guard condition will add code inline for evaluating that condition. Each added call to an action function on the transition will add the code needed to call that function with the correct parameters. Transitions from states inside a state hierarchy will add some extra code for each level of hierarchy.If several transitions are triggered by one and the same event in one and the same region, code for a conflict test will be generated even if the transitions are guarded by proper guard conditions.

Typical cost of the visualSTATE API

If you choose the table-based approach to generating code from visualSTATE state machine diagrams, the tables are generated in a way that is extremely compact, thus giving a very small representation of the actual state machine logic.

However, using a table-driven approach means that you need some sort of execution engine to exercise the state machine logic at runtime. This is common to all table-driven solutions for a wide range of problems associated with finite automata and is not limited to state machines in general or visualSTATE in particular. (Compare with parsers, compiler generators etc.)

The execution engine represents a fixed overhead in terms of code size and is often believed to be prohibitively large, rendering automatic code generation useless for resource-constrained targets. Contrary to such beliefs, we show that the overhead of the engine is extremely small, if a modern compiler is used.

Since the code generated from the model is so tight, the advantage over hand-coding the model is apparent even for small state machines.

The code

A fully implemented visualSTATE application consists of the following parts:

  • The actual application that uses the state machine(s); this includes all startup code and generic runtime library code as used by the particular target hardware and compiler
  • The API file for the execution engine. (SEMLibB.c)
  • The generated code - typically split into a number of files:
    - The state machine tables.
    - Variables and expressions defined in the model.
    - Declarations of action functions.
    - Definitions of action expression functions.
  • Action functions implemented by the developer and called by the state machine.

All these parts contribute to the footprint of the complete application. Of these, visualSTATE determines the size of the API and the generated code. The other parts are fully controlled by the developer and are more or less independent of the implementation model for the state machine.

A typical visualSTATE application uses a limited set of the functions that are present in the API, to insert stimuli into the state machine and to process input. To measure the minimum size of the API code, we have created a minimal state machine and compiled the resulting code with different compilers from IAR Systems. The model looks like this:

minimal

The state machine model consists of an initial state, a simple state and a default transition that also assigns a value to an externally defined variable. The API functions are the ones typically used by a visualSTATE application.

(Most other functions available in the API are for advanced use, to enable very fine-grained control of the state machine or for debugging purposes.)

The size

We have compiled the minimal state machine application with five different compilers from IAR Systems. All these compilers are built on the latest technology platform and are available for purchase today. For the sake of comparison we have chosen one 32-bit platform, two 16-bit platforms (A and B) and two 8-bit platforms (A and B).

Since the purpose of this exercise is to give approximate figures for various target platforms, and not to produce an architecture benchmark, we do not disclose the actual targets we have used. All compilations are performed with the optimization level "-z9", which is the highest level of size optimization. No target-specific tuning has been applied. All sizes are in bytes.

Platform Mini.c SEMLibB.c MiniMain.c MiniData.c Linked appl.
32-bit 8 256 54 16 680
16-bit A 6 262 44 8 386
16-bit B 6 245 46 9 496
8-bit A 6 294 64 10 555
8-bit B 6 362 75 17 840

The table shows that the compiled size of the API file SEMLibB.c accounts for approximately 0.25 Kbytes of code. This is the compiled size; the actual size occupied by the code in SEMLibB.c in the linked image is probably even smaller, since the linker removes all sections that are not referenced.

The columns in the table have the following content:

  • Mini.c is the table representation of the model. The size is 6 bytes regardless of compiler, but since this model is so small, the 32-bit target suffers from a 2-byte alignment requirement.
  • SEMLibB.c is, as mentioned before, the file containing the definitions of all API functions.
  • MiniMain.c is the main (user-supplied) function that calls the API functions that execute the engine.
  • MiniData.c is the definition of internal variables and functions that deal with expressions in the model operating on internal and external variables. In this case, it consists of one function to execute the assignment in the model and a function pointer table.
  • Linked application is the total size of the executable application for each platform. This size includes all the necessary startup code and the generic runtime library code needed by the compiler.

The big picture

To find the maximum size of the API, a different strategy is needed. The visualSTATE code generator configures the API to exclude internal functionality that is not needed for a particular model. This is dependent on the use of specific model constructs like guard expressions, state conditions, action functions, action expressions, signals etc.

To measure the API for a realistic application, we created a state machine model that uses all these model constructs. To activate the associated runtime code, it is sufficient to use the construct once in the model. The resulting code sizes for SEMLibB.c are summarized in the table below. Note that the set of called API functions is the same in both application examples.

API file 32-bit 16-bit A 16-bit B 8-bit A 8-bit B
SEMLibB.c 592 598 621 724 911

The table shows that three of the target architectures cluster around 600 bytes and that the 8-bit B architectures are 100-300 bytes behind. This is analogous to the results for the minimal API.

Conclusion

For a modern compiler and a modern code generator, the overhead associated with automatic code generation is small. Of the 5 compilers we tried, 3 had API sizes between 256 and 621 bytes, depending on the set of modeling constructs that was used. This is really a small price to pay for the benefits of high-level design, verification and test.The table shows that three of the target architectures cluster around 600 bytes and that the 8-bit B architectures are 100-300 bytes behind. This is analogous to the results for the minimal API.