Skip to content

Module Behavior

A module can take several forms: it may be purely structural, acting as a container for submodule instances; it may have only a behavior block describing its own actions over time; it may have both submodules and a behavior; or it may have neither, serving as a placeholder. In all cases, the module participates in simulation as a concurrent entity running on the global clock.

module A
    // declarations of submodules, nets, ports etc. if present
    ...
    behavior
        statement1;
        statement2;
        statement3;
        ...
    end behavior
end module

Behavior as a Sequence

When a module has a behavior block, that block is a sequence of statements separated by semicolons, executing one by one from top to bottom. Some statements are instantaneous. Others (such as wait) suspend the module for some amount of simulation time. When a module suspends, its full execution state is preserved and control returns to the simulation kernel. When the module is next nudged, execution resumes exactly where it left off.

The same structure applies to procedure bodies. A procedure is a named, reusable sequence that can be invoked from within a module's behavior using run. Procedures are described in detail in Procedures.


Atomic and Compound Statements

Every statement in a sequence is either atomic or compound.

An atomic statement is a single indivisible action. It either executes instantaneously or suspends the module. Examples:

  • $...$ - an embedded C++ code block (instantaneous)
  • wait(2,0); - suspend for 2 cycles
  • wait until (this_phase==1); - suspend until a condition is true
  • nothing; - a no-op (instantaneous)
  • stop simulation; - halt the simulation
  • stop behavior; - halt this module's behavior

A compound statement contains one or more nested sequences inside it. Examples:

  • if ... then ... else ... end if - two nested sequences (branches)
  • do ... while(...) end do - one nested sequence (loop body, possibly repeated)
  • [ ... || ... ] - two or more nested sequences running as parallel branches
  • run p - invokes procedure p, which wraps a sequence. run completes only after p's sequence completes. If p hits a wait internally, the entire module suspends until p resumes and finishes.

Any level of nesting is allowed. A compound statement's nested sequences are themselves sequences of atomic and compound statements, following the same rules.


An Example

Let's look at a complete, runnable model. Its behavior contains a code block, a do-while loop, an if-else with a parallel block in the true branch, and a stop statement.

module Top
    behavior
        // S1: instantaneous code block
        $log << endl << "Starting at " << current_time;$;

        // S2: do-while loop
        do
            // S2.1: wait (suspends module)
            wait(1,0);
            // S2.2: instantaneous code block
            $log << endl << "tick at " << current_time;$;
        while (this_cycle < 5) end do;

        // S3: if-else
        if (this_cycle == 5) then
            // S3.T.1: parallel block (two concurrent branches)
            [
                // branch A
                wait(2,0);
                $log << endl << "branch A done at " << current_time;$;
            ||
                // branch B (instantaneous)
                $log << endl << "branch B done at " << current_time;$;
            ];
        else
            $log << endl << "cycle is not 5, this should not happen";$;
        end if;

        // S4: stop simulation
        stop simulation;
    end behavior
end module

The Behavior as a Statement Tree

The nesting structure of the behavior above can be read as a tree, where each node is either a sequence or a statement within it:

flowchart TD
    B["behavior"]
    B --> S1["S1: code block"]
    B --> S2["S2: do-while (this_cycle < 5)"]
    B --> S3["S3: if-else (this_cycle == 5)"]
    B --> S4["S4: stop simulation"]

    S2 --> S2_1["S2.1: wait(1,0)"]
    S2 --> S2_2["S2.2: code block"]

    S3 --> S3_T["True branch"]
    S3 --> S3_F["False branch"]
    S3_T --> S3_T1["S3.T.1: parallel block"]
    S3_T1 --> PA["Branch A"]
    S3_T1 --> PB["Branch B"]
    PA --> PA1["S3.T.1.A.1: wait(2,0)"]
    PA --> PA2["S3.T.1.A.2: code block"]
    PB --> PB1["S3.T.1.B.1: code block"]
    S3_F --> S3_F1["S3.F.1: code block"]

Execution Flow and Suspension Points

The flowchart below traces the execution of the same behavior, showing where the module suspends (wait nodes, in orange) and where it executes instantaneously (code blocks, in blue):

flowchart TD
    START(["Start"]) --> S1

    S1(["S1: **code block** $...$"]):::code --> S2_ENTER

    subgraph S2["S2: do-while (this_cycle < 5)"]
        S2_1(["S2.1: **wait(1,0)**"]):::wait
        S2_2(["S2.2: **code block** $...$"]):::code
        S2_CHK{"this_cycle < 5?"}
        S2_1 --> S2_2 --> S2_CHK
    end

    S2_ENTER((" ")) --> S2_1
    S2_CHK -->|"true: repeat"| S2_1
    S2_CHK -->|"false: exit"| S3_COND

    S3_COND{"S3: this_cycle == 5?"}
    S3_COND -->|"true"| S3_T1_ENTER

    subgraph S3_T1["S3.T.1: parallel block"]
        direction LR
        subgraph BranchA["Branch A"]
            PA1(["S3.T.1.A.1: **wait(2,0)**"]):::wait --> PA2
            PA2(["S3.T.1.A.2: **code block** $...$"]):::code
        end
        subgraph BranchB["Branch B"]
            PB1(["S3.T.1.B.1: **code block** $...$"]):::code
        end
    end

    S3_T1_ENTER((" ")) --> PA1
    S3_T1_ENTER --> PB1
    PA2 --> S3_T1_DONE
    PB1 --> S3_T1_DONE

    S3_COND -->|"false"| S3_F1
    S3_F1(["S3.F.1: **code block** $...$"]):::code --> S4

    S3_T1_DONE((" ")) --> S4
    S4(["S4: **stop simulation**"]) --> STOP(["End"])

    classDef wait fill:#f4a339,color:#000,stroke:#c47d10
    classDef code fill:#4a90d9,color:#fff,stroke:#2c6fad

Execution State

Each active sequence has an implicit execution pointer tracking where it currently is. When a module suspends at a wait, all execution pointers across all active nested sequences are preserved, together with all state variables. When the module is next nudged, execution resumes from exactly the right place at every level of nesting. This is conceptually similar to coroutines or generator functions in other languages.

A sequence at any level is in one of four states at any point during simulation:

State Meaning
Not yet active Control has not entered this sequence yet
Active, not converged Executing; has not yet hit a wait in this phase
Active, converged Suspended at a wait; will resume when next nudged
Terminated Completed. May be re-activated by its containing statement (e.g. the next iteration of a do-while)

Note

A module is considered converged in a phase once every active sequence within it has either hit a wait or terminated. The kernel moves on to the next module once convergence is reached.


Overview of Statements

The table below gives a brief overview of all available statements. Each is covered in detail with examples in the Describing Behavior section.

Statement Type Description
$...$ atomic Embedded C++ code block, executes instantaneously
wait(c,p) atomic Suspend for c cycles and p phases
wait until (expr) atomic Suspend until condition is true
nothing atomic No-op
stop simulation atomic Halt entire simulation
stop behavior atomic Halt this module's behavior
if (condition) then ... else ... compound Conditional branch
do ... while (condition) end do compound Loop
[ ... ||... ] compound Parallel block (fork-join)
run p compound Invoke procedure p; completes when p terminates

Embedded C++ code can also appear in three structural positions outside the behavior block: include $...$ for header includes, decl $...$ for member declarations, and init $...$ for constructor initialization. These are covered in Code Blocks.


What's Next

Proceed to Tokens to understand how data is transferred between modules over nets.