Verifying Ptolemy II Discrete-Event Models using Real

Verifying Ptolemy II Discrete-Event Models
using Real-Time Maude
Kyungmin Bae1 , Peter Csaba Ölveczky2 , Thomas Huining Feng3 , and Stavros
Tripakis3
1
University of Illinois at Urbana-Champaign
2
University of Oslo
3
University of California, Berkeley
Abstract This paper shows how Ptolemy II discrete-event (DE) models
can be formally analyzed using Real-Time Maude. We formalize in RealTime Maude the semantics of a subset of hierarchical Ptolemy II DE
models, and explain how the code generation infrastructure of Ptolemy II
has been used to automatically synthesize a Real-Time Maude verification
model from a Ptolemy II design model. This enables a model-engineering
process that combines the convenience of Ptolemy II DE modeling and
simulation with formal verification in Real-Time Maude.
1
Introduction
Model-based design principles put the construction of models at the center of
embedded system design processes [1,2]. Useful models are executable, providing
simulations of system functionality, performance, power consumption, or other
properties. Ideally, models are translated (code generated) automatically to produce deployable embedded software. Commercial examples of such modeling and
code generation frameworks include Real-Time Workshop (from The MathWorks)
and TargetLink (from dSpace), which generate code from Simulink models, and
LabVIEW Embedded, from National Instruments. Models can also be used to
guide formal verification, which can provide proofs of safety properties or identification of security vulnerabilities.
Ptolemy II is a well-established modeling and simulation tool, developed at
UC Berkeley, that provides a powerful and intuitive graphical modeling language
to allow a user to build hierarchical models that combine different models of
computations [3]. In this paper, we focus on discrete-event (DE) models, which
are explicit about timing behavior of systems. Discrete-event modeling is a time
honored and widely used approach for system simulation [4,5]. More recently,
it has been proposed as basis for synthesis of embedded real-time software [6].
The Ptolemy II realization of DE has a rigorous formal semantics rooted in the
fixed-point semantics of synchronous languages [7].
This paper describes our work on enriching a significant subset of hierarchical Ptolemy II DE models with formal verification capabilities using Real-Time
Maude [8,9] as back-end. Real-Time Maude is a high-performance tool that extends the rewriting-logic-based Maude [10] system to support the formal specification and analysis of object-based real-time systems. Real-Time Maude provides
a spectrum of formal analysis methods, including rewriting for simulation purposes, reachability analysis, and linear temporal logic (LTL) model checking.
In particular, we explain how we have enriched Ptolemy II DE models with
formal verification capabilities by:
1. Formalizing the semantics of transparent hierarchical Ptolemy II DE models
in Real-Time Maude.
2. Defining useful atomic state propositions, so that the Ptolemy user can specify
temporal logic properties to be verified without understanding how Ptolemy
models are represented in Real-Time Maude.
3. Using Ptolemy II’s code generation infrastructure [11] to automatically synthesize a Real-Time Maude verification model from a Ptolemy design model,
and by explaining how both code generation and verification have been integrated into Ptolemy, so that a Ptolemy model can be verified within Ptolemy.
This integration of Ptolemy II and Real-Time Maude enables a model-engineering
process that combines the convenience of Ptolemy II modeling with formal verification in Real-Time Maude.
The main contributions of our work are:
– Enriching Ptolemy with formal verification capabilities to verify properties,
such as the liveness property in Section 6, that cannot be checked using
Ptolemy simulations. Furthermore, the synthesized verification model can be
formally analyzed w.r.t. other properties (e.g., determinism, etc.).
– We show how Real-Time Maude can define the semantics of synchronous
languages with fixed-point semantics. These techniques should be useful for
defining the formal semantics of other synchronous languages. Our semantics
also provides a basis for extensions to, e.g., probabilistic Ptolemy models, that
can then be subjected to statistical model checking using tools like VeStA [12].
Our work is conducted in the context of the NAOMI project [13], where
LM ATL (Lockheed Martin Advanced Technology Laboratories), UC Berkeley,
UIUC, and Vanderbilt University work together to develop a multimodeling design methodology. A key part of this project is the systematic use of model
transformations and code generation to maintain consistency across models.
Section 2 introduces Ptolemy II and Real-Time Maude. The Real-Time Maude
semantics of Ptolemy II DE models is described in Section ??. Section ?? presents
the atomic propositions that allow the user to specify his/her LTL properties
without having the understand the Real-Time Maude translation of a Ptolemy
model. Section ?? explains the Real-Time Maude code generation and integration into Ptolemy. Section 6 illustrates the use of our techniques to verify three
Ptolemy DE models. Finally, Section 7 presents some related work and Section 8
gives some concluding remarks.
2
Preliminaries
This section presents some preliminaries on rewriting logic and Real-Time Maude
(Section 2.1) and the selected discrete event subset of Ptolemy (Section 2.2).
2.1
Rewriting Logic and Real-Time Maude
Modeling. A Real-Time Maude timed module specifies a real-time rewrite theory [14] of the form (Σ, E, IR, TR), where:
– (Σ, E) is a membership equational logic [10] theory with Σ a signature4 and
E a set of confluent and terminating conditional equations. (Σ, E) specifies
the system’s state space as an algebraic data type, and must contain a specification of a sort Time modeling the (discrete or dense) time domain.
– IR is a set of (possibly conditional) labeled instantaneous rewrite rules specifying the system’s instantaneous (i.e., zero-time) local transitions, written
rl [l] : t => t0 , where l is a label. Such a rule specifies a one-step transition from an instance of t to the corresponding instance of t0 . The rules are
applied modulo the equations E.5
– TR is a set of tick (rewrite) rules, written with syntax
rl [l] : {t} => {t0 } in time τ .
that model time elapse. {_} is a built-in constructor of sort GlobalSystem,
and τ is a term of sort Time that denotes the duration of the rewrite.
The initial state must be a ground term of sort GlobalSystem and must be
reducible to a term of the form {t} using the equations in the specifications.
The Real-Time Maude syntax is fairly intuitive. For example, function symbols, or operators, are declared with the syntax op f : s1 . . . sn -> s. f is the
name of the operator; s1 . . . sn are the sorts of the arguments of f ; and s is
its (value) sort. Equations are written with syntax eq t = t0 , and ceq t = t0 if
cond for conditional equations. The mathematical variables in such statements
are declared with the keywords var and vars. We refer to [10] for more details
on the syntax of Real-Time Maude.
We make extensive use of the fact that an equation f (t1 , . . . , tn ) = t with the
owise (for “otherwise”) attribute can be applied to a subterm f (. . .) only if no
other equation with left-hand side f (u1 , . . . , un ) can be applied.6
In object-oriented Real-Time Maude modules, a class declaration
class C | att1 : s1 , ... , attn : sn .
4
5
6
i.e., Σ is a set of declarations of sorts, subsorts, and function symbols
E is a union E 0 ∪ A, where A is a set of equational axioms such as associativity,
commutativity, and identity, so that deduction is performed modulo A. Operationally,
a term is reduced to its E 0 -normal form modulo A before any rewrite rule is applied.
A specification with owise equations can be transformed to an equivalent system
without such equations [10].
declares a class C with attributes att1 to attn of sorts s1 to sn . An object of class
C in a given state is represented as a term < O : C | att1 : val1 , ..., attn : valn >
of sort Object, where O, of sort Oid, is the object’s identifier, and where val1
to valn are the current values of the attributes att1 to attn . In a concurrent
object-oriented system, the state is a term of the sort Configuration. It has
the structure of a multiset made up of objects and messages. Multiset union
for configurations is denoted by a juxtaposition operator (empty syntax) that
is declared associative and commutative, so that rewriting is multiset rewriting
supported directly in Real-Time Maude.
The dynamic behavior of concurrent object systems is axiomatized by specifying each of its transition patterns by a rewrite rule. For example, the rule
rl [l] :
< O : C | a1 : 0, a2 : y, a3 : w, a4 : z > =>
< O : C | a1 : T, a2 : y, a3 : y + w, a4 : z >
defines a parameterized family of transitions (one for each substitution instance)
which can be applied whenever the attribute a1 of an object O of class C has
the value 0, with the effect of altering the attributes a1 and a3 of the object.
“Irrelevant” attributes (such as a4, and the right-hand side occurrence of a2)
need not be mentioned in a rule (or equation).
A subclass inherits all the attributes and rules of its superclasses.
Formal Analysis. A Real-Time Maude specification is executable, and the tool
offers a variety of formal analysis methods. The rewrite command simulates
one behavior of the system up to a certain duration. It is written with syntax
(trew t in time <= τ .), where t is the initial state and τ is a term of sort
Time. The search command uses a breadth-first strategy to analyze all possible
behaviors of the system, by checking whether a state matching a pattern and
satisfying a condition can be reached from the initial state.
Real-Time Maude also extends Maude’s linear temporal logic model checker [10]
to check whether each behavior, possibly up to a certain time bound, satisfies
a temporal logic formula. State propositions are terms of sort Prop, and their
semantics should be given by (possibly conditional) equations of the form
{stateP attern} |= prop = b
for b a term of sort Bool, which defines the state proposition prop to hold in
all states {t} where {t} |= prop evaluates to true. A temporal logic formula
is constructed by state propositions and temporal logic operators such as True,
False, ~ (negation), /\, \/, -> (implication), [] (“always”), <> (“eventually”),
and U (“until”). The time-bounded model checking command has syntax
(mc t |=t formula in time <= τ .)
for initial state t and temporal logic formula formula .
2.2
Ptolemy II and its DE Model of Computation (Stavros?
Thomas?)
The Ptolemy project7 studies modeling, simulation, and design of concurrent,
real-time, embedded systems. The key underlying principle in the project is the
use of well-defined models of computation (MoCs) that govern the interaction
between concurrent components. A major problem area being addressed is the
use of heterogeneous mixtures of MoCs [3]. A result of the project is a software
system called Ptolemy II, implemented in Java. Ptolemy II allows a user to build
hierarchical models that combine different MoCs, also called domains, including
state machines, data flow, and discrete-event models. Models can be edited and
simulated. In addition, code generation capabilities allow these models to be
translated into models in other languages (e.g., in Real-Time Maude, the topic
of this paper) or into sequential C or Java code.
Discrete-Event Models in Ptolemy II. As mentioned above, Ptolemy II
models can combine multiple MoCs. The focus of this paper is the formalization
of a subset of Ptolemy II models, namely, discrete-event (DE) models, in RealTime Maude. A Ptolemy II model consists of a set of actors, each having a set
of ports, that can be connected to form a diagram. A Ptolemy II model can be
hierarchical, in the sense that some actors are composite: they are refined into
another diagram. Non-composite actors are called atomic actors. See Section 6
for examples of Ptolemy II DE models.
DE actors consume and produce tagged events at their input and output ports,
according to the tagged signal model [15]. A tagged event is a pair (v, t) where
v is a value and t is a tag, modeling the time that the event occurs. Ptolemy II
DE models use super-dense time, where a tag t is a pair (τ, n) ∈ R≥0 × N,
where R≥0 is the set of non-negative real numbers and N is the set of natural
numbers: τ is called the timestamp and indicates the model time when this event
occurs; n is the microstep index. Super-dense time is useful for modeling multiple
events that happen at the same time (i.e., have the same timestamp), but in
sequence, where perhaps some events cause other events. Super-dense tags are
totally ordered using a lexicographic order: (τ1 , n1 ) ≤ (τ2 , n2 ) iff τ1 < τ2 , or
τ1 = τ2 and n1 ≤ n2 . Two events are called simultaneous if they have identical
tags, i.e., (τ1 , n1 ) = (τ2 , n2 ).
The operational semantics of Ptolemy II DE models combines a synchronousreactive fixed-point iteration with advancement of time governed by an event
queue [7]. Events in this queue are ordered according to their tags, from earlier
(at the head of the queue) to later. Operation proceeds by removing events from
the head of the queue and processing them, that is, feeding them to actors, which
may in turn generate new events, also stored in the queue. The tag of the event(s)
being currently processed defines a global time for the model, called model time.
One difference between Ptolemy II and standard DE simulators is that, at a given
model time tag (τ, n), the semantics is defined as the least fixed-point of a set of
7
http://ptolemy.eecs.berkeley.edu/
equations, similarly to a synchronous model [16]. This allows Ptolemy II models
to have arbitrary feedback loops. Semantics of such models can always be given,
although they may result in unknown (bottom) values, in case the model contains
causality cycles. See [7] for details. Conceptually, the semantics can be captured
as shown in Figure 1.
Q := empty; //
for each actor
A.init(); //
//
end for;
Initialize the global event queue to be empty.
A do
Initialize actor A (e.g., its state).
May generate new events, stored in Q.
while Q is non-empty do
E := set of all simultaneous events at the head of Q;
remove E from Q;
initialize ports with values in E or "unknown";
while port values changed do
for each actor A do
A.fire();
// May change port values
end for;
end while;
// Fixed-point reached for the current tag
for each actor A do
A.postfire(); // Updates actor state, and may generate new queue events
end for;
end while;
Figure 1. Pseudo-code of Ptolemy II DE semantics.
To complete the semantics, the semantics of each actor needs to be defined
(this semantics is implemented by functions “init()”, “fire()” and “postfire()”).
We now briefly explain the semantics of some basic atomic actors.
– SingleEvent: This actor has a single output port, and it produces a single
event (v, t) at that port, with a specified value v at a specified time tag t.
Both v and t are specified as parameters.
– TimedDelay: this actor has a single input port, a single output port, and a
parameter d ∈ R≥0 . For each event (v, (τ, n)) that it receives at its input port,
the TimedDelay actor produces an event (v, (τ 0 , n0 )) at its output port, such
that: if d > 0 then τ 0 = τ + d and n0 = 0, otherwise, τ 0 = τ and n0 = n + 1.
An essential feature of Ptolemy II is hierarchy. This helps hide internal details
of parts of a model, and therefore is crucial for managing model complexity, and
for achieving modularity and scalability. Hierarchical models can be created using
composite actors, which are refined into new diagrams. If all composite actors in
the hierarchy belong to the DE domain, then the semantics of the hierarchical
model is equivalent to that of the corresponding flattened model, where composite
actors are replaced by their internal diagrams, with hierarchy removed.
Ptolemy II also allows another mechanism for building hierarchical models,
using finite state machines, where every state of the machine can be refined into
an internal diagram. These are called modal models in Ptolemy II. To illustrate
the semantics of such models, consider the example of a modal model with two
states, s1 and s2 , refined into diagrams A1 and A2 . The latter diagrams can be
viewed as composite actors, and they have the same sets of input and output
ports. Then, the semantics of this modal model can be thought of as that of a
diagram consisting of A1 , A2 and Ac , where Ac is an actor modeling the finite
state machine, that controls the mode of the rest of the diagram. If Ac is in state
s1 , then the external inputs are routed to the inputs of A1 and the outputs of
A1 are routed to the external outputs. During that time, A2 remains “frozen” in
the sense that its state does not evolve. When the automaton Ac changes state
to s2 , A1 becomes “frozen” while A2 becomes active. And so on.
Ptolemy code gen adapter infrastructure. Ptolemy II is built in a highly
modular manner, with flexible and extensible components that communicate
through generic interfaces. This type of inter-component communication introduces overhead, however, which generally results in component models that are
slower than custom-built code. To regain the lost efficiency, Ptolemy II offers
a code generation capability where inter-component communication is reduced
by generating “monolithic” code with highly specialized components. The idea
is to use static analysis to discover data types, buffer sizes, parameter values,
execution schedules, and so on, and then partial evaluation to propagate this information in order to synthesize an efficient implementation that preserves the
original semantics.
Our code generation framework uses a adapter-based mechanism. A codegen
adapter is essentially a component that generates code for a Ptolemy II actor.
Each actor may have multiple associated adapters, one for each target language
(C, VHDL, etc.). A adapter essentially consists of a Java class file and a code
template file. The latter contains code blocks written in the target language. The
codegen kernel uses the Java class of the adapter to harvest code blocks from the
code template file. The Java class of the adapter determines which code blocks
to harvest based on the actor instance-specific information (e.g., port type, port
width, and parameter values). The main advantages of this scheme are, first, that
it decouples the writing of Java code and target code (otherwise the target code
would be wrapped in strings and interspersed with Java code), and second, that
it allows using a target language specific editor while working on the target language code blocks. For details on the adapter-based code generation scheme, the
reader is referred to http://ptolemy.eecs.berkeley.edu/ptolemyII/ptII7.
0/ptII7.0.1/doc/codegen.htm.
3
Formalizing the Semantics of Ptolemy DE Models in
Real-Time Maude
This section presents the Real-Time Maude semantics of the targeted subset of
Ptolemy given in Section 3.1.
In order to convey our ideas without introducing too much detail, we first
explain our semantics for flat Ptolemy models; that is, models without hierarchical actors (Section 3.2). Section 3.4 shows how this semantics is extended to the
hierarchical setting. The entire exutable Real-Time Maude semantics is available
at http://www.ifi.uio.no/RealTimeMaude/Ptolemy.
3.1
Supported Subset of Ptolemy
We currently support Real-Time Maude analysis of transparent discrete event
(DE) Ptolemy models; that is, DE models where subdiagrams are also executed
under the DE director. We support composite actors, modal models (hierarchical finite state machines), and the following atomic actors: finite state machine
(FSM), timed delay, variable delay, clock, current time, timer, noninterruptible
timer, pulse, ramp, timed plotter, set variable, and single event actors. We also
support connections with multiple destinations, split signals, and both single
ports and multi-input ports.
3.2
Representing Flat Ptolemy DE Models in Real-Time Maude
This section defines our semantics for flat Ptolemy models.
Our Real-Time Maude semantics is given in an object-oriented style, where
the global state has the form of a multiset
{actors
connections
< global : EventQUEUE | queue : event queue >}
where
– actors are objects corresponding to the actors in the system,
– connections are the connections between the ports of the different actors, and
– < global : EventQUEUE | queue : event queue > is an object whose queue
attribute denotes the global event queue.
This section explains the representation of these entities in Real-Time Maude,
and Section ?? defines the semantics of the behaviors of the Ptolemy models.
Actors. Each Ptolemy actor is modeled in Real-Time Maude as an object instance of a subclass of the following class Actor:
class Actor | ports : Configuration,
parameters : AssignMap,
store : Memory .
The ports attribute denotes the set of ports of the actor. In our model, a port is
modeled as an object, as shown below. The parameters attribute represents the
parameters of the corresponding Ptolemy actor, together with their user-defined
values/expressions, as a semicolon-separated set of terms of the form ’parametername |-> expression. The sort AssignMap is declared as follows:
sort VarAssignment ValueAssignment .
subsort ValueAssignment < VarAssignment .
op _|->_ : VarId Exp -> VarAssignment [ctor prec 80] .
op _|->_ : VarId Value -> ValueAssignment [ditto] .
sort AssignMap ValueMap .
--- set of variable assignments
subsorts ValueAssignment < VarAssignment ValueMap < AssignMap .
op emptyMap : -> ValueMap [ctor] .
op _;_ : AssignMap AssignMap -> AssignMap [ctor assoc comm id: emptyMap prec 90] .
op _;_ : ValueMap ValueMap -> ValueMap [ditto] .
The parameters attribute is not modified during system execution. Instead, the
store attribute computes the values of the needed parameters in each iteration
using a continuation style computation (see [?] for details).
Specific Actors. The actors are all modeled as objects of subclasses of the
sort Actor. Some actors, such as clocks and timed plotters, have an internal
clock measuring “model time.” Such actors are represented as object instances
of subclasses of the following class TimeActor, where current-time denotes the
current model time:
class TimeActor | current-time : Time .
subclass TimeActor < Actor .
Clocks. Ptolemy’s Clock actors have as parameters a clock period, and samesized arrays values and offsets. In each period, a clock generates events with
given values and offsets within the period. For example, if the period is 5, the
values are {3, 8} and the offsets are {2, 4}, then an event with value 3 is generated
at times 2, 7, 12, 17, 22, . . . , and an event with value 8 is generated at times
4, 9, 14, . . . . As explained above, the Ptolemy parameters of an actor (period,
offsets, and values for clock actors) are represented in the parameters attribute.
The only additional attribute needed for the Real-Time Maude representation of
clock actors is the attribute index keeping track of the “index” of the offsets and
values arrays for the next event to be generated:
class Clock | index : Nat .
subclass Clock < Actor .
For instance, the initial state of the clock described above would be decribed by
the object in state
< ’Clock : Clock |
index : 0, store : emptyMap,
parameters : (’period |-> # 5 ;
’offsets |-> {# 2.0, # 4.0} ;
’values |-> {# 3, # 8}),
ports : (< ’output : OutPort | value : # 0, status : absent >
< ’trigger : InPort | value : # 0, status : absent >
< ’period : PortParameter | value : # 0, status : absent,
paramName : ’period >) >
Current Time. Ptolemy’s current time actor produces an output token on each
firing with a value that is the current model time. Since the superclass TimeActor
already contains the current time in the current-time attribute, the CurrentTime
subclass does not add any new attributes:
class CurrentTime .
subclass CurrentTime < TimeActor .
Timed Plotter. A timed plotter records its received data values and the times they
were received. In our representation, these values are recorded as a list (source:
s1 time: t1 value: v1 ) ++ ... ++ (source: sn time: tn value: vn ) of triples
(source: si time: ti value: vi ), denoting, respectively, the port from which
the data was received, the time it was received, and the received data value. Since
such as actor must keep track of the current-time, the TimedPlotter class is
declared as a subclass of TimeActor:
class TimedPlotter | event-history : EventHistory .
subclass TimedPlotter < TimeActor .
sort EventTriple EventHistory .
subsort EventTriple < EventHistory .
op source:_time:_value:_ : EPortId Time Value -> EventTriple .
op emptyHistory : -> EventHistory .
op _++_ : EventHistory EventHistory -> EventHistory [ctor assoc id: emptyHistory] .
Timed Delay. A timed delayed actor propagates an incoming event after a given
time delay. In particular, if the delay parameter is 0.0, then the this actor produces on its output port the event consumed in the previous iteration with the
same time stamp, if there was one; that is, there is a ”microstep” delay on the
generation of the output event. Since the delay parameter is represented in the
parameters attribute of Actor, this subclass does not add any attributes:
class Delay .
subclass Delay < Actor .
Variable Delay. The variable delay actor works in a similar way as the timed
delay actor, except that the amount of time delayed is specified by an incoming
token through the delay port (a parameter port):
class VariableDelay .
subclass VariableDelay < Actor .
Timer. The difference between a timer actor and a delay actor is that the value
of the generated output of a timer is not the same as the input, but is instead
given by the output parameter of this actor. The length of the delay is specified
by the input:
class Timer .
subclass Timer < Actor .
Noninterruptible Timer. A noninterruptible timer is similar to a normal timer,
but with the difference that the noninterruptible timer actor delays the processing
of a new input if it has not finished processing a previous input, while the Timer
actor begins processing inputs immediately upon their arrival. The Real-Time
Maude declaration of this class is
class NonInterruptibleTimer | processing : Bool,
wait-queue : TimeList .
subclass NonInterruptibleTimer < Actor .
For this class, we need some attributes that keeps track of the state: processing is
true when the timer has not finished processing previous inputs. The wait-queue
is a list that stores (the values of) the inputs received while the timer is “busy.”
This list is therefore a list of time values declared in the usual Maude style as
follows:
sort TimeList .
subsort Time < TimeList .
op emptyList : -> TimeList .
op __ : TimeList TimeList -> TimeList [ctor assoc id: emptyList] .
Ramps. Each time a ramp actor fires, it generates an event with a value that is
incremented by the specified step each iteration. The first output is given by the
init parameter. The corresponding class Ramp is declared in the expected way:
class Ramp | firstStep : Bool,
subclass Ramp < Actor .
output-value : Value .
The firstStep attribute is used to separate between the first and the other
events generated by the ramp, and output-value denotes the value of the last
event generated by the ramp.
Pulse. A pulse actor produces pulses with values given by the values parameter;
the parameter indexes specifies when those values should be produced. A zero
is produced when the iteration count does not match an index. For example, if
values is {4, 11, 8} and the indexes parameter is {0, 2, 3}, the sequence of outputs will be 4, 0, 11, 8, 0, 0, . . .. The current-index attribute keeps tracks of the
iteration count:
class Pulse | current-index : Nat .
subclass Pulse < Actor .
Set Variable. This actor contains a variable that is set by a signal. In principle,
this has a delayed parameter, so that if delayed is true, then the change of the
variable value is only effected at the end of current toplevel iteration. If delayed
is false, then the change to the value of the variable is performed immediately.
However, this can result in nondeterminism if the variable values are observed
by any other actor in the system. (This nondeterminism can be overcome by
using the output of the set variable.) In our model, we haven’t taken the delayed
parameter into account, and assume it to be false. The setv message is sent to
change the value of the variable:
class SetVariable | variableName : VarId .
subclass SetVariable < Actor .
--- message to change a variables of the container.
msg setv : VarId Value -> Msg .
Single Event. This actor produces an event with the specified value at the specified time:
class SingleEvent .
subclass SingleEvent < Actor .
Finite State Machine (FSM) Actors. An FSM actor is a transition system containing a finite set of states and a finite set of transitions. A transition
has a guard expression, and can contain a set of output actions and variable
assignments. When an FSMActor is fired, there is never more than one enabled
transition. If there is exactly one enabled transition then it is chosen and the
actions contained by the transition are executed. Under the DE director, only
one transition step is performed in each iteration.
An FSM-Actor is then characterized by its current state and its set transitions:
class FSM-Actor | currState : Location,
initState : Location,
transitions : TransitionSet .
subclass FSM-Actor < Actor .
A location is the sort of the local “states” of the transition system. In particular,
quoted identifiers (Qids) are state names:
sort Location .
subsorts Qid < Location .
In our fragment of Ptolemy, we model the set of transitions as a semi-colonseparated set of transitions of the form
l : s1 --> s2 {guard: g output: pi1 |-> ei01 ;...; pik |-> ei0k set: vj1 |-> ej10 ;...;
vil |-> ejl0 }
for state/locations s1 and s2 , port names pi , variables vi , and expressions ei .
The guard, output, and/or set parts may be omitted. The transition label l is a
unique label within the actor for each transition, and is added in our semantics.8
In Real-Time Maude, such sets of transitions are declared as follows:
sort Transition TransitionSet TransitionId .
subsort Transition < TransitionSet .
subsort Qid < TransitionId .
op _:_-->_‘{_‘} : TransitionId Location Location TransBody -> Transition [ctor] .
op emptyTransitionSet : -> TransitionSet [ctor] .
op _;_ : TransitionSet TransitionSet -> TransitionSet
[ctor assoc comm id: emptyTransitionSet] .
sort TransBody .
op guard:_output:_set:_ : Exp AssignMap AssignMap -> TransBody [ctor] .
8
In our code generation, this label is automatically generated for each transition.
Ports. A port is represented as an object, with a name (the identifier of the port
object), a status (unknown, present, or absent), and a value. We also have
subclasses for input, output, and input/output ports:
sorts PortId .
subsort Qid < PortId < Oid .
--- names for (local) ports
class Port | status : PortStatus, value : Value .
class InPort .
class OutPort .
class InOutPort .
subclass InPort < Port .
subclass OutPort < Port .
subclass InOutPort < InPort OutPort .
sort PortStatus .
ops unknown present absent : -> PortStatus [ctor] .
We also support multiple input ports.
A Port parameter is an input port whose input value updates a given actor
parameter:
class PortParameter | paramName : VarId .
subclass PortParameter < InPort .
Connections. A connection is just a term po ==> pi1 ; . . . ; pin of sort Connection,
where the pj s are either local port names or have the form a!p for a a relative
name of an actor. Such a connection connects the output port po to all the
input ports pi1 , . . . , pin Since connections appear in configurations, we let them
be subsorts of the sort Object:
sort Connection .
op _==>_ : EPortId EPortIdSet -> Connection [ctor] .
subsort Connection < Object .
sorts EPortId .
op _!_ : ActorID PortId -> EPortId [ctor] .
sort EPortIdSet .
subsort EPortId < EPortIdSet .
op noPort : -> EPortIdSet [ctor] .
op _;_ : EPortIdSet EPortIdSet -> EPortIdSet [ctor assoc comm id: noPort] .
The Global Event Queue. The global event queue is represented by an object
< global : EventQUEUE | eventQueue : event queue >
where event queue is an ::-separated ordered list of terms of the form
set of events ; time to fire ; microstep
where the set of events is a set of events, each event characterized by the “global
port name” where the generated event should be output and the corresponding
value, time to fire denotes the time until the events are supposed to fire, and
microstep is the additional “microstep” until the event fires:
sort Event .
op event : EPortId Value -> Event [ctor] .
sort Events .
subsort Event < Events .
op noEvent : -> Events [ctor] .
op __ : Events Events -> Events [ctor assoc comm id: noEvent] .
sort TimedEvent .
op _;_;_ : Events Time Nat -> TimedEvent [ctor] .
sort EventQueue .
subsort TimedEvent < EventQueue .
op nil : -> EventQueue .
op _::_ : EventQueue EventQueue -> EventQueue [ctor assoc id: nil] .
3.3
Specifying the Behavior of Flat DE Models
The behavior of Ptolemy DE models can be summarized as repeatedly performing
the following actions:
– Time is advanced until the remaining time until the firing of the first events
in the global event queue is (0, 0).
– Then an iteration of the system is performed. That is,
1. (Prefire) The events that are supposed to fire are added to the corresponding output ports; the status of all other ports is set to unknown.
2. (Fire) Then the fixpoint of all ports is computed by gradually increasing
the knowledge about the presence/absence of inputs to and output from
ports until a fixpoint is reached.
3. (Postfire) Finally, the actors with inputs or scheduled events are executed;
states are changed and new events are generated and inserted into the
global event queue.
Our semantics uses three rules to make the various steps explicit.
The following tick rule advances time until the time when the first events in
the event queue are scheduled:
vars SYSTEM CF PORTS : Configuration . var O : Oid . var EVTS : Events .
var NZT : NzTime . var N : Nat . var NZ : NzNat . var P : PortId .
var EVENTQUEUE : EventQueue . vars T T’ : Time . var PS : PortStatus .
rl [tick] :
{SYSTEM < global : EventQUEUE | queue : (EVTS ; NZT ; N) :: EVENTQUEUE >}
=>
{delta(SYSTEM, NZT)
< global : EventQUEUE | queue : (EVTS ; 0 ; N) :: delta(EVENTQUEUE, NZT) >}
in time NZT .
In this rule, the first element in the event queue has non-zero delay NZT. Time
is advanced by this amount NZT, and as a consequence, the (first component of
the) event timer goes to zero. In addition, the function delta is applied to all
the other objects (denoted by SYSTEM) in the system. The function delta defines
effect of time elapse on the objects. This function is also applied to the other
elements in the event queue, where it decreases the remaining time of each event
set by the elapsed time NZT (x monus y equals max(0, x − y)):
op delta : EventQueue Time -> EventQueue .
eq delta((EVTS ; T ; N) :: EVENTQUEUE, T’)
= (EVTS ; T monus T’ ; N) :: delta(EVENTQUEUE, T’) .
eq delta(nil, T) = nil .
The function delta on configurations distributes over the elements in the
configuration:
op delta : Configuration Time -> Configuration .
eq delta(none, T) = none .
eq delta(NCF NCF’, T) = delta(NCF, T) delta(NCF’, T) .
This function must then be defined on single objects, as shown later.
The next rule is a “microstep tick rule” that advances “time” with some
microsteps if needed to enable the first events in the event queue:
rl [shortTick] :
{SYSTEM < global : EventQUEUE | queue : (EVTS ; 0 ; NZ) :: EVENTQUEUE >}
=>
{SYSTEM < global : EventQUEUE | queue : (EVTS ; 0 ; 0) :: EVENTQUEUE >} .
Finally, when the remaining time and microsteps of the first events in the
event queue are both zero, an iteration of the system can be performed:
rl [executeStep] :
{SYSTEM < global : EventQUEUE | queue : (EVTS ; 0 ; 0) :: EVENTQUEUE >}
=>
{< global : EventQUEUE | queue : EVENTQUEUE >
postfireAll(portFixPoints(addEventsToPorts(EVTS, prefireAll(SYSTEM))))} .
The function prefireAll initializes each actor before execution. In particular, it
computes the current values of the variables in the store, and sets the status of
each port to unknown. The operator addEventsToPorts inserts the events scheduled to fire into the corresponding output ports. The portFixPoints function
then finds the fixed points for all the port (fire), and postfire “executes” the
steps on the computed port fixpoints (postfire). postfire also changes the states
of the objects and generates new events and inputs them into the global event
queue.
Therefore, to completely define the behavior of the actors, we must define the
functions prefireAll, portFixPoints, postfire, and delta on the different
objects in the system.
Initialize Actors. The prefireAll function calls the prefire function for each
actor object in the state. The prefire function takes an additonal argument: the
(smallest) class of the object. In this way, the prefire function on a subclass
may also call the prefire function defined on the superclass:
op prefireAll : Configuration -> Configuration [frozen (1)] .
op prefire : Cid Object -> Configuration [frozen (2)] .
var REST : Configuration .
var OBJ : Object .
eq prefireAll(OBJ REST) = prefire(class(OBJ), OBJ) prefireAll(REST) .
For the main superclass Actor, the prefire function just clears all the ports,
that is, sets their status to unknown:
eq prefire(Actor, < O : Actor | ports : PORTS >)
= < O : Actor | ports : clearPorts(PORTS) > .
op clearPorts : Configuration -> Configuration .
eq clearPorts(< P : Port | status : PS > PORTS)
= < P : Port | status : unknown > clearPorts(PORTS) .
eq clearPorts(none) = none .
The prefire function for the different actors is defined by first (re-)initializing
the store to compute the current values of the expressions for the parameters,
and then calling prefire for the superclass Actor. For example, the prefire
function is defined on Clock actors as follows:
eq prefire(Clock ,
< O : Clock | parameters : (’period |->
’values |->
= prefire(Actor ,
< O : Clock | store : ’period |-> k(EP)
’values |-> k(EV)
EP ; ’offsets |-> EO ;
EV ; AM) >)
; ’offsets |-> k(EO) ;
>) .
For FSM actors, the prefire function initializes the store to contain a value
@guard(li ) |-> k(guardi ) for each transition with label li (and corresponding
guard expression guardi ) that starts in the location STATE:
eq prefire(FSM-Actor ,
< O : FSM-Actor | store : MEM, currState : STATE,
transitions : TRANSSET >)
= prefire(Actor ,
< O : FSM-Actor | store : evalGuards(STATE, TRANSSET) ; MEM >) .
Computing the Fixpoint for Ports. The idea behind the definition of the
function portFixPoints, that computes the fixpoint for the values of all the
ports, is simple. The state has the form portFixPoints(objects and connections), where initially, the only port information are the events scheduled for
this iteration. For each possible case when the status of an unknown port can be
determined to be either present or absent, there is an equation
eq portFixPoints(< O : ... | ports : < P : Port | status : unknown > PORTS,
... >
connections and other objects) =
portFixPoints(< O : ... | ports : < P : Port | status : present,
value : ... > PORTS, ... >
connections and other objects) .
(and similarly for deciding that input/output will be absent). The fixpoint is
reached when no such “information-increasing” equation can be applied. Then,
the portFixPoints operator is removed by using the owise construct of RealTime Maude9 :
var OC : ObjectConfiguration .
eq portFixPoints(OC) = OC [owise] .
The following equation propagates port status from a “known” output port
to a connecting unknown input port. The present/absent status (and possibly
the value) of the output port PI of actor O is propagated to the input port PI’
of the actor O’ through the connection (O ! PI) ==> ((O’ ! PI’) ; EPIS):
ceq portFixPoints(
< O : Actor |
ports : < PI : OutPort | status : PS , value : V > PORTS >
((O ! PI) ==> ((O’ ! PI’) ; EPIS))
< O’ : Actor | ports : < PI’ : InPort | status : unknown > PORTS’ >
REST)
= portFixPoints(< O : Actor | >
((O ! PI) ==> ((O’ ! PI’) ; EPIS))
< O’ : Actor |
ports : < PI’ : InPort | status : PS, value : V > PORTS’ >
REST)
if PS =/= unknown .
When the status of port PI has been decided, the value of each subexpression
isPresent(PI) in the store is replaced by # true (resp., # false for absent
ports):
eq portFixPoints(
< O : Actor | ports : < PI : Port | status : present > PORTS,
store : MI |-> k(isPresent(PI) -> K) ; MEM >
REST)
= portFixPoints(
< O : Actor | store : MI |-> k(# true -> K) ; MEM > REST) .
9
The restriction to object configurations is crucial, as it ensures that all prefire and
addEventsToPorts applications have been performed before the equation below is
taken.
The portFixPoints function must then be defined for each kind of actor to
decide whether the actor produces any output in a given port.
For example, the timed delay actor does not produce any output in this iteration as a result of any input. Therefore, if its status is unknown (that is, the
delay actor did not schedule an event for this iteration), its output port should
be set to absent:
eq portFixPoints(
< O : Delay | ports : < PI : OutPort | status : unknown > PORTS > REST)
= portFixPoints(
< O : Delay | ports : < PI : OutPort | status : absent > PORTS > REST) .
We have run this
with input only to
the step port, and
are somewhat surprised to find that
output is generated
even if the trigger
port has no input!
Actors, such as variable delay, clock actors, timers, etc., that generate “delayed”
events as a result of receiving input, have the same definition of portFixPoint.
Other actors generate immediate output when receiving input. For example, a
Ramp actor generates an output value which equals the previous value plus the
current value of step if it gets input from either its port trigger or its step:
eq portFixPoints(
< O : Ramp | count : N, output-value : V,
store : ’init |-> VI ; ’step |-> VS,
ports : < PI : InPort | status : present >
< ’output : OutPort | status : unknown > PORTS >
REST)
= portFixPoints(
< O : Ramp | ports : < PI : InPort | > PORTS
< ’output : OutPort | status : present,
value : if N > 0 then V + VS else VI fi > >
REST) .
eq portFixPoints(
< O : Ramp | ports : <
<
<
REST)
= portFixPoints(
< O : Ramp | ports : <
<
REST) .
’trigger : InPort | status : absent >
’step : InPort | status : absent >
’output : OutPort | status : unknown > PORTS >
’trigger : InPort | >
< ’step : InPort | >
’output : OutPort | status : absent > >
Likewise, when the current time actor gets an input, it outputs the current
model time, given by its current-time attribute:
ceq portFixPoints(
< O : CurrentTime | current-time : T,
ports : < PI : InPort | status : PS >
< PI’ : OutPort | status : unknown > >
REST)
= portFixPoints(
< O : CurrentTime | ports : < PI : InPort | >
< PI’ : OutPort | status : PS , value : # T >
REST)
if PS =/= unknown .
Likewise, the set variable actor generates output if and only if it receives input
in its only input port:
ceq portFixPoints(
< O : SetVariable | ports : <
<
REST)
= portFixPoints(
< O : SetVariable | ports : <
<
REST)
if PS =/= unknown .
PI : InPort | status : PS , value : V >
PI’ : OutPort | status : unknown > >
PI : InPort | >
PI’ : OutPort | status : PS, value : V > >
For the FSM actor, the portFixPoints function should check whether some
of the guard expressions entered into the store by prefire evaluates to # true.
If so, the corresponding transition should be applied. That is, for portFixPoints,
the expressions in the output list of that transition must be computed. Hence,
these expressions are added to the store for further evaluation. Furthermore, we
must “remember” the transition taken, so that postFire can do the corresponding updates. Therefore, we add to the store the next location and the variable
assignment expressions in the transition “taken”:
eq portFixPoints(
< O : FSM-Actor | ports : < PI : InPort | status : present > PORTS,
store : @guard(TI) |-> # true ; MEM,
transitions : (
(TI : STATE --> STATE’ {guard: TG output: OL set: AL}) ; TRANSSET) >
REST)
= portFixPoints(
< O : FSM-Actor | ports : < PI : InPort | > PORTS,
store : @next(STATE’) ; evalOut(OL) ;
evalSet(AL) ; clearGuards(MEM) >
REST) .
The other transition guards to be evaulated can be cleared from the store of
values to be computed.
The following equation updates the outgoing port PI for which the output
value V has been computed. All other outgoing ports are set to absent at the
last step, where no @out exists in the store. In the left-hand side, we make sure
that there is some input to the FSM actor.
eq portFixPoints(
< O : FSM-Actor | ports : < PI’ : InPort | status : present >
< PI : OutPort | > PORTS,
store : @out(PI) |-> V ; MEM >
REST)
= portFixPoints(
< O : FSM-Actor | ports : < PI’ : InPort | >
< PI : OutPort | status : present , value : V >
if noOutValue(MEM) then
setUnknownOutPortsAbsent(PORTS)
else PORTS fi,
store : MEM >
REST) .
Other equations for portFixPoints on FSM actors specify the cases when
all inputs are absent or when no transition can be enabled. In these cases, every
output ports should be set to absent. The following equation does the process if
every input ports are absent and any output port is unknown.
ceq portFixPoints(
< O : FSM-Actor | ports : < PI : InPort | status : absent >
< PI : OutPort | status : unknown > PORTS >
REST)
= portFixPoints(
< O : FSM-Actor | ports : < PI : InPort | >
< PI : OutPort | status : absent >
setUnknownOutPortsAbsent(PORTS) >
REST)
if allInputPortsAbsent(PORTS) .
The other case is not so trivial, since we need to consider undecidable situation
so that a value of guard expression could not be decided. That is typically caused
by unknown input ports. However, the solution is surprisingly simple. We can
use that a guard expression is not reduced to a value if there exists an unknown
component in the expression. Therefore, we only need to check if every guard
expression is reduced to false; if a guard expression is not reduce to both true
and false, it means undecidable situation. The following equation takes care this:
ceq portFixPoints(
< O : FSM-Actor | ports : < PI : InPort | status : present > PORTS,
store : @guard(TI) |-> # false ; MEM >
REST)
= portFixPoints(
< O : FSM-Actor | ports : < PI : InPort | >
setUnknownOutPortsAbsent(PORTS),
store : clearGuards(MEM) >
REST)
if allGuardsFalse(MEM) .
Postfire. Finally, the postfireAll function corresponds to “postfire” in Ptolemy.
The internal states are updated, and new events are generated and entered into
the event queue. The prefireAll function distributes over the actor objects in
the configuration, and becomes the prefire function on such objects. Just like
the prefire function, the postfire function takes as first argument the class
name of the object, so that the prefire function of objects of a subclass can also
call prefire for the superclass:
op
eq
eq
eq
postfireAll : Configuration -> Configuration .
postfireAll(CX:Connection CF) = CX:Connection postfireAll(CF) .
postfireAll(OBJ CF) = postfire(class(OBJ), OBJ) postfireAll(CF) [owise] .
postfireAll(none) = none .
op postfire : Cid Object -> Configuration .
The prefire function must then be defined for each class. Sometimes, postfire
generates a new event with value v that should fire at time t and microstep n
from the current time. In these cases, postfire puts the new event into the event
queue and has the form
eq postfire(C,
< O : C | ports : < PI : OutPort | > PORTS, ... >)
< global : EventQueue | queue : EQ >
=
< O : C | ... >
< global : EventQueue | queue : addEvent(event(O . PI, v), t, n EQ) > .
where the function addEvent adds the new event into the correct place in the
event queue:
op addEvent : Event Time Nat EventQueue -> EventQueue .
--- add event first:
ceq addEvent(EVENT, T, N, (EVTS ; T’ ; N’) :: EQ)
= ((EVENT ; T ; N) :: (EVTS ; T’ ; N’) :: EQ)
if T < T’ or (T == T’ and N < N’) .
--- add event to the first event:
eq addEvent(EVENT, T, N, (EVTS ; T ; N) :: EQ) = (EVENT EVTS ; T ; N) :: EQ .
--- add event to rest of queue:
ceq addEvent(EVENT, T, N, (EVTS ; T’ ; N’) :: EQ)
= ((EVTS ; T’ ; N’) :: addEvent(EVENT, T, N, EQ))
if (T’ < T) or (T’ == T and N’ < N) .
eq addEvent(EVENT, T, nil) = (EVENT ; T ; 0) .
The postfire function is defined on the superclass Actor to update port
variables, and to clear the store (so that the different stores do not add to the
state space during model checking):
eq postfire(Actor ,
< O : Actor | ports : PORTS, parameters : VAL, store : MEM >)
= < O : Actor | parameters : updateParam(VAL, PORTS), store : emptyMap > .
Delay. If a time delay actor has input in its ’input port, then it generates an
event with a delay equal to the current value of the ’delay parameter expression.
If this delay is 0.0, the microstep is 1, otherwise the microstep is 0:
eq postfire(Delay ,
< O : Delay | ports : < ’input : InPort | status : present, value : V >
< ’output : OutPort | >,
store : ’delay |-> TV ; MEM >)
< global : EventQueue | queue : EQ >
=
postfire(Actor , < O : Delay | >) .
< global : EventQueue | queue : addEvent(event(O ! ’output, V), toTime(TV),
if toTime(TV) == 0 then 1 else 0 fi, EQ) > .
Clock. When a clock actor is scheduled, that is, produces output, the postfire
function should schedule the next event, and update the index variable:
ceq postfire(Clock ,
< O : Clock | ports
store
index
< global : EventQueue
=
postfire(Actor , < O :
< global : EventQueue
:
:
:
|
< PI : OutPort | status : present > PORTS,
(’offsets |-> VO ; ’values |-> VV ; MEM),
NI >)
queue : EQ >
Clock | index : NI + 1 >)
| queue : addEvent(event(O ! PI, VV(#(s NI))),
TIME-TO-FIRE,
if TIME-TO-FIRE == 0 then 1 else 0 fi, EQ) >
if TIME-TO-FIRE := toTime((VO(#(s NI))) - (VO(# NI))) /\
((# NI + # 1) lessThan (VO .. ’length(()) )) == # true .
ceq postfire(Clock,
< O : Clock | ports
store
index
< global : EventQueue
=
postfire(Actor , < O :
< global : EventQueue
:
:
:
|
< PI : OutPort | status : present > PORTS,
(’period |-> VP ; ’offsets |-> VO ; ’values |-> VV ; MEM),
NI >)
queue : EQ >
Clock | index : 0 >)
| queue : addEvent(event(O ! PI, VV(# 0)),
TIME-TO-FIRE,
if TIME-TO-FIRE == 0 then 1 else 0 fi, EQ) >
if TIME-TO-FIRE := toTime((VP - (VO(# NI))) + (VO(# 0)))
/\ ((# NI + # 1) equals (VO .. ’length(()) )) == # true
/\ (VP lessThan Infinity) == # true .
Current time. The current time actor does not have a state that is changed,
except by the passage of time, and does not schedule later events. Therefore,
postfire does not affect CurrentTime objects:
eq postfire(CurrentTime, < O : CurrentTime | >) =
postfire(Actor, < O : CurrentTime | >) .
Ramp. The ramp actor updates its internal state by setting the output value to
the previous output value plus the current evaluation of the expression defining
the value of the ’step parameter (unless this is the first round, in which case
the output-value will be set to the current evaluation of the ’init expression).
prefire only changes the state if the actor actually was fired during the iteration
(one of the actor’s output ports have output a value):
eq postfire(Ramp,
< O : Ramp | store : ’init |-> VI ; ’step |-> VS,
firstStep : B, output-value : V,
ports : < PI : OutPort | status : present > PORTS >)
= postfire(Actor,
< O : Ramp | firstStep : false,
output-value : if B then VI else V + VS fi >) .
If the ramp actor was not enabled, another equation just does not update the
values.
Timer. If a timer actor received input from its input port, it generates an event
with value equal to the current value of the output parameter. The event is
scheduled for a time from now given by the value of the input port:
eq postfire(Timer,
< O : Timer | store : ’output |-> V ; MEM,
ports : < ’input : InPort | status : present, value : TV >
PORTS >)
< global : EventQueue | queue : EQ >
=
postfire(Actor, < O : Timer | >)
< global : EventQueue | queue : addEvent(event(O ! ’output, V), toTime(TV),
if toTime(TV) == 0 then 1 else 0 fi, EQ) > .
eq postfire(Timer,
< O : Timer | ports : < ’input : InPort | status : absent >
= postfire(Actor, < O : Timer | >) .
PORTS >)
Timed Plotter. At the end of an iteration, the timed plotter records any input
through its multi-input port by adding triple source: channel time: current
time value: value of input for each such input to its event-history attribute. This job is done by the auxiliary function genEventHistory which traverses each channel in the multi-input port and generates a “history triple” for
those channels where input were present:
eq postfire(TimedPlotter,
< O : TimedPlotter | current-time : T, event-history : EH,
ports : < PI : MultiInPort | channels : CHS > >)
= postfire(Actor,
< O : TimedPlotter | event-history : EH ++ genEventHistory(T, CHS) >) .
op genEventHistory : Time ChannelSet -> EventHistory .
eq genEventHistory(T, [EPI,present,V] ; CHS)
= (source: EPI time: T value: V) ++ genEventHistory(T, CHS) .
eq genEventHistory(T, CHS) = emptyHistory [owise] .
FSM Actors. An FSM actor does not generate future events, but postfire updates the internal state (location and variables) of the actor if it has gotten input.
eq postfire(FSM-Actor,
< O : FSM-Actor | parameters : VAL, currState : STATE, store : MEM >)
= postfire(Actor,
< O : FSM-Actor | parameters : updateVars(VAL, MEM),
currState : updateState(STATE, MEM) >) .
op
eq
=
eq
updateVars : AssignMap Memory -> AssignMap .
updateVars((VI |-> E) ; AM, (@set(VI) |-> V) ; MEM)
(VI |-> V) ; updateVars(AM, MEM) .
updateVars(AM, MEM) = AM [owise] .
op updateState : Location Memory -> Location .
eq updateState(STATE, @next(STATE’) ; MEM) = STATE’ .
eq updateState(STATE, MEM) = STATE [owise] .
Note that if no input was present, then updateVars and updateState do
nothing, since this @set and @next are only added to the store (in portFixPoints)
when some transition is taken.
Defining Timed Behavior. Finally, we must define the function delta, that
specifies the effect of time elapse, on single actors. Time only affects the internal
state of TimeActor objects (CurrentTime and TimedPlotter), that have an internal “clock” attribute current-time, by increasing the value of current-time
according to the elapsed time:
eq delta(< O : TimeActor | current-time : T >, T’) =
< O : TimeActor | current-time : T + T’ > .
Time elapse does not affect other actors and connections:
eq delta(O:Object, T) = O:Object [owise] .
Defining Initial Events. The initial state is defined as the term:
{init(< global : EventQueue | queue : nil >
connections
actor objects }
where init adds the initial events of the system to the global event queue. In
out flat subset, only Single and Clock actors generate such initial events:
op init : Configuration -> Configuration .
eq init(< O : SingleEvent | parameters : ’value |-> V1 ; ’time |-> V2 >
< global : EventQueue | queue : EQ > REST)
=
< O : SingleEvent | >
init(< global : EventQueue | queue :
addEvent(event(O ! ’output, V1), toTime(V2), 0, EQ) > REST) .
eq init(< O : Clock | parameters : ’value |-> V1 ; ’offsets |-> V2 >
< global : EventQueue | queue : EQ > REST)
=
< O : Clock | >
init(< global : EventQueue | queue :
addEvent(event(O ! ’output, V1(# 0)), toTime(V2(# 0)), 0, EQ) > REST) .
eq init(CF) = CF [owise] .
3.4
Extending the Semantics to Hierachical Ptolemy DE Models
Ptolemy DE models can be hierarchial because
1. modal models are state machines where each “state” is (or “refines to”) a
Ptolemy diagram, and
2. some actors are composite actors that are themselves Ptolemy diagrams.
This section briefly outlines how the above Real-Time Maude semantics has
been extended to hierarchical Ptolemy models. We refer to http://www.ifi.
uio.no/RealTimeMaude/Ptolemy for details.
Representing Hierarchical Models. Our Real-Time Maude representation
of hierarchical actors preserve the hierarchical structure of Ptolemy models.
Composite actors are modeled as object instances of the class CompositeActor,
which is a subclass of the class Actor. This class adds only one attribute, innerActors,
which denotes the inner actor objects and connections in the composite actor:
class CompositeActor | innerActors : Configuration .
subclass CompositeActor < Actor .
As mentioned in Section 2.2, Ptolemy II internally transforms a modal model
to a composite actor, consisting of the refinement actors of the states, and a
“controller” FSM corresponding to the “modal state machine”. We therefore
represent modal models in this way, as a CompositeActor containing the controller FSM, the refined actors, and the connections in the innerActors attribute,
and with an additional attribute controller pointing to the controller FSM in
innerActors, and with the additional refinementSet attribute mapping each
state in the modal model to its refinement:
class ModalModel | controller : Oid,
refinement : RefinementSet .
subclass ModalModel < CompositeActor .
In addition, the definition of the basic Actor class adds an attribute status
which can have the value enabled or disabled to reflect that any actor may
be disabled as a result of being contained in a refinement of a state in a modal
model which is “frozen.” The equations for prefire, portFixPoints, etc., only
apply to objects whose status is enabled.
In the hierarchical setting, each actor can be uniquely identified by its global
actor identifier, which is a list o1 . o2 . . . . . on of object names, where o1 is the
name a toplevel actor, and oi+1 (for i ≥ 1) is the (local) identifier of an actor
which is part of the innerActors :
sort ActorID .
subsort Oid < ActorID .
op _._ : ActorID ActorID -> ActorID [ctor assoc] .
Extracting and Adding Event to the Event Queue. In the flat setting,
each actor is at the same hierarchical level as the global EventQueue. Each actor
therefore has direct access to the event queue, so that at the start of an iteration,
the scheduled events could be directly inserted into the corresponding actor ports
(by the function addEventsToPorts), and actors could add generated events
directly into the global event queue (by postfire).
In the hierarchical case, an actor that receives or generates an event from/to
the global event queue can be located deep down in the actor hierarchy. Events
communicated between the actors and the global event queue may therefore cross
hierarchical boundaries. We have modeled this “traveling” of events by “method
calls” or “messages”. For example, inserting an event into the output port p of
some actor with global actor identifier g corresponds to generating the message
active-evt(event(g ! p), v). Likewise, an event communicated from actor to
the event queue has the form schedule-evt(event, time, microstep):
msg schedule-evt : Event Time Nat -> Msg .
msg active-evt : Event -> Msg .
For example, when an actor generates an event, it creates an schedule-evt
“message”:
eq postfire(Delay,
< O : Delay | status : enabled,
ports :
< ’input : InPort | status : present, value : V >
< ’output : OutPort | > PORTS,
store : ’delay |-> TV ; MEM >)
= schedule-evt(event(O ! ’output, V), toTime(TV),
if toTime(TV) == 0 then 1 else 0 fi)
postfire(Actor, < O : Delay | >) .
Such an event is propagated towards the top of the actor hierarchy by the following equation, in which a composite actor inside whose innerActors attribute
the schedule-evt message resides moves the message one level out:
eq < O : CompositeActor | innerActors : CF schedule-evt(event(AI ! PI, V), T, N) >
= < O : CompositeActor | innerActors : CF >
schedule-evt(event((O . AI) ! PI, V), T, N) .
Finally, when the schedule-evt request has been propagated to the top level
in the hierarchy, it is entered into the event queue:
eq < QUE : EventQUEUE | queue : EQ > schedule-evt(EVENT, T, N)
= < QUE : EventQUEUE | queue : addEvent(EVENT, T, N, EQ) > .
The propagation of active-evts from the event queue to some inner actor
is analoguous. It is worth remarking that careful use of sorts ensure that our
equations remain confluent.
Defining prefire, portFixPoints, and postfire for hierarchical models. The prefire function is unchanged in the hierarchical setting. For the new
classes, the prefire function propagates to the inner actors. For modal models, prefire also sets the status attribute of the inner actors according to the
current state of the controller:
eq prefire(CompositeActor, < O : CompositeActor | innerActors : CF >)
= prefire(Actor, < O : CompositeActor | innerActors : prefireAll(CF) >) .
eq prefire(ModalModel,
< O : ModalModel | controller : CO, refinement : REFS,
innerActors : ACTS
< CO : FSM-Actor | status : STAT,
currState : STATE > >)
= prefire(CompositeActor,
< O : ModalModel | innerActors : < CO : FSM-Actor | status : disabled >
setStateRefinement(STATE, REFS, ACTS) >) .
The controller is temporally disabled to allow the refinement actors to be
executed first according to Ptolemy semantics. The controller is set to be enabled
in portFixPoints.
postfire is modified so that disabled actor do not change their state or
generate new events. For a hierarchical actor, postfire just propagates to its
inner actors:
eq postfire(CompositeActor,
< O : CompositeActor | status : enabled, innerActors : CF >)
= postfire(Actor, < O : CompositeActor | innerActors : postfireAll(CF) >) .
Port fixpoints. The extension of the portFixPoints function to the hierarchical case is more subtle. The portFixPoints function should distribute to the
subdiagrams of composite actors, to compute the fixpoint of these subsystems.
However, an equation of the form
eq portFixPoints(< O : CompositeActor | innerActors : IA, ... > REST) =
portFixPoints(< O : CompositeActor | innerActors : portFixPoints(IA), ... > REST) .
would be applicable again when the inner portFixPoints function disappears,
leading to nontermination (and non-applicability of the owise equation defining
the end of the fixpoint computation). We therefore define the portFixPoints
function in two stages:
1. In the “initializing” stage, the portFixPoints function is propagated to subdiagrams of composite actors.
2. In the second stage, portFixPoints works similarly to the flat setting. portFixPoints
equations are applied to add information about the status of the ports. An
owise equation is again used to end the fixpoint computation when no equation adding new information about the ports can be applied. However, to
end the fixpoint computation of a (sub)system, the fixpoint computations of
the subsystems of composite actors must have finished. Therefore, this owise
equation should only be applied when there is no portFixPoints operator
in the innerActors of the CompositeActors in the system.
To separate the two stages, we add an additional parameter, which is either
init or inited, to portFixPoints:
op portFixPoints : InitStatus Configuration -> Configuration .
sort InitStatus .
ops init inited : -> InitStatus [ctor] .
When the first parameter of portFixPoints is init, the function should be
applied to the subconfigurations of composite actors:
eq portFixPoints(init , OBJECTS) =
portFixPoints(inited , computeInnerFixPoints(OBJECTS)) .
The function computeInnerFixPoints calls portFixPoints for all innerActors
in OBJECTS:
op computeInnerFixPoints : Configuration -> Configuration .
eq computeInnerFixPoints(< O : CompositeActors | innerActors : IA >
< O : CompositeActors | innerActors : portFixPoints(init ,IA) >
computeInnerFixPoints(REST) .
eq computeInnerFixPoints(REST) = REST [owise] .
REST) =
The “ordinary” equations for portFixPoints for flat systems are inherited
in the hierarchical case, with the modification that they only apply to inited
portFixPointss, such as in, e.g.:
eq portFixPoints(inited ,
< O : Delay | ports : < PI : OutPort | status : unknown > PORTS > REST)
= portFixPoints(inited,
< O : Delay | ports : < PI : OutPort | status : absent > PORTS > REST) .
We must then define the portFixPoints function for composite actors (including ModalModel actors). In particular, an input to the compsite actor will
lead to an input in one of its subactors, and an output at a subactor will lead to
an output from the containing composite actor. (We use the special nale ’parent’
to denote the containing actor of an actor in port names.) It could happen that
the inner fixpoint computation has finished when this information is added to
the out input port. In that case, the portFixPoints must again be called to (re-)
compute the fixpoint of the inner diagram:
ceq portFixPoints(inited ,
< O : CompositeActor |
ports : < P : InPort | status : PS, value : V > PORTS,
innerActors :
(parent ! P) ==> (O’ ! P’)
< O’ : Actor | ports : < P’ : InPort | status : unknown,
value : V’ > PORTS2 >
REST2 >
REST)
=
portFixPoints(inited,
< O : CompositeActor |
ports : < P : InPort | > PORTS,
innerActors : portFixPoints(inited,
(parent ! P) ==> (O’ ! P’)
< O’ : Actor | ports : < P’ : InPort | status : PS,
value : if PS == absent then V’ else V f
REST2)
REST)
if PS =/= unknown .
ceq portFixPoints(inited ,
< O : CompositeActor |
ports : < P : InPort | status : PS, value : V > PORTS,
innerActors : portFixPoints (inited,
(parent ! P) ==> (O’ ! P’)
< O’ : Actor | ports : < P’ : InPort | status : unknown,
value : V’ > PORTS2 >
REST2) >
REST)
=
portFixPoints(inited,
< O : CompositeActor |
ports : < P : InPort | > PORTS,
innerActors : portFixPoints(inited,
(parent ! P) ==> (O’ ! P’)
< O’ : Actor | ports : < P’ : InPort | status : PS,
value : if PS == absent then V’ else V f
REST2) >
REST)
if PS =/= unknown .
Likewise, an inner actor can generate output for the containing actor:
ceq portFixPoints(inited ,
< O : CompositeActor |
ports : < P : OutPort | status : unknown, value : V > PORTS,
innerActors :
(O’ ! P’) ==> (parent ! P)
< O’ : Actor | ports : < P’ : OutPort | status : PS,
value : V’ > PORTS2 >
REST2 >
REST)
=
portFixPoints(inited,
< O : CompositeActor |
ports : < P : OutPort | status : PS, value : if PS == present then V’ else V fi >
innerActors :
(O’ ! P’) ==> (parent ! P)
< O’ : Actor | ports : < P’ : OutPort | > PORTS2 >
REST2 >
REST)
if PS =/= unknown .
Finally, the computation of a subsystem is finished when the fixpoint computation of its subsystems is finished, and no more equations adding information
to the status of the ports can be performed:
ceq portFixPoints(inited, OBJECTS) = OBJECTS if innerFixpointsComputed(OBJECTS) [owise] .
op innerFixPointsComputed : Configuration -> Bool .
eq innerFixPointsComputed(
< O : CompositeActor | innerActors : portFixPoints(X, REST) > REST2) = false .
eq innerFixPointsComputed(REST) = true [owise] .
4
Formal Analysis of Ptolemy Models
In this section, we show that how a property of a Ptolemy model can be expressed
as user friendly way, and the property can be verified using Real-Time Maude
semantics.
4.1
Model checking in Real-Time Maude
As mentioned in Section 2.1, Real-Time Maude supports linear temporal logic
model checker. A general LTL formula is given by Real-Time Maude, and state
propositions should be defined for each model by an equation. Then, model checking command with an initial state and a formula returns a verification result. For
example, consider a safety condition of a traffic light system which means that
both a car light and a pedestrian light will be never green at the same time.
[] ~ (Car light is green /\ Pedestrian light is green )
In the above formula, Car light is green and Pedestrian light is green
are atomic propositions. To tell a model checker whether a given state pattern S(x1 , . . . , xn ) satisfies a parametrized atomic proposition P (x1 , . . . , xk ), it
is enough to define an equation S(x1 , . . . , xn ) |= P (x1 , . . . , xk ) = true. Note that
the operator |= is defined like the following:
sorts State Prop .
op _|=_ : State Prop -> Bool [frozen] .
For example, If the car light in the model are FSM-Actors, the state proposition could be defined like the following:
var CF : Configuration .
eq {< ’CarLight : FSM-Actor | currState : ’green > CF}
|= Car light is green = true .
eq {CF} |= Car light is green = false [owise] .
Pedestrian light is green can be defined by the same way, so that the model
checking command will be like the following if init is a term describing the given
ptolemy model:
(mc {init} |=u
[] ~ (Car light is green /\ Pedestrian light is green ) .)
4.2
Ptolemy property pattern in Real-Time Maude
This section defines a property specification pattern for supporting user-friendly
way to describe a property of Ptolemy model.
In general, to express a LTL property of a Ptolemy model, an user need to
define equations for basic blocks of properties, i.e., atomic propositions. It makes
a verification process much harder for usual Ptolemy users because users requires
to know Real-Time Maude and details of translated representation of the given
model.
The above difficulties can be avoid for most of situation, by defining a property
specification pattern for each class of actors. Since each actor can be uniquely
determined by an actor id, the pattern consists of an actor id and a actor-specific
patterns as follows.
sort ActorPattern .
op __ : ActorID ActorPattern -> Prop [ctor] .
Each interesting properties of Ptolemy models, such as parameters and states
of FSM-Actors, could define ActorPattern, respectively, and OO hierarchy of
actors enable to do it by minimal definition 10 .
Parameters of an Actor. A parameter of an Actor can represent a state of
specific point in most cases. A property pattern with an actor id O looks like
O | var1 = value1 , var2 = value2 , . . . , varn = valuen
which can be defined in Real-Time Maude like the following:
10
e.g., to define the pattern for parameters, we can consider only Actor class, since it
has parameters attributes and every other actor class is a subclass of it.
op |_ : VarCheckSet -> ActorPattern [ctor] .
sort VarCheck VarCheckSet .
subsort VarCheck < VarCheckSet .
op _=_ : VarId Exp -> VarCheck [ctor] .
op ‘(‘) : -> VarCheckSet [ctor] .
op _‘,_ : VarCheckSet VarCheckSet -> VarCheckSet
[ctor assoc comm id: ()] .
A VarCheckSet expresses a comma-separated equation set, where an empty set
is (). The equations for this pattern is defined by:
var CF : Configuration . var O : Oid . var I : VarId .
var E E’ : Exp . var VM : ValueMap . var VS : VarCheckSet .
ceq {< O : Actor
|= O | (I =
= {< O : Actor
if E equals E’
eq {< O : Actor
eq {< O : Actor
| parameters
E’, VS)
| parameters
== # true .
| parameters
| > CF} |= O
: (I |-> E) ; VM > CF}
: VM > CF} |= O | VS
: VM > CF} |= O | () = true .
| VS = false [owise] .
The equations means that a given parameter pattern is true (i) if it is empty, or
(ii) all the values in the pattern matches with the parameters in the actor.
States of FSM-Actor. FSM-Actors have its own state, and it is denoted by
current state attribute of FSM-Actor. A property pattern for a FSM-Actor F O
looks like
F O @ State
The pattern is true only if the current state of a FSM-Actor F O is State : Exp,
which is defined by:
var CF : Configuration .
var O : Oid .
var L : Location .
op @_ : Location -> ActorPattern [ctor] .
eq {< O : FSM-Actor | currState : L > CF} |= O @ L = true .
eq {< O : FSM-Actor | > CF} |= O @ L = false [owise] .
Inner Actors. We also need to access inner actors of an actor for verifying
hierarchical model of Ptolemy. It is sufficient to pass given atomic proposition to
inner actors for a CompositeActor, which is defined by the following.
var CF ACTS : Configuration . var O : Oid .
var AI AI’ : ActorID . var AP : ActorPattern .
eq < O : CompositeActor | innerActors : ACTS > CF |= (O . AI) AP
= ACTS |= AI AP .
Modal modal state. A top level of a ModalModel actor is the same with
FSM-Actor in the user-level, but the translation result is slightly different with
that: the top level FSM-Actor is also one of inner actors, and a controller
attribute denote that. Hence, a property pattern is exactly the same with the
FSM-Actor case, but it should be transfered to the controller FSM actor, so that
the equation for the pattern is defined by:
var ACTS CF : Configuration .
vars O CO : Oid . var L : Location .
eq < O : ModalModel | controller : CO,
innerActors : ACTS > CF |= O @ L
= ACTS |= CO @ L .
Scope. A formula for inner actors is simplified with the actor scope feature. In
the previous definition, to define a proposition for inner actors, we need to write
identifies for every ancestor. It results very long formula if we want to verify a
properties of inner actors. To avoid it, A scope operator is defined as follows:
op _:_ : ActorID Formula -> Formula .
The formula restricted by a scope operator is just a syntactic sugar of existing
formula. It will be automatically reduced to an usual formula by the following
rewriting equations.
var AI : ActorID . vars F1 F2 : Formula .
eq
eq
eq
eq
AI
AI
AI
AI
4.3
:
:
:
:
(~ F1) = ~ (AI : F1) .
(F1 /\ F2) = (AI : F1) /\ (AI : F2) .
(O F1) = O (AI : F1) .
(F1 U F2) = (AI : F1) U (AI : F2) .
Example
Consider a simple traffic light model of Ptolemy in Figure 2.
In the above model, each traffic light is represented by set of variables, e.g.,
Pred,Pgrn, Pred represents the pedestrian light, which are controlled by relating
FSM-Actors. In this model, the safety condition we are interested in is that
there is no case such that both a car light and a pedestrian light are green. It is
represented like the following by our property language.
(mc init |=u
[] ~ (’DE_SimpleTrafficLight | (’Pgrn = # 1, ’Cgrn = # 1)) .)
To check states of FSM-Actors directly, we can write:
(mc init |=u
[] ~ (’DE_SimpleTrafficLight . ’CarLightNormal @ ’Cgrn /\
’DE_SimpleTrafficLight . ’PedestrianLightNormal @ ’Pgreen)
.)
Using an actor scope, the above safety property can be simplified by:
(mc init |=u ’DE_SimpleTrafficLight :
[] ~ (’CarLightNormal @ ’Cgrn /\ ’PedestrianLightNormal @ ’Pgreen)
.)
Figure 2. Simple Traffic Light model of Ptolemy
5
Using Ptolemy’s Code Generation Infrastructure to
Integrate Real-Time Maude Analysis into Ptolemy
This section shows how Ptolemy’s helper code generation infrastructure has
been used to automatically generate Real-Time Maude code from a Ptolemy DE
model, and to integrate Real-Time Maude analysis of DE models into Ptolemy.
Section 5.1 gives a brief introduction to the code generation infrastructure of
Ptolemy, Section 5.2 shows how a Real-Time Maude model is generated from a
Ptolemy model, and Secton 5.3 briefly explains the Real-Time Maude interface
in Ptolemy.
5.1
The Code Generation Infrastructure of Ptolemy
Ptolemy gives the user the possibility of adding a “code generation button” to a
(top-level) Ptolemy model. When the user clicks on this button, Ptolemy opens
a dialogue window which allows the user to start code generation and give commands to execute the code.
Ptolemy provides a helper infrastructure to support the generation of code
into any target language. In particular, Ptolemy provides a Java class CodeGeneratorHelper. This class contains utility methods, such as the function
getComponent(), which returns a (Java) object containing all information about
an actor, including its name, parameters, ports, inner actors, etc. The class
CodeGeneratorHelper furthermore contains “skeleton” functions like String
generateFireCode(), String generateInitializeCode(), String generatePostfireCode(), Set getSharedCode(), and so on. For example, the generateFireCode() function should generate the code exeucted when the actor is “fired.”
The getSharedCode() function should generate code shared by multiple instances of the same actor. The point is that, for each kind of actor, we must define
a helper class that extends the class CodeGeneratorHelper and defines the above
functions generateFireCode(), etc. Each director (DE, SR, SDF, etc.) also has
an associated helper class, which uses the code generation infrastructure of each
actor to generate the entire code of a Ptolemy model.
Templates. A helper class may have a associated template file containing code
blocks written in a target language. By using template file, target language code
can be separated from a Java class file of a helper, so that readability and maintainability in the implementation of helper classes are increased. A code block
in a template files is not just fixed text, but a sort of code pattern. It can be
parameterized with several variables, and also have macro functions.
Each code block has a header and ends with /**/. The header starts with
/*** and ends with ***/, and contains a code block name and a comma-saperated
parameter list. Each parameter is prefixed by the dollar sign $.
Macros in a code block are prefixed with the dollar sign $symbol. The specific
macro name follows immediately. The parameters to the macro are enclosed in
parentheses. For example:
/***exampleBlock ($name, $value)***/
...
if ($val($name) == $value) then doSomething() else reportError();
...
/**/
During execution, $name and $value are substituted by given parameter values,
and $val($name) gives the current value of the parameter $name of an actor.
5.2
Implementing the Real-Time Maude Code Generator
The Real-Time Maude code generation is implemented by overriding two of
the helper functions described in Section 5.1. The function getSharedCode()
is used to generate the Real-Time Maude modules defining the semantics of
those actors that appear in the Ptolemy model, as well as the modules defining the infrastructure for LTL model checking of Ptolemy models. The function
generateFireCode() is used to generate the initial Real-Time Maude term corresponding to the given Ptolemy model. For each kind of actor, we therefore
define a Java class extending CodeGeneratorHelper, and define the above two
functions for that actor.
To give idea about the implementation of the Real-Time Maude code generator, we show how getSharedCode() and generateFireCode() have been implemented for the CurrentTime actor.
First of all, we have defined a Java class RTMaudeAdaptor that extends CodeGeneratorHelper, which is a base class for our code generator. The getSharedCode()
function is defined in this class as follows:
public Set getSharedCode() throws IllegalActionException {
// Use LinkedHashSet to give order to the shared code.
Set sharedCode = new LinkedHashSet();
semanticsIncludes = getModuleCode("semantics");
formalIncludes = getModuleCode("formal");
for (String m : semanticsIncludes)
sharedCode.add(getRTMmodule().get(m));
for (String m : formalIncludes)
sharedCode.add(getRTMmodule().get(m));
return sharedCode;
}
The auxiliary function getModuleCode(header ) reads Real-Time Maude code
blocks from the related templates to the helper class, whose code block name
starts with header . The getModuleCode function uses templates of each super
class of an actor.
In Ptolemy, each actor class is a subclass of the class Entity. Therefore, we
defined a helper class of Entity, extending RTMaudeAdaptor, that is a superclass
of every actor helper class. The semantics_Entity code block in the template
is:
/***semantics_Entity***/
(tomod ACTOR is
...
class Actor | ports : Configuration,
parameters : AssignMap,
status : ActorStatus,
store : Memory .
...
endtom)
/**/
For the CurrentTime actor, the semantics_CurrentTime is:
/***semantics_CurrentTime***/
(tomod CURRENT-TIME is
inc ACTOR .
inc OBJECT-WITH-TIMER .
class CurrentTime .
subclass CurrentTime < Actor TimeObj .
vars PORTS : Configuration .
vars PI PI’ : PortId . var DPS : DetPortStatus .
var O : Oid . var REST : ObjectConfiguration .
var T : Time .
eq portFixPoints(
< O : CurrentTime |
status : enabled,
current-time : T,
ports :
< PI : InPort |
< PI’ : OutPort
PORTS
> REST)
= portFixPoints(
< O : CurrentTime |
ports :
< PI : InPort |
< PI’ : OutPort
PORTS > REST) .
status : DPS >
| status : unknown >
>
| status : DPS, value : # T >
endtom)
/**/
For the CurrentTime actor, getModuleCode("semantics") returns a set of above
two code blocks.
The template file for Entity also contains the following code blocks:
/***fireBlock($attr_terms)***/
< ’$info(name) : $info(class) | $attr_terms >
/**/
/***attr_Entity***/
store : emptyMap,
status : enabled,
ports : ($indent($info(ports))),
parameters : ($indent($info(parameters)))
/**/
The fireBlock code block used by generateFireCode() to generate the
Real-Time Maude object corresponding to a Ptolemy actor instance. The parameter attr_terms will be replaced by set of attr Actor code blocks for each
Actor of super classes of a given actor.
$info is a macro that uses Ptolemy’s getComponent() to extract information,
such as the name, the class, etc., about the actor instance.
For CurrentTime, attr_CurrentTime is the following:
/***attr_CurrentTime***/
current-time : 0
/**/
The definition of generateFireCode() is not given (since it calls a set of
other auxiliary functions), but its effect is that it generates the object defined
in the fireBlock code block. For example, given a Ptolemy CurrentTime actor
with the name CT, the generateFireCode() function returns the term
< ’CT : CurrentTime | status : enabled,
current-time : 0,
store : emptyMap,
ports :
< ’output : OutPort | value : # 0, status : absent >
< ’trigger : InPort | value : # 0, status : absent >,
parameters : emptyMap >
5.3
Verifying Ptolemy DE Models from within Ptolemy
We have integrated not only the code generation, but also the verification into
Ptolemy, so that a Ptolemy DE model can be verified by pushing the RTMaudeCodeGenerator button in the Ptolemy model. The dialogue window that pops
up when the above button is double-clicked allows the user to write his or her
simulation or model checking command, as shown in Fig. 3. After clicking the
Generate button of the dialogue window, the resulting Real-Time Maude code
shows up in another window, and the result of executing the model checking
commands is shown in the Code Generator Commands dialogue box.
Figure 3. Dialogue window for Real Time Maude code generation
The resulting Real-Time Maude code generated from a Ptolemy model illustrated in Figure 4. It consists of 1) semantics modules, 2) formal analysis modules, 3) an initial model module, 4) verification commands. Semantics modules
and formal analysis modules 11 are generated by getSharedCode(). An initial
model module contains an OO term of a Ptolemy model, which are generated by
generateFireCode(). Each helper class for Real-Time Maude code generation
overrides the both method from CodeGeneratorHelper class. In addition, LTL
properties in a dialog window is used to generate verification commands.
Real-Time Maude code blocks are defined in the associated template file of
a helper class. getSharedCode() and generateFireCode() use the code blocks.
If the name of an actor is Actor, a semantics module is defined in a code block
semantics Actor , a formal analysis in a code block, formal Actor , the OO
11
described in Section 3.1 and Section 4, respectively.
******** include basic definitions ********
load ptolemy-base.maude
******** semantics modules ********
(tomod ACTOR is ... endtom)
(tomod COMPOSITE-ACTOR is ... endtom)
(tomod CLOCK is ... endtom)
(tomod DELAY is ... endtom)
(tomod FSM-ACTOR is ... endtom)
(tomod SET-VARIABLE is ... endtom)
******** formal analysis modules ********
(tomod CHECK-ACTOR is ... endtom)
(tomod CHECK-COMPOSITE-ACTOR is ... endtom)
(tomod CHECK-FSM-ACTOR is ... endtom)
******** Initial model modules ********
(tomod INIT is
including DE-DYNAMICS .
including ACTOR + ... + CLOCK .
...
op init : -> Configuration .
eq init = initAll(
*** OO term of a Ptolemy model ***
< ’Model : CompositeActor | innerActors : ... >
) < global EventQUEUE | queue : nil > .
.)
(tomod PTOLEMY-MODELCHECK is
including INIT + ... + CHECK-FSM-ACTOR .
endtom)
******** verification commands ********
(mc init |=u [] ~ (’Model | (’Pgrn = # 1, ’Cgrn = # 1)) .)
(mc init |=u <> (’Model | (’Pgrn = # 1, ’Cgrn = # 0)) .)
quit
Figure 4. Structure of a resulting code
term of an actor in a parameterized code block fireBlock($attr terms), and
attributes of the OO term in attr Class 12 .
To illustrate more precisely, consider the helper class of Entity class, which
generates code for a general Real-Time Maude Actor and other helpers of Ptolemy
actors extend it. Figure 5 shows contents of Entity.rtmaude. Note that $info
and $indent are template functions to be substituted to suitable Real-Time
maude code during translation. Helper classes of actors extends Entity helper
/***semantics_Entity***/
(tomod ACTOR is
...
class Actor | ports : Configuration,
parameters : AssignMap,
status : ActorStatus,
store : Memory .
...
endtom)
/**/
/***formal_Entity***/
(tomod CHECK-ACTOR is
...
endtom)
/**/
/***fireBlock($attr_terms)***/
< ’$info(name) : $block(className) |
$attr_terms
>/**/
/***attr_Actor***/
store : emptyMap,
status : $block(statusBlock),
ports : ($indent(
$info(ports)
)),
parameters : ($indent(
$info(parameters)
))/**/
Figure 5. Contents of the template file Entity.rtmaude
class. Since code blocks of super classes are automatically included for each helper
class, the code of general actor is generated for any actor.
12
the suffix Class for a code block name is required since the current codegen infrastructure does not support call to superclass code blocks that have been overridden.
6
Examples
This section presents three Ptolemy II discrete-event models and shows how
they can be analyzed in Real-Time Maude from within Ptolemy. Section ??
presents the benchmark railroad crossing example, Section ?? presents a hierarchical model of a fault-tolerant traffic light system, and Section ?? presents an
assembly line due to Misra.
6.1
Railroad System
In the benchmark railroad crossing example, trains approach an intersection,
and it is the goal of the system that the gates at the intersection are lowered
(down??) when a train is in the intersection. Figure 6 shows a Ptolemy II DE
model RailroadSystem of such a system. This model consists of two finite state
machine (FSM) actors: a Train actor that models trains, and a Gate actor that
controls the gate. In addition, the model has a set of “boolean” variables Tin
(which is 1 when a train is in the intersection), Tleave (which is 1 when a train
is leaving the environment), Tapproaching, and Gopen (which is 1 when the gate
is open). State changes are triggered by a Clock actor. These variables are set
by signals from the output ports of the train and the gate controller.
Figure 6. Ptolemy DE model of the railroad crossing.
Do we have/need a
reference to Misra’s
example??
Figure 7 shows the Train FSM actor modeling trains. This actor has five
states, or locations, and a local variables distance, denoting the distance between
the train and the beginning of the intersection. The Train has one input port,
Sec, and three output ports (Tin, Tleave, and Tapproaching). Initially, the
state is in location Tinit; in the first step, a new train is arriving, but is yet
far away, at a distance −10. The FSM actor stays in location far as long as the
distance < -3. The value of distance increases by 2 each time there is input in
the Sec port (that is, each time unit in our case) as long as the train is state far.
When distance has reached −3, and a new Sec signal arrives, the train takes
a transition to location approaching, where it stays until the distance reaches
0. At the same time, it outputs a signal with value 1 through its Tapproaching
output port. A train that is approaching the intersection slows down; therefore,
the distance only increases by one for each time unit in location approaching (as
well as in locations within and leaving). When the distance to the intersection
is 0, the actor goes to state within, and emits a signal through its Tin port. When
the distance is greater than or equal to 3, the train is leaving the intersection,
and an output is emitted through the Tleave port. Finally, when the distance
becomes greater than or equal to 10, the train disappears and a signal with value
0 is output through all three output ports. The actor goes to location far and
the next train is seen in the horizon, and the distance is set to -10.
Figure 7. The FSM actor modeling trains.
Figure 8 shows the FSM actor controlling the gate. This actor responds to
input from the Train actor through its Tapproaching, Tin, and Tleave input
ports by the necessary signal through its Gopen output port.
Figure 8. The FSM actor modeling the gate controller.
Kyungmin, Real-Time Maude code corresponding to the initial
state!
Formal Analysis. The main property the system RailroadSystem must satisfy
is the safety property that whenever a train is in the intersection, the gate must
be closed. In our model, a train is in the intersection when the ’Train actor in
the ’RailroadSystem main component is in location ’within, then the ’Gate
actor is in location ’closed.
Using the convenient propositions defined in Section ??, the atomic proposition (’RailroadSystem . ’Train @ ’within holds for states in which the ’Train
actor is in location within, and the proposition (’RailroadSystem . ’Gate @
’closed holds when the Gate is in location closed. We want to check whether
it is always the case that the former implies the latter. In temporal logic, this
can be given by the formula:
[] ((’RailroadSystem . ’Train @ ’within)
-> (’RailroadSystem . ’Gate @ ’closed))
Giving this command in the dialogue box that pops up when the user double
clocks the “RTMaudeCodeGenerator” button gives the expected result true,
proving that the desired property is satisfied in this Ptolemy model. Notice that
the railroad system is finite state, hence we do need to perform time-bounded
LTL model checking.
The following model checking command checks whether it is always the case
that the Train actor will reach the state within within 7 time units from the
start of system execution:
<>( ’RailroadSystem . ’Train @ ’within) in time <= 7
6.2
Hierarchical Traffic Light
The following hierarchical Ptolemy model specifies a simple fault-tolerant traffic
light system at a pedestrian crossing. The system consist of one car light and one
pedestrian light. Every 20 time units, a device fails and remains failed for 5 time
units.
Figure 9 gives an overview of the system: the FSM actor Decision “generates”
errors and repairs; TrafficLight is a modal model handling the two lights; and
Pred, Pgrn, Cred, Cyel, and Cgrn are variables that denote the current color(s)
(if any) of, respectively, the pedestrian light and the car light. For example, if
Pgrn is 1, then the pedestrian light shows green. The TrafficLight is a modal
model with two modes: normal operating mode and an error mode that handles
the light during device failure.
Figure 9. A hierarchical fault-tolerant traffic light system.
The Decision actor in Fig. 10 is an FSM actor that “generates” failures and
repairs by alternating between staying in location Normal for 15 time units and
staying in location for Abnormal for 5 time units. Whenever the actor takes a
transition with target Normal, it sends a signal through its Ok port, and whenever
it reaches, or stays in, location Abnormal, the actor sends a signal through its
Error port.
The TrafficLight actor shown in Fig. 11 is a modal model with two modes,
normal and error. Whenever the actor is in error mode and receives a signal
through its Ok port, the actor goes to normal mode, and vice versa when it
receives an Error event in normal mode.
Figure 10. The Decision actor.
Figure 11. The TrafficLight modal model.
The FSM actor that refines the error mode of TrafficLight is shown in
Fig. 12. In this mode, all lights are turned off (by sending a value 0 through the
corresponding port), except for the yellow light of the car light, which is blinking,
that is, alternates between showing yellow for one time unit and turning off the
yellow for one time unit.
Figure 12. The refinement of error mode in TrafficLight.
The refinement of the normal mode in TrafficLight is the composite actor
shown in Fig. 13. It consists of the two FSM actors CarLightNormal, given in
Fig. 14, and PedestrianLightNormal, given in Fig. 15, that define the behavior
of the two lights during normal operations.
Analyzing the traffic light system. The main safety property we are interested in is that in the model (the entire model is called HierarchicalTrafficLight),
it is never the case that both the car light and the pedestrian show green at the
same time. That is, it must never be the case that the variable Pgrn and the
variable Cgrn are both 1 at the same time. The following temporal logic formula
states exactly that it is never the case ([] ~) that the variables are both 1:
[] ~ (’HierarchicalTrafficLight | (’Pgrn = # 1, ’Cgrn = # 1) )
We can also check the liveness property that both pedestrian and cars can
cross the crossing infinitely often. That is, it is infinitely often the case the pedestrian light is green when the car light is not green, and it also infinitely often the
case the car light is green when the pedestrian light is not green:
[]<> (’HierarchicalTrafficLight | (’Pgrn = # 1, ’Cgrn = # 0)) /\
[]<> (’HierarchicalTrafficLight | (’Pgrn = # 0, ’Cgrn = # 1))
Figure 13. The refinement of the normal mode of TrafficLight.
Figure 14. The FSm actor CarLightNormal defining the operation of the car light
controller in normal mode.
Figure 15. The FSM actor PedestrianLightNormal defining the operation of the
pedestrian light controller in normal mode.
’HierarchicalTrafficLight : ([] ~ (’TrafficLight @ ’normal /\
’TrafficLight . ’normal :
(’CarLightNormal @ ’Cgrn /\ ’PedestrianLightNormal @ ’Pgreen)))
6.3
Reference, please!
Assembly Line
Finally, we have analyzed in Real-Time Maude the “assembly line” example of
Misra given in Fig. 16. Here, an “advanced” clock Jobs generates a set of jobs
at certain times. The timed plotter JobArrivedTime records the actual times
(obtained through the CurrentTime actor) when the jobs arrived.
Figure 16. The assembly line example.
Each job has to be executed in three different ways (at Station1, Station2,
and Station3). First, a job gets assigned the time it takes to execute the first
task of the job. This is done by the Ramp actor ServiceTimes1. The actual
“wait” is first done at the noninterruptible timer Station1. The point of using a
noninterruptible timer is that the count down does not start if some other job is
serviced. This can be compared to a gas station. It takes so and so long to fill up
the gas tank of your car, but if someelse is alsready pumping gas, you must also
wait for that car to stop pumping and to drive away. Anyways, after finishing
the part of the task, the job is then assigned a duration of the second part in
the ramp ServiceTimes2, and waits accordingly at the noninterrruptible timer
Station2. Finally, when that wait is over, the process repeats for the third part
of the task.
The timed plotter StationsFinishedTimes records the times when jobs finish
executing the first, the second, and the third “part” of the jobs.
To simulate the system in Real-Time Maude, we can give the following simulation command, which simnulates the system up to time XXX, in the dialogue
box:
Kyungmin: can you give the exact simulation command!!
The output shows the final state, where the ’StationsFinsihedTimes object
shows the times when events happended at the different ports:
Kyungmin: can you provide the resulting state here!!!
For example, we see that MUST BE COMPLETED!!
These results are the same as the results shown in the Ptolemy timed plotters
after the Ptolemy executions.
7
Related Work
A preliminary exploration of translations of synchronous reactive (i.e., untimed)
Ptolemy II models into Kripke structures, that can be analyzed by the NuSMV
model checker, and of DE models into communicating timed automata is described in [17]. However, they require data abstraction to map models into finitary automata. They also do not use the adapter code generation framework.
On the other hand, Maude has been used to give semantics to a wide range of
programming and modeling languages (see, e.g., [18,19]). Real-Time Maude is
also used to analyze AADL [20] models of avionics embedded systems, but we
are not aware of any translation of a synchronous real-time language into Maude
or Real-Time Maude.
8
Concluding Remarks
We have formalized the semantics of a significant subset of transparent hierarchical Ptolemy II DE models in Real-Time Maude, and have shown how such
Ptolemy design models can be verified by integrating Real-Time Maude code generation and model checking into Ptolemy, enabling a model-engineering process
for embedded systems that leverages the convenience of Ptolemy II DE modeling
and simulation with the formal verification of Real-Time Maude.
This work should continue in different directions. We should cover larger
subsets of Ptolemy II and verify larger and more sophisticated applications than
the three Ptolemy applications we have analyzed so far. We should also add other
relevant analysis methods, such as, e.g., statistical model checking to analyze
probabilistic Ptolemy II models. Finally, counterexamples from Real-Time Maude
verification should be visualized in Ptolemy II.
Acknowledgments. This work is part of the Lockheed Martin NAOMI project.
We thank the members of the NAOMI project for enabling and encouraging this research, and for fruitful discussions. We are also grateful to Christopher Brooks, Chihhong Patrick Cheng, Edward A. Lee, and Man-Kit Leung for discussing different aspects
of Ptolemy II with us, and to Edward A. Lee for helpful suggestions to drafts of this paper. Finally, we gratefully acknowledge financial support by Lockheed Martin through
the NAOMI project and by The Research Council of Norway.
References
1. Sztipanovits, J., Karsai, G.: Model-integrated computing. IEEE Computer (1997)
110–112
2. Giese, H., Karsai, G., Lee, E., Rumpe, B., Schtz, B., eds.: Model-based Engineering of Embedded Real-time Systems. Dagstuhl Seminar Proceedings 07451, Internationales Begegnungs- und Forschungszentrum fur Informatik (IBFI), Schloss
Dagstuhl, Germany (2007)
3. Eker, J., Janneck, J.W., Lee, E.A., Liu, J., Liu, X., Ludvig, J., Neuendorffer, S.,
Sachs, S., Xiong, Y.: Taming heterogeneity—the Ptolemy approach. Proceedings
of the IEEE 91 (2003) 127–144
4. Fishman, G.S.: Discrete-Event Simulation: Modeling, Programming, and Analysis.
Springer-Verlag (2001)
5. Zeigler, B.P., Praehofer, H., Kim, T.G.: Theory of Modeling and Simulation. 2nd
edn. Academic Press (2000)
6. Zhao, Y., Lee, E.A., Liu, J.: A programming model for time-synchronized distributed real-time systems. In: Real-Time and Embedded Technology and Applications Symposium (RTAS), Bellevue, WA, USA, IEEE (2007)
http://ptolemy.eecs.berkeley.edu/publications/papers/07/RTAS/.
7. Lee, E.A., Zheng, H.: Leveraging synchronous language principles for heterogeneous
modeling and design of embedded systems. In: EMSOFT, Salzburg, Austria, ACM
(2007)
8. Ölveczky, P.C., Meseguer, J.: The Real-Time Maude tool. In Ramakrishnan, C.R.,
Rehof, J., eds.: Tools and Algorithms for the Construction and Analysis of Systems
(TACAS’08). Volume 4963 of Lecture Notes in Computer Science., Springer (2008)
332–336
9. Ölveczky, P.C., Meseguer, J.: Semantics and pragmatics of Real-Time Maude.
Higher-Order and Symbolic Computation 20 (2007) 161–196
10. Clavel, M., Durán, F., Eker, S., Lincoln, P., Mart-Oliet, N., Meseguer, J., Talcott,
C.: All About Maude - A High-Performance Logical Framework. Volume 4350 of
Lecture Notes in Computer Science. Springer (2007)
11. Zhou, G., Leung, M.K., Lee, E.A.: A code generation framework for actor-oriented
models with partial evaluation. In: International Conference on Embedded Software
12.
13.
14.
15.
16.
17.
18.
19.
20.
and Systems (ICESS). Volume LNCS 4523., Daegu, Korea, Springer-Verlag (2007)
786–799
Sen, K., Viswanathan, M., Agha, G.A.: VeStA: A statistical model-checker and
analyzer for probabilistic systems. In: QEST’05, IEEE Computer Society (2005)
251–252
Denton, T., Jones, E., Srinivasan, S., Owens, K., Buskens, R.W.: NAOMI – an
experimental platform for multi-modeling. In: MoDELS ’08: Proceedings of the 11th
International Conference on Model Driven Engineering Languages and Systems,
Toulouse, France (2008) 143–157
Ölveczky, P.C., Meseguer, J.: Specification of real-time and hybrid systems in
rewriting logic. Theoretical Computer Science 285 (2002) 359–405
Lee, E., Sangiovanni-Vincentelli, A.: A unified framework for comparing models of
computation. IEEE Trans. on Computer Aided Design of Integrated Circuits and
Systems 17 (1998) 1217–1229
Edwards, S., Lee, E.: The semantics and execution of a synchronous block-diagram
language. Science of Computer Programming 48 (July 2003) 21–42(22)
Cheng, C.P., Fristoe, T., Lee, E.A.: Applied verification: The ptolemy approach.
Technical Report UCB/EECS-2008-41, EECS Department, University of California,
Berkeley (2008)
Farzan, A., Chen, F., Meseguer, J., Rosu, G.: Formal analysis of Java programs
in JavaFAN. In Alur, R., Peled, D., eds.: CAV. Volume 3114 of Lecture Notes in
Computer Science., Springer (2004) 501–505
Meseguer, J., Rosu, G.: The rewriting logic semantics project. Theoretical Computer Science 373 (2007) 213–237
SAE: AADL (2007) http://www.aadl.info/.