Skip to content

Producer-consumer

A producer module generates a stream of integer-valued tokens and a consumer receives them through a bounded channel. This example uses the producer-consumer pair to illustrate three different ways to terminate a simulation: by production count, by consumption count, or by elapsed time.

What this example demonstrates:

  • Integer payloads with sitar::pack and sitar::unpack
  • Bounded channel with back-pressure (net channel : capacity 4)
  • Three distinct stop conditions selectable at the top level
  • A Timer module for cycle-count-based termination

Selecting a stop condition

Change STOP_MODE in the Top module to select how the simulation ends:

STOP_MODE Meaning
1 Producer calls stop simulation after sending MAX_TOKENS tokens
2 Consumer calls stop simulation after receiving MAX_TOKENS tokens
3 A Timer module calls stop simulation after MAX_CYCLES cycles
module Top
    // ---------------------------------------------------------------
    // Change STOP_MODE to 1, 2, or 3 to select the termination style
    parameter int STOP_MODE  = 1
    parameter int MAX_TOKENS = 10
    parameter int MAX_CYCLES = 25
    // ---------------------------------------------------------------
    submodule sys : System<STOP_MODE, MAX_TOKENS, MAX_CYCLES>
end module

module System
    parameter int MODE       = 1
    parameter int MAX_TOKENS = 10
    parameter int MAX_CYCLES = 25

    submodule prod  : Producer<MODE, MAX_TOKENS>
    submodule cons  : Consumer<MODE, MAX_TOKENS>
    submodule timer : Timer<MODE, MAX_CYCLES>

    net channel : capacity 4 width 4
    prod.outp => channel
    cons.inp  <= channel
end module

All three modules (Producer, Consumer, Timer) are always instantiated. Each checks its own MODE parameter and acts only when it matches. When MODE is a compile-time constant, the C++ compiler eliminates the dead branches entirely.


Producer

The Producer packs each successive integer into a token<4> payload and pushes it to the channel. It greedily fills the channel each cycle (the while (outp.push(t)) loop pushes until the net is full). In mode 1, the Producer calls stop simulation once it has sent MAX_TOKENS tokens.

module Producer
    parameter int MODE       = 1
    parameter int MAX_TOKENS = 10

    outport outp : width 4

    decl $
    int      count;
    token<4> t;
    $
    init $count = 0;$

    behavior
        do
            wait until (this_phase == 1);
            $
            sitar::pack(t, count);
            while (outp.push(t)) {
                log << endl << "produced " << count;
                count++;
                if (count >= MAX_TOKENS && MODE == 1) break;
                sitar::pack(t, count);
            }
            $;
            wait;

            // MODE 1: stop after producing MAX_TOKENS
            if (MODE == 1 and count >= MAX_TOKENS) then
                $log << endl << "producer: " << MAX_TOKENS << " tokens sent -- stopping";$;
                stop simulation;
            end if;
        while (1) end do;
    end behavior
end module

Consumer

The Consumer drains all available tokens in phase 0 each cycle, unpacking and logging each value. In mode 2, it calls stop simulation once it has received MAX_TOKENS tokens.

module Consumer
    parameter int MODE       = 1
    parameter int MAX_TOKENS = 10

    inport inp : width 4

    decl $
    int      count;
    token<4> t;
    int      val;
    $
    init $count = 0;$

    behavior
        do
            wait until (this_phase == 0);
            $
            while (inp.pull(t)) {
                sitar::unpack(t, val);
                log << endl << "consumed " << val;
                count++;
            }
            $;
            wait;

            // MODE 2: stop after consuming MAX_TOKENS
            if (MODE == 2 and count >= MAX_TOKENS) then
                $log << endl << "consumer: " << MAX_TOKENS << " tokens received -- stopping";$;
                stop simulation;
            end if;
        while (1) end do;
    end behavior
end module

Timer

The Timer module is a one-shot: if MODE == 3, it waits MAX_CYCLES cycles and then stops the simulation. If MODE != 3, its behavior ends immediately and the module is dormant for the rest of the simulation.

module Timer
    parameter int MODE       = 1
    parameter int MAX_CYCLES = 25

    behavior
        // MODE 3: stop after MAX_CYCLES cycles; otherwise do nothing
        if (MODE == 3) then
            wait(MAX_CYCLES, 0);
            $log << endl << "timer: " << MAX_CYCLES << " cycles elapsed -- stopping";$;
            stop simulation;
        end if;
    end behavior
end module

Expected output

Mode 1 (STOP_MODE=1, MAX_TOKENS=10):

(0,1) TOP.sys.prod : produced 0
(0,1) TOP.sys.prod : produced 1
(0,1) TOP.sys.prod : produced 2
(0,1) TOP.sys.prod : produced 3
(1,0) TOP.sys.cons : consumed 0
(1,0) TOP.sys.cons : consumed 1
...
(2,1) TOP.sys.prod : produced 9
(2,1) TOP.sys.prod : producer: 10 tokens sent -- stopping
Simulation stopped at time (2,1)

Mode 2 (STOP_MODE=2, MAX_TOKENS=10): The Producer runs indefinitely; the Consumer stops simulation after receiving 10 tokens. The final stop simulation call comes from the Consumer rather than the Producer.

Mode 3 (STOP_MODE=3, MAX_CYCLES=25): Both Producer and Consumer run indefinitely until the Timer fires at cycle 25. The number of tokens in flight at that point is determined by the channel capacity and the relative rates of production and consumption.

Modes 1 and 2 are not equivalent

In mode 1, tokens may still be in transit inside the channel when stop simulation is called; the Consumer may not have seen all of them. In mode 2, the Consumer counts only tokens it has actually pulled, so the stop condition captures end-to-end delivery. Choose the mode that matches what your model needs to measure.