From UML to embedded system: Is it possible?
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
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.
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 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 are executable, and consist of three primary diagrams as shown in Figure 2 below. (For a complete description, see ; for an extended example, see ). 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.
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.
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.
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.
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.
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
 Stephen, J. Mellor, Marc J. Balcer. Executable UML: A Foundation for Model-Driven Architecture
 Leon Starr, Executable UML, The Elevator Case Study, Model Integration, LLC.
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