By Stephen J. Mellor, John R. Wolfe, Campbell McCausland, Mentor Graphics
Once you have captured the semantics of your SoC application model
completely in a neutral form (
see
Part 2), you are now ready to compile it into
software and silicon. Only the semantics of the modeling language
matter for translation purposes.
If a class is represented graphically as a box, or even as text,
this is of no consequence. The UML is just an accessible graphical
front-end for those simple elements.
When you build a 'class' such as CookingStep in a
microwave oven, that represents a set of possible cooking steps you
might execute, each with a cooking time and power level.
Similarly, when you describe the lifecycle of a cooking step using a
state machine, it follows a sequence of states as synchronized by other
state machines, external signals, and timers. And in each state, we
execute some functions. All of this structure and behavior, for the
entire model, is captured as data in the metamodel.
Rules and Rule Languages
Now that we have a verified platform-independent model of our
application captured as a metamodel, we can translate it into hardware
and software description languages, such as VHDL
or C, using a model compiler. A model compiler is implemented as a set
of rules.
One rule might take a 'class' represented as a set CookingStep(
cookingTime, powerLevel ), and produce a class declaration.
Crucially, the rule could just as easily produce a struct for a C
program, or a VHDL entity. Similarly, we may define rules that turn
states into arrays, lists, switch statements, or follow the State
pattern from the Design
Patterns community.
(This is why we put 'class' in
quotation marks. A 'class' in an executable model represents the set of
data that can be transformed into anything that captures that data; in
a blueprint-type model, a class is an instruction to construct a class
in the software.)
These rules let us separate the application from the architecture.
The xtUML model
captures the problem domain graphically, and represents it in the
metamodel. The rules read the application as stored in the metamodel,
and turn that into code.
There are many possible rule languages, and the Object
Management Group is now working to define a standard [1]. All that's required is the
ability to traverse a model and emit text. As an example, the rule below generates code for
private data members of a class by selecting all related attributes and
iterating over them.
.function PrivateDataMember(
class Class )
.select many PDMs from instances of
Attribute related to Class
.for each PDM in PDMs
${PDM.Type} ${PDM.Name};
.endfor
All lines beginning with a period ('.')
are commands to the rule language processor, which traverses the
metamodel whose instances represent the executable model and performs
text substitutions.
${PDM.Type}
recovers the type of the attribute, and substitutes it on the output
stream. Similarly, the fragment ${PDM.Name}
substitutes the name of the attribute. The space that separates them
and the lone ';'
is just text, copied without change onto the output stream, as shown
below:
You may wonder what the produced code is for. It is an enumeration
that contains each of the states of a state machine for a class. (There is a similar rule that produces an
enumeration of signals.)
The enumerations are used to declare a two-dimensional array
containing the pointers to the activity to be executed. You may not
like this code, or you may have a better way to do it.
Cool: all you have to do is modify
the rule and regenerate. Every single place where this code appears
will then be changed. Propagating changes this way enables rapid
performance optimization.
While the generated code is less than half the size of the rule, the
rule can generate any number of these enumerations, all in the same
way, all equally right—or wrong. We have also used the rule language to
generate VHDL, as follows:
The rule language can be used in conjunction with the generator to
generate code in any language: C, C++, Java, Ada, and, if you know the
syntax, Klingon.
Model Compilers and System
Construction
Figure 1 below shows the
overall structure of a model compiler. The application is captured in
the metamodel, and the model compiler comprises some library code and a
set of rules. The rules are interpreted against the metamodel to
produce text. The rules may also "wrap" external libraries and legacy
code. All of this is compiled—using the appropriate compilers—to
produce the system-on-a-chip.
 |
| Figure
1: Model Compiler and System Construction |
The overall architecture of the generated system is determined by
the model compiler. Each model compiler is thus coupled to the target,
but the model compiler is independent of the application models that it
translates. This separation of application from design allows us to
reuse both application model and model compiler as needed.
We can translate the same application model for many different
targets by using different model compilers, but the models of the
application do not change. Similarly we can use the same model compiler
to translate any number of application models for a given target
without changing the model compiler.
For example, a model compiler for an object-oriented architecture
will likely contain rules to translate each UML class to a C++ class or
a C struct with each associated UML attribute being translated to a
data member of the class or a member of the struct.
The state machines in the application model would be translated into
two dimensional arrays where the row index represents the current state
of an object, and the received event provides the column index. Each
cell contains the value of the next state for the transition identified
by the current state (row) and the
received event (column).
This next-state value is then used as an index into an array of
function pointers, each corresponding to a state. This particular
approach leads to a constant-time dispatching mechanism for the actions
of each state machine.
Of course we can use alternative implementations depending on our
needs.
For example, in some cases, we might choose to use a switch
statement or nested if-else statements to implement the state machine,
each of which would have slightly different speed and space
characteristics (if-else is linear in
the product of states and events).
For hardware implementations we might choose to translate each UML
class into a collection of registers, one for each attribute in the
class. Each state machine of the application model could be mapped to a
VHDL case statement. There are, of course, many other possible
implementations. UML classes can be mapped into blocks of RAM, and
state machines can be translated into a data-driven and
gate-conservative dispatcher.
But what about the interfaces between the hardware and software
components of the system? These interfaces are just a part of the
architecture encapsulated within the model compiler.
Let's look at a simple example.
Suppose we have two UML classes, CookingStep and Oven where CookingStep is
translated to software and Oven is
translated to hardware. In this case the hardware architecture for Oven is a
collection of memory-mapped registers.
The generated interface for an action in CookingStep that
accesses an attribute of Oven is then a
simple memory access of the address allocated for the register in
question.
Consider a slightly different hardware architecture in which the UML
class Oven
is mapped to only two addresses, one representing a control register
and one for a data register. Accesses to attributes of the class would
then be accomplished by writing the attribute identifier and the type
of access (read or write) to the control register and then manipulating
the data register accordingly (reading it after writing to the control
register or writing it before writing to the control register).
Since the model compiler is generating the implementation for both
the hardware and the software components of the system, it has
sufficient information to generate the interface between the two, and
it will do so without succumbing to the ambiguities of a natural
language specification. And it will do it correctly, every time.
It's possible to build and deploy model compilers that provide
completely open translation systems. Exposing the translation rules in
this way allows you to make changes to the rules to meet your
particular needs so that the model compiler generates code exactly the
way you want. This puts the translation of the models completely under
your control.
The importance of marks in your UML design
To tell the model compiler whether to generate hardware or software for
a given model element we need additional inputs to decide which mapping
to perform. These additional inputs are provided as marks, which are
lightweight, non-intrusive extensions
to models that capture information required for model
transformation without polluting those models.
Each kind of mark has a name and a type. In addition, a kind of mark
can have a default value. In programmer-esque language, we might write:
Mark HardSoft [ isHardware,
isSoftware ] = isSoftware
which declares a kind of mark named HardSoft that
can have one of two values, where the default is isSoftware.
Most marks apply to instances of metaclasses, so that, for example,
if we have a metamodel with class Class, and two instances of that
class, Oven and
CookingStep,
the mark HardSoft
can have a separate value for each of those instances, isHardware for
the Oven,
say, and isSoftware
for the CookingStep.
Were the kind of mark to apply instead to generated signals, then
the marks would be associated with instances of the class GenerateSignalAction
in the metamodel. A given application model element can have several
marks associated with it. Each of these marks is an extended attribute
of the appropriate metamodel class.
Metacasses as plastic sheets
We do not intend to leave the impression that the metamodel should be
extended directly. Marks are not part of either the application model
or the metamodel, though they can refer to them both. Rather, we view
the extended attributes of the metaclasses as being housed on a plastic
sheet that can be peeled off at will for a different model compiler.
This separation supports both model portability and longevity.
The separation also provides the ability to evaluate a number of
different architectural possibilities without requiring modification of
the application model. Just change the values of the marks. Not to put
too fine a point on it, this solves the problem of changing the
hardware-software partition after we have verified the behavior of the
application model by executing test cases against it.
The plastic sheet analogy suggests that some marks might be related
and could all be placed on the same sheet. A single sheet could contain
multiple related marks, such as those indicating which types of
hardware implementations should be applied to which elements of the
application model. Removal of the plastic sheet, then, implies the
removal of the entire hardware architecture represented by that sheet
from the system.
Marks may also be quantities used to optimize the target
implementation. Consider a model that must be transformed into an
implementation that occupies as small an amount of memory as possible.
We can save memory if we observe that a particular class has only one
instance (e.g.,
Oven). Such a class can be marked as extentLess, and
no container need be generated for instances of that class, making
references to the instances more efficient.
Similarly, we can make trade-offs within the hardware architecture.
Suppose the original target had plenty of address space available and
consequently mapped each class attribute implemented in hardware to a
specific address, making the software access to the attributes simple
and efficient.
In a subsequent release we move to a lower-cost processor with a
constrained address space. Through marks we then instruct the model
compiler to use a single pair of addresses for each class to provide
access to all the attributes in the class. Since the model compiler
knows how to generate the software required for this more interesting
approach for accessing hardware-resident attributes, the application
models do not change even though the nature of the hardware/software
interface has been drastically altered.
There have to be ways for the modeler to assign values to marks.
Some implementations provide for graphical drag-and-drop allocation of
model elements into folders that correspond with marks; others define
an editor for the defined mark sets that can display all marks defined
by the model for a selected model element, with pull-down menus for
each of the marks. Another option is to define text files, and then use
the large set of available editing, and scripting tools.
In the final article of this series, we assess progress in
standardization, and in the market.
To read Part 1 in this series, go to
"The
central problem of SoC design and a proposed xtUML solution."
To read Part 2 in this series, go to
"Executable
UML Models for SoC design."
This series is
reprinted in four parts from Chapter 2 of "UML
for
SoC Design"
with permission of Springer.
Stephen
J. Mellor, Chief Scientist of the Embedded Software
Division at Mentor Graphics, 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. His latest book MDA Distilled:
Principles of Model-Driven Architecture was published in 2004.
John R. Wolfe, Director of Operations and Engineering
for the
BridgePoint UML suite at Mentor Graphics, was President and CEO of
Project Technology, Inc., since acquired by Mentor Graphics acquired
the company, which was focused on tools to execute and translate UML
models in the context of Agile MDA.
Campbell D.
McCausland is a Principal
Engineer, Mentor Graphics
Corp, where he is the chief designer of the BridgePoint UML Suite. He
is the creator of (to his knowledge) the very first model compiler that
translates xtUML models to behavioral VHDL.
John Wolfe will be at the upcoming
Embedded Systems
Conference Boston, September 25-28 at the Mentor Graphics booth
#1201. In addition, Cortland
Starrett will be conducting a series of
seminars. Go to www.embedded.com/esc/boston/ to check on the speaker's schedule
details. Starrett is Engineering Manager leading development of
translation
technology at Mentor Graphics, responsible for the production of tools
and
methods for translating xtUML models into source code such as C, C++,
Java and
VHDL.
References:
[1] MOF Model to Text Transformation RfP - http://www.omg.org/cgi-bin/doc?ad/04-04-07
Other
resources on Embedded.com about UML and xtUML:
1)
Executable and Translatable UML
2)
Need for modeling tools rises with design complexity
3)
In Focus: Unified Modeling Language
4)
Introduction to UML statecharts
5)
UML evolves total system perspective
6)
Introduction to UML Class Diagrams
7)
State machine shortcuts
8)
From
UML to embedded system: Is it possible?