Sinelabore Homepage

Modeling Guide for Simulation and Testing#

SysML v2 textual models can be turned into executable C++ code within minutes — no UML tool required. This guide explains how to structure your model so that Sinelabore generates code you can simulate on your PC and use as a basis for testing state-based behavior.

The focus here is on state machines embedded in parts, executable actions (assign, control flow, loops), their interaction via ports, and the execution loop needed to run a model. For element-level syntax details see the sub-pages (Parts, Items, States, Ports, …). For the full traffic-light walkthrough see the GitHub example.

What you can do today#

Goal Supported How
Simulate state machines on a PC Yes Generate C++, call state machine methods in a loop
Model interacting parts Yes Parts, ports, connections
Timed transitions Yes accept after … [SI::second]
Guarded transitions Yes accept when condition or if condition then
Executable action behavior Yes assign, sequencing (first/then), if/else, decide, loop/while/for — see Actions
Hardware / platform hooks Optional Empty action def + C++ subclass override when I/O is not modeled in SysML
Inject test stimuli via ports Yes Send events/data from one part to another
Automatic test route generation UML only (so far) See Model-Based Testing
Trace / live visualization UML only (so far) Same as above; SysML v2 trace support may follow

SysML v2 is ideal for early executable models: validate state logic, timing, and part interaction before committing to target hardware or a full UML toolchain.

Decision Guide#

flowchart TD
    Start([New SysML v2 model]) --> Single{Single part<br/>or system of parts?}
    Single -->|One part| SM[Part with state machine]
    Single -->|Multiple parts| SYS[System part composes sub-parts]
    SYS --> Connect[Connect ports between parts]
    SM --> Actions{Need behavior<br/>in states or parts?}
    Connect --> Actions
    Actions -->|Logic and data| ActModel[Model with action syntax<br/>assign, if/else, loops, decide]
    Actions -->|Port I/O| Port[Define in/out ports<br/>accept / send in actions]
    Actions -->|Hardware I/O| HW[Empty action def<br/>override in C++ subclass]
    Actions -->|Timed sequences| Time[Use accept after transitions]
    ActModel --> Run[Generate code<br/>run simulation loop]
    Port --> Run
    HW --> Run
    Time --> Run
    Run --> Test{Testing goal?}
    Test -->|Explore behavior| Sim[Manual simulation<br/>observe state changes]
    Test -->|Repeatable checks| Auto[Script stimuli in main<br/>assert on attributes]

1. Model Structure Patterns#

Every executable model follows the same skeleton. Keep this structure in mind before adding details.

One file or several#

A model may live in one file with several packages, or be split across files. Each package maps to a C++ namespace.

private import ScalarValues::*;

package Types {
    part def Sensor { attribute reading : Real = 1.0; }
}

package MySystem {
    private import Types::*;
    // enums, items, ports, parts, connections — and imported types
}

If an imported package is not in the current file, it is loaded from a sibling file next to the primary one, and its imports are followed in turn. Generate the primary file; the rest is pulled in. Keep the files of one model in one directory. See Packages.

Part with state machine#

State machines can only be attached to parts. Write the machine as a state usagestate controllerSm { … } — so that the attributes, ports and actions of the part stay accessible from inside it. A state definition is owned by the part but not featured by it, and standards-conforming tools reject feature access from within one.

Each part with a state machine gets a generated method named after the machine:

part def Controller {
    attribute msg : DeviceEvent;

    state controllerSm {
        entry; then Idle;
        state Idle;
        accept after 1[SI::second] then Active;
        state Active;
        accept when msg == DeviceEvent::evStop then Idle;
    }
}

System composition#

To simulate interaction between components, define a system part that owns sub-parts and connects their ports:

part def MySystem {
    part master : MasterController;
    part slave  : DeviceController;
    connect master.sendPort to slave.recvPort;
}

Sub-parts must be initialized in code after construction — call init() on the system part, then initialize() on each state machine. See Parts.

Event definitions#

Define events as enums.

enum def DeviceEvent {
    evStart;
    evStop;
    evError;
}

See Enumerations.


2. Action Modeling Patterns#

Actions are first-class executable behavior in SysML v2 — not just placeholders. The code generator translates action bodies into C++ using a context object that gives access to part attributes, parameters, and nested action results. Model your logic here whenever possible; reserve empty action def bodies only for hardware or platform code you intentionally keep in C++.

Construct Purpose Example
assign Update attributes, pass data assign msg := m.msg;
Sequencing Ordered steps first start; then act1; then act2; then done;
Action invocation Reuse behavior then action act2:ParentWithPara;
Inline action Local one-off steps then action { assign x := n; }
decide / guarded branches Conditional flow then decide; if n==1 then Path1; else done;
if / else if / else Structured conditionals then if batLevel<4 { … } else { … }
loop / until Repeat until condition loop action charging { … } until chargeStatus <= 100;
while Pre-checked iteration while whileGuard <= 5 { assign whileGuard := whileGuard + 1; }
forin Iterate over collections for anA in a { assign anA.n := 34; }
Parameters Typed inputs/outputs in x:Real; out p:Real; assign p := x*x;
Port interaction Receive or send via ports action accept m:PortData via recvPort { assign msg := m.msg; }
perform Trigger behavior of another part then perform redLamp.setOn;

Actions can be referenced from state machines (entry …, exit …, do action …) and from other actions. Nested action usages are mapped to executable C++ code.

Full syntax and generated C++ examples: Actions.

Driving other parts#

When a part owns another part, model the behavior where it belongs — on the owned part — and trigger it with perform. This keeps each part testable on its own:

part def Lamp {
    attribute switchCounter : Natural default 0;
    action setOn  { assign switchCounter := switchCounter + 1; }
    action setOff { assign switchCounter := switchCounter + 1; }
}

part def Controller {
    ref part redLamp : Lamp;

    action setRed { first start; then perform redLamp.setOn; then done; }
}

The counter inside Lamp is then a natural assertion point for a test: it records how often the lamp was actually switched, without any hand-written C++.

When to use empty action defs#

Use an empty action def only as a marker for hardware or OS services that you do not want to model in SysML — for example setRedLED{} driving a GPIO pin. All other behavior (calculations, guards, port handling, control flow) belongs in the action body.

An empty action generates an empty operator(), so nothing happens at run time. To attach platform code, set a part attribute from the model and act on it in an overridden process() — see Extending with custom C++ behavior.

/* Modeled in SysML — generated C++ runs directly */
action def computeThreshold {
    in level:Integer;
    out ok:Boolean;
    assign ok := level >= 10 and level <= 90;
}

/* Hook for target hardware — implement in C++ subclass */
action def setRedLED {}

3. State Machine Modeling Patterns#

SysML v2 state machines are less expressive than UML state charts but well suited for simulation. These patterns produce clean, readable generated code.

Pattern Purpose Example
Entry → default state Define startup state entry; then Idle;
Short-form timed transition Blinking, timeouts, periodic behavior accept after 0.5[SI::second] then Off;
Long-form transition Named transition with explicit source transition t1 first Red accept after 2[SI::second] then Green;
Guard on attribute React to model data, not just time accept when msg==DeviceEvent::evStart then Running;
Entry / exit actions One-time setup/teardown per state entry setRed; performs an existing action; entry action setRedLED; declares a new empty one
Do activity Poll ports or run logic each cycle do action getMsg;
Hierarchical states Group related states (e.g. OutOfService, Operational) Nested state Operational { … }
Parallel (orthogonal) regions Concurrent regions in one state machine state sm parallel { state RA { … } state RB { … } } — see States
History (naming convention) Return to last substate Shallow: S1_H, deep: S1_HH
Final state Non-reactive end state Done; with no outgoing transitions

Full traffic-light example: States.

Timed transitions#

Time values are specified in seconds only:

state deviceSm {
    entry; then Starting;

    state Starting;
    accept after 2[SI::second] then Operational;      /* 2 s   */

    state Operational;
    accept after 0.001[SI::second] then Next;         /* 1 ms  */

    state Next;
}

The code generator emits timer management code. You must call the state machine method cyclically in your simulation loop (see below).

Reading data from ports in states#

A common pattern for testable models: a do action reads incoming port data into a part attribute; transitions use guards on that attribute:

action getMsg {
    action accept m:PortData via recvPort {
        assign msg := m.msg;
    }
}

state deviceSm {
    entry; then Idle;
    do action getMsg;

    state Idle;
    transition t1 first Idle accept when msg==DeviceEvent::evStart then Running;
    state Running;
}

A transition may only name states that are reachable from where it is written. A transition between two sibling states belongs in their enclosing state, not inside one of them.


4. Communication Patterns#

Parts interact through ports and connections. This is the primary mechanism for injecting test stimuli during simulation.

Pattern When to use Modeling
Unidirectional command One controller, one or more devices out portin port, connect in system part
Event + data payload Send typed messages Define item or port with attributes
Bidirectional Request/response Two port pairs in opposite directions
Timed stimulus Auto-start simulation Controller part with accept after sends via port

Example:

item def PortData {
    attribute msg : DeviceEvent;
}

port def ControlPortData {
    in item data : PortData;
}

part def Master {
    out port sendPort : ControlPortData;

    state masterSm {
        entry; then Starting;
        state Starting;
        accept after 2[SI::second] do send DeviceEvent::evStart via sendPort then Done;
        state Done;
    }
}

part def Device {
    attribute msg : DeviceEvent;
    in port recvPort : ~ControlPortData;

    action getMsg {
        action accept m : PortData via recvPort {
            assign msg := m.msg;
        }
    }

    state deviceSm {
        entry; then Idle;
        do action getMsg;

        state Idle;
        transition t1 first Idle accept when msg == DeviceEvent::evStart then Running;
        state Running;
    }
}

Transitions live inside a state machine, never directly in the part body — the accept line above belongs to masterSm.

See Ports.


5. Simulation Pattern#

Generated models are intended to run in a simple C++ main function. No RTOS or target hardware is required.

Code generation#

java -cp path_to_bin_folder/* codegen.Main -p Sysml2Text -l cppx your_model.sysml

Set these parameters in codegen.cfg (required for SysML v2 C++ output):

UseEnumBaseTypes=yes
UseStdLibrary=yes
CallInitializeInCtor=no
EnumBaseTypeForEvents=std::int16_t

Execution loop#

Each part with a state machine exposes a method named after the machine. Call these cyclically:

using namespace MySystem;

int main() {
    MySystem system;
    system.init();

    system.master->initialize();
    system.device->initialize();

    for (int i = 0; i < 100; i++) {
        system.master->controllerSm();
        system.device->deviceSm();
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
    return 0;
}

The sleep interval defines the simulation tick. Choose it smaller than your shortest timeout for reliable timed transitions.

init() on the system part is required — it constructs the sub-parts. The explicit initialize() calls are optional: each generated state machine method initializes itself on its first call. Keep them if you want the startup point to be visible in the code.

Extending with custom C++ behavior#

Most behavior should live in the SysML model and is generated automatically. Subclass the generated part only when you need platform-specific code that cannot or should not be expressed in SysML — typically hardware I/O, OS calls, or test assertions on external interfaces.

The generated part class offers these overridable methods:

Method Purpose
virtual void init(void) Construct sub-parts and wire references. Call the base-class init() from your override
virtual void process() Runs once per cycle for the part and its sub-parts
virtual void handle(const XxxDef& e) Called for data arriving on an in port of type Xxx
class DeviceSim : public Device {
public:
    void init(void) override {
        Device::init();          // let the generated code build sub-parts first
        // additional wiring for the simulation
    }

    void process() override {
        Device::process();
        std::cout << "counter = " << someAttribute << "\n";
    }
};

Actions are not virtual methods of the part. An action def X {} is generated as a struct XDef with a virtual void operator()(Part&), so void X() override in a subclass does not hook into anything — it simply adds an unrelated method. For platform code, put the call into an overridden process() or handle(), or drive it from a part attribute that the model sets with assign.

See States — Executing a model.


6. Testing Patterns#

SysML v2 simulation is the foundation for testing state-based models. These approaches work well with the current backend.

Manual exploratory testing#

Run the generated executable and observe attribute changes, port traffic, and state sequences. Because action logic is generated from the model, most behavior is visible without writing C++ — add std::cout in subclass overrides only for hardware hooks.

Scripted stimulus testing#

Drive the model from main with a fixed sequence of port messages or timed runs:

// After N ticks, inject an error event via the master part
if (i == 50) {
    system.master->sendError();
}

Check part attributes (updated by assign in actions) or subclass overrides for hardware hooks to verify expected behavior.

Incremental modeling pattern#

  1. Model control logic, calculations, and port handling directly in SysML actions.
  2. Run the simulation and verify state transitions, guards, and attribute updates.
  3. Add empty action defs only for hardware boundaries; implement those in a C++ subclass when moving toward target code.

Validating the model itself#

Before testing behavior, make sure the model says what you think it says. The generator reports problems with stable codes — see Diagnostics — and a warning like W3117 or W3115 usually points at a modelling mistake, not at a tooling quirk.

For a second opinion, run the model through the SysML v2 reference implementation. It is stricter than any code generator and catches errors that still produce compilable C++, such as feature access from inside a state def. See Tooling and Validation.

Transition coverage (UML workflow)#

Automatic test route generation, Excel test sheets, and live trace visualization are available for UML state charts today. See Model-Based Testing. If you need these features, model in UML; if you need fast textual iteration, use SysML v2 simulation and port-based scripting.


7. Supported Features (Summary)#

Category Supported
Package / namespace Yes
Imports (membership / wildcard, same file) Yes — see Packages
Multi-file models Yes — sibling files, resolved recursively
Standard library Stubs for ScalarValues and ISQ only
Parts, nested parts Yes
Part / attribute / item multiplicity Yes
Attributes, enums Yes
Units on attribute values Yes — normalized, never converted; see Attributes
Default SM event enum (no @ annotation) Yes
Items Yes — see Items
ref / abstract / subsets / :>> Partial
Collections (abstract ref … [*] + subsets) Yes — see Parts
Ports (in/out), connections Yes
State machines in parts (usage or definition) Yes
Entry, exit, do actions (reference or declare) Yes
perform an action of another part Yes — see Actions
Diagnostics with stable codes Yes — see Diagnostics
Timed transitions (after) Yes (seconds)
Guarded transitions (when, if) Yes
Parallel (orthogonal) regions Yes — see States
History states (naming convention) Yes
Final states Yes
Actions — assign, sequencing, parameters Yes
Actions — decide, if/else if/else Yes
Actions — loop/until, while, for Yes
Actions — nested usages, port accept/send Yes
at time transitions No
Inner transitions No

Full limitation list: SysML v2 overview.


8. Anti-Patterns#

Anti-pattern Problem Better approach
State machine outside a part Not supported Always attach the machine to a part def
state def inside a part Part attributes and ports are not accessible from inside; conforming tools reject the model Write the machine as a usage: state sm { … } — see States
entry action X; when X already exists Declares a second, empty action; nothing happens at run time (W3117) Reference it instead: entry X;
Transition between siblings written inside one of them The target state is not reachable from there Move the transition into the enclosing state
[*] without abstract Multiplicity is dropped; you get one owned part instead of a collection abstract ref part items : T [*]; plus subsets members — see Parts
Mixing unit scales in a sum Units are normalized but never converted; 5[km] + 200[m] computes 205 (W3115) Write both operands in the same unit
Ignoring imports Duplicate type definitions across packages Prefer import Types::*; / import Types::Sensor; instead of copying defs
Blocking code in actions Simulation stalls Keep individual action steps short; use state machines for long-running processes
Empty defs for logic that belongs in SysML Unnecessary C++ hand-coding Use assign, control flow, and nested actions in the model
Tick interval too long Missed or delayed timeouts Sleep shorter than smallest after duration
Forgetting init() Sub-parts are never constructed Call init() on the system part before the simulation loop
Deep hierarchy without purpose Hard to debug in simulation Flatten; use hierarchical states only when they clarify behavior
Implicit events instead of attributes SysML v2 uses guards on data, not UML-style event queues Read ports in do-actions; guard on attributes
Expecting UML test tooling Not available for SysML v2 yet Use scripted simulation or model in UML for coverage tools

Quick Checklist#

Before generating and running your model:

  • All model files in one directory; generate the primary file
  • Every state machine belongs to a part and is written as a usage (state sm { … })
  • entry / exit / do reference existing actions unless you really want to declare a new one
  • System part composes and connects sub-parts
  • Events defined as enums (or rely on the default SM event enum); port payloads typed
  • Default state set (entry; then …)
  • Behavior modeled in action bodies; hardware hooks only where needed
  • codegen.cfg parameters set for SysML v2
  • Simulation loop calls all state machine methods cyclically
  • Tick interval suitable for shortest timeout

Next Steps#

  1. Clone the traffic-light example and run it locally.
  2. Adapt the model: change timeouts, add states, inject port events.
  3. Explore element details in the SysML v2 documentation.
  4. When the model stabilizes, consider a UML tool workflow for formal test-case generation and target code export.

This guide will evolve as the SysML v2 backend gains features. Feedback and example models are welcome.