By Stephen J. Mellor, Mentor Graphics Corp.
Software design tools have been around for over 20 years now. Although
they have improved greatly over that time, there seems to be a problem
with generated code: it is still too inefficient and unreadable. Until
now.
At one level, building efficient executable UML models is trivial:
write some code and put some boxes around it. The code will run as fast
as you build it, and the boxes (for a class, eg) can easily be turned
into some appropriate code. But the value added from this approach is
low, and the mental overhead high.
An option is to generate more of the code from boxes, under the
supposition that the boxes are more abstract and so more productive
than writing code directly. This is intuitively correct, but in
practice it tends to break down because bit-fiddling code cannot
cheaply be represented in UML. It is, in fact, more productive to write
the code directly—but then we lose the productivity advantages of
abstraction.

An alternative is to make UML both executable and translatable.
This approach lets the developer verify that the design is solving the
problems he intends to solve by executing an executable UML model and
debugging the model's behavior before any code is generated.
Moreover, translation is carried out using a set of rules that
builds a system in a strict and repeatable manner. The developer has
full control over these rules so that the most appropriate code for a
particular architecture can be generated.
Rather than bit-fiddling the generated code, a more structured and
repeatable process is now used. If there is a problem with the behavior
of the application, then the model is changed accordingly; if there is
a problem with the performance of the code, then the rules are
adjusted.
This separation of the application from the architecture (the
translation rules) results in a more maintainable and efficient system.
The Problem
Embedded software developers have a reputation in the software world at
large for being a bit slow. Not dim, you understand—everyone knows we
tackle sophisticated problems—but slow to pick up on new software tools
and languages.
This accusation has some truth. Today, some 77.2% of projects use
some C, another 46.2% use assembly language, and 44.2% use C++.
(Venture Development Corporation, The
Embedded Software Strategic
Market Intelligence Program, 2004.}
The numbers add up to more than 100% because a project may use more
than one language. Hardly anyone in IT would think of writing in a
language as close to the machine as C, let alone assembly code, these
days.
We embedded developers would quickly retort that our problems have
to be very efficient, very small, and in many cases, very fast too.
Moreover, we need to have complete control over the generated code, and
that is taken away from us by programming language compilers.
We recognize, of course, the advantages of writing code at a higher
level of abstraction. After all, studies for nigh thirty years have
shown that we can write the same number of lines of code per day,
irrespective of the language. Since we get more functionality for a
line of C++ than for a line of assembly, it stands to reason that we
can increase productivity if we use a more abstract language.
Meanwhile, the IT folk are moving towards higher-level-still
languages such as the Unified Modeling Language(UML), mostly as a
sketching language, or as a blueprint for further software development.
The UML is also an executable language, with state machines for
modeling concurrent behavior and exploring synchronization issues,
which would make the language appropriate for the embedded market, but
it is so far away from the machine it seems at first blush to be even
more unlikely than using C++ in the embedded space.
But what if you could generate small, efficient code? And what if
you could have complete control over it? That would give us the
productivity advantages of a higher-level language while still meeting
our performance constraints. Then moving from UML to embedded system
would indeed be possible.
We begin our discussion, then, by describing a model-based approach
to systems development, based on executable models.
 |
| Figure
1: The overall development process |
The IDEA
The first step, shown on the left in
Figure 1 above as a light bulb,
represents the concept behind an embedded project, a marriage of the
desirable, as expressed by the participation of marketing, and the
available, as expressed by the participation of hardware and software
experts.
Defining the concept behind a product is often a compromise and
trade-off between possible features desired by marketing and the
capabilities that can be delivered by a given hardware-software
architecture.
At this point, the development process bifurcates into application
and architecture sections. These sections may be carried out in
parallel, but we describe the application first.
Application Models
Application models are executable, and consist of three primary
diagrams as shown in Figure 2 below.
(For a complete description, see
[1]; for an extended example, see [2]). The first part is the
declaration of the conceptual entities in a particular subject matter;
these are represented using a Unified Modeling Language (UML) class
diagram.
It is critical to recognize that our use of a "class diagram" says
nothing about the software structure. In a translatable model, a "class
diagram" is only a convenient representation for a conceptual grouping
of information and behavior.
Behavior over time is represented using a subset of a UML state
chart diagram (the second part of Figure
2 below). There is a
state-chart diagram for each object of a class. (More accurately, there
may be one state chart for each instance and there may be one state
chart for the behavior of the collection of instances as a whole.
Moreover, instances may participate in subtyping hierarchies.)All the
dynamic behavior in an application model takes place as a part of a
state machine.
An executable model is meaningless without rules that define
execution. In executable UML, each state machine executes concurrently
with all other state machines. They communicate by sending signals that
define precedence relationships between sequences of actions. Just as
with class diagrams, the state chart diagram does not imply an
implementation; signals may be implemented in any manner that preserves
the desired precedence of actions.
 |
| Figure
2: An application model example |
Actions are the last of the primary artifacts of executable UML.
Actions are also implementation independent, in that they do not assume
a software structure. Consider, for example, a function that sums the
total time used in the last n phone calls.
Looping through a linked list of recent calls is one way to
implement this, but that definition relies of a specific software
structure: a linked list. UML actions are cast so we first get the
times for each of the calls and then sum them up. This subtle
difference in expression allows us to access stored the data in any way
we choose, so as to get the best performance.
Other diagrams, such as communication diagrams, can be derived from
the primary models as required for presentation and comprehension
purposes.
Architecture
Work on defining the architecture can take place at any time in respect
to work on defining the application. This unexpected property comes
about because the architecture is not an elaboration of the application
but rather a set of rules that define how to transform an application
written in executable UML into an implementation. A consistent set of
rules that target an architecture is called a model compiler.
When we build an executable UML model, the meaning of the model is
stored in a database. The diagram shown in Figure 2 above creates
instances in the database as shown in Figure
3, below.
The structure of the database is given by the elements of the
executable UML language. Hence, we have a table Class to store
information about classes, and a table State to store information about
states. The structure of this database is called a metamodel.
 |
| Figure
3: Metamodel |
A model compiler applies the architecture rules to the application
as stored in the database. The rules traverse this database in
accordance with the desired target output; they also manipulate strings
to produce text acceptable to a compiler in the language of choice. The
result is text on some output streams that may be compiled. The model
compiler is oblivious to the language; it merely manipulates text.
It is here that the rubber meets the code: the performance needs to
be adequate. Should you find the code is too slow or too big, you can
quickly identify the cause and write a rule to fix it.
For example, we may find that turning the magnetron on and off every
so many seconds is not fast enough. In that case, you should fix the
rules to generate a periodically executing task, not diddle the
generated code: Write the new rule, add it to the rule set and test it.
That way your expertise can be leveraged across the organization.
Off-the-shelf rule sets are available from vendors or internal
organizations that have modified rule sets to meet the needs of a
specific environment or product. The reason for such modification is to
meet performance constraints on speed or memory and perhaps to
interface to a library or some legacy code.
Compilation
Once the model compiler has produced text, it can be compiled by a
programming language compiler. This represents the transition from the
abstract world of model-driven architectures to the familiar embedded
software development environment. An open model compiler makes the
process controllable and seamless.
The generated code may be in a selection of familiar languages:
1. Standard C. This is still
the most widely used language for embedded software development.
2. Standard C++. Although not
embraced by all developers, C++ is used increasingly often for embedded
code.
3. C with special features.
Sometimes standard C does not map directly onto a processor's
architecture and a special variant is useful. Or you need to meet
certain standards such as MISRA.
4. Assembler. Although
high-level languages are the now norm, assembler has its place,
particular with smaller devices.
This covers all the widely-used embedded development languages. The
next step is to simply put the generated code through the appropriate
tool-chain"compile, assemble, link with relevant libraries (e.g. RTOS)
for the chosen target.
Model Debugging
The application model has been verified; the architecture rules have
been verified; the model compiler is part of well-tested tool set. Is
there anything left to do? Intellectually, no. But we may still get it
wrong. At that point, it is helpful to be able to examine the generated
code in action and debug the resulting code using a debugger.
Just like trying to debug assembly code generated from a C compiler,
this can be difficult without a symbolic debugger. A model-level
debugger allows us to set breakpoints in the model, halt the running
code, and hyperlink back to the model element that caused the problem.
Prototyping
Frequently, hardware and the embedded user interface are being
assembled as the software is being written. To provide a common
platform for all developers, a host-compiled simulation environment can
run embedded applications on the development host. This provides the
ability to simulate the complete system, including hardware and
peripherals, I/O and the application's GUI, which allows you to start
development before receiving hardware.
With several powerful additions including visualization, monitoring
and tracing tools, this also provides expanded network simulation and
the ability to simulate not just the application's GUI, but also the
complete Man-Machine-Interface (MMI) of the product under development.
Debugging
Beyond the standard debug support, you need a number of advanced
features that enable in-depth analysis and debugging of complex
embedded systems. Features for establishing the state of the machine,
identifying areas of code for performance gains and interacting with
the program executes are all required.
Of course, the next step is to move on to deployment
From UML to Embedded System
The software development flow of Figure
1, above, says it all. From idea to deployment.
The key concept is that of open translation rules. These allow you
to tune the code to produce anything you need.
That said, we have found that better than two thirds of projects
have no need to tweak the rules at all from the out-of-the-box model
compiler.
Yes. Not only is it possible to go from UML to embedded system, it
is easy.
This
article is excerpted from a paper of the same name presented at the
Embedded Systems Conference Silicon Valley 2006. Used with permission
of the Embedded Systems Conference. For more information, please visit www.embedded.com/esc/sv.
Stephen
J. Mellor is an internationally recognized pioneer in creating
effective, engineering approaches to software development. In 1985, he
published the widely read Ward-Mellor trilogy Structured Development
for Real-Time Systems, and in 1988, the first books defining
object-oriented analysis. He is now chief scientist of the Embedded
Systems Division at Mentor Graphics. He also chairs the IEEE
Software Industrial Advisory Board. www.acceleratedtechnology.com
References
[1] Stephen, J. Mellor, Marc J. Balcer. Executable UML: A Foundation
for Model-Driven Architecture
[2] Leon Starr, Executable UML, The Elevator Case Study, Model
Integration, LLC.
Other UML
references on Embedded.com:
Executable
and Translatable UML
Need
for modeling tools rises with design complexity
In
Focus: Unified Modeling Language
Introduction
to UML statecharts
UML
evolves total system perspective
Introduction
to UML Class Diagrams
State
machine shortcuts