2
votes

I have the following program that models a FIFO with a process in PROMELA:

mtype = { PUSH, POP, IS_EMPTY, IS_FULL };

#define PRODUCER_UID 0
#define CONSUMER_UID 1

proctype fifo(chan inputs, outputs)
{
    mtype command;
    int data, tmp, src_uid;
    bool data_valid = false;

    do
        :: true ->
            inputs?command(tmp, src_uid);
            if
                :: command == PUSH ->
                    if
                        :: data_valid ->
                            outputs!IS_FULL(true, src_uid);
                        :: else ->
                            data = tmp
                            data_valid = true;
                            outputs!PUSH(data, src_uid);
                    fi
                :: command == POP ->
                    if
                        :: !data_valid ->
                            outputs!IS_EMPTY(true, src_uid);
                        :: else ->
                            outputs!POP(data, src_uid);
                            data = -1;
                            data_valid = false;
                    fi
                :: command == IS_EMPTY ->
                    outputs!IS_EMPTY(!data_valid, src_uid);
                :: command == IS_FULL ->
                    outputs!IS_FULL(data_valid, src_uid);
            fi;
    od;
}

proctype producer(chan inputs, outputs)
{
    mtype command;
    int v;

    do
        :: true ->
            atomic {
                inputs!IS_FULL(false, PRODUCER_UID) ->
                outputs?IS_FULL(v, PRODUCER_UID);
            }
            if
                :: v == 1 ->
                    skip
                :: else ->
                    select(v: 0..16);
                    printf("P[%d] - produced: %d\n", _pid, v);
access_fifo:
                    atomic {
                        inputs!PUSH(v, PRODUCER_UID);
                        outputs?command(v, PRODUCER_UID);
                    }
                    assert(command == PUSH);
            fi;
    od;
}

proctype consumer(chan inputs, outputs)
{
    mtype command;
    int v;

    do
        :: true ->
            atomic {
                inputs!IS_EMPTY(false, CONSUMER_UID) ->
                outputs?IS_EMPTY(v, CONSUMER_UID);
            }
            if
                :: v == 1 ->
                    skip
                :: else ->
access_fifo:
                    atomic {
                        inputs!POP(v, CONSUMER_UID);
                        outputs?command(v, CONSUMER_UID);
                    }
                    assert(command == POP);
                    printf("P[%d] - consumed: %d\n", _pid, v);
            fi;
    od;
}

init {
    chan inputs  = [0] of { mtype, int, int };
    chan outputs = [0] of { mtype, int, int };

    run fifo(inputs, outputs);     // pid: 1
    run producer(inputs, outputs); // pid: 2
    run consumer(inputs, outputs); // pid: 3
}

I want to add wr_ptr and rd_ptr in the program to indicate write and read pointers relative to the depth of FIFO when a PUSH update is performed:

wr_ptr = wr_ptr % depth;
empty=0;
if
    :: (rd_ptr == wr_ptr) -> full=true; 
fi

and similar chances on POP updates

Could you please help me to add this to this program?

or should i make it an ltl property and use that to check it?


from comments: and i want to verify this property, for example If the fifo is full, one should not have a write request, that is the right syntax?full means that fifo is full and wr_idx is the write pointer, I do not know how to access the full, empty, wr_idx, rd_idx, depth on the fifo process in the properties ltl fifo_no_write_when_full {[] (full -> ! wr_idx)}

1
is there a way to put each value sent to the fifo by the producer in a array on this program?lamia
for example i want that the depth of the fifo is 10, i make that in the declaration of inputs and outputs like this: init { chan inputs = [10] of { mtype, int, int }; chan outputs = [10] of { mtype, int, int }; run fifo(inputs, outputs); // pid: 1 run producer(inputs, outputs); // pid: 2 run consumer(inputs, outputs); // pid: 3 }lamia

1 Answers

1
votes

Here is an example of the process-based FIFO with size 1 that I gave you here adapted for an arbitrary size, which can be configured with FIFO_SIZE. For verification purposes, I would keep this value as small as possible (e.g. 3), because otherwise you are just widening the state space without including any more significant behaviour.

mtype = { PUSH, POP, IS_EMPTY, IS_FULL };

#define PRODUCER_UID 0
#define CONSUMER_UID 1
#define FIFO_SIZE    10

proctype fifo(chan inputs, outputs)
{
    mtype command;
    int tmp, src_uid;
    int data[FIFO_SIZE];
    byte head = 0;
    byte count = 0;
    bool res;

    do
        :: true ->
            inputs?command(tmp, src_uid);
            if
                :: command == PUSH ->
                    if
                        :: count >= FIFO_SIZE ->
                            outputs!IS_FULL(true, src_uid);
                        :: else ->
                            data[(head + count) % FIFO_SIZE] = tmp;
                            count = count + 1;
                            outputs!PUSH(data[(head + count - 1) % FIFO_SIZE], src_uid);
                    fi
                :: command == POP ->
                    if
                        :: count <= 0 ->
                            outputs!IS_EMPTY(true, src_uid);
                        :: else ->
                            outputs!POP(data[head], src_uid);
                            atomic {
                                head = (head + 1) % FIFO_SIZE;
                                count = count - 1;
                            }
                    fi
                :: command == IS_EMPTY ->
                    res = count <= 0;
                    outputs!IS_EMPTY(res, src_uid);
                :: command == IS_FULL ->
                    res = count >= FIFO_SIZE;
                    outputs!IS_FULL(res, src_uid);
            fi;
    od;
}

No change to producer, consumer or init was necessary:

proctype producer(chan inputs, outputs)
{
    mtype command;
    int v;

    do
        :: true ->
            atomic {
                inputs!IS_FULL(false, PRODUCER_UID) ->
                outputs?IS_FULL(v, PRODUCER_UID);
            }
            if
                :: v == 1 ->
                    skip
                :: else ->
                    select(v: 0..16);
                    printf("P[%d] - produced: %d\n", _pid, v);
access_fifo:
                    atomic {
                        inputs!PUSH(v, PRODUCER_UID);
                        outputs?command(v, PRODUCER_UID);
                    }
                    assert(command == PUSH);
            fi;
    od;
}

proctype consumer(chan inputs, outputs)
{
    mtype command;
    int v;

    do
        :: true ->
            atomic {
                inputs!IS_EMPTY(false, CONSUMER_UID) ->
                outputs?IS_EMPTY(v, CONSUMER_UID);
            }
            if
                :: v == 1 ->
                    skip
                :: else ->
access_fifo:
                    atomic {
                        inputs!POP(v, CONSUMER_UID);
                        outputs?command(v, CONSUMER_UID);
                    }
                    assert(command == POP);
                    printf("P[%d] - consumed: %d\n", _pid, v);
            fi;
    od;
}

init {
    chan inputs  = [0] of { mtype, int, int };
    chan outputs = [0] of { mtype, int, int };

    run fifo(inputs, outputs);     // pid: 1
    run producer(inputs, outputs); // pid: 2
    run consumer(inputs, outputs); // pid: 3
}

Now you should have enough material to work on and be ready to write your own properties. On this regard, in your question you write:

I do not know how to access the full, empty, wr_idx, rd_idx, depth on the fifo process in the properties ltl fifo_no_write_when_full {[] (full -> ! wr_idx)}

First of all, please note that in my code rd_idx corresponds to head, depth (should) correspond to count and that I did not use an explicit wr_idx because the latter can be derived from the former two: it is given by (head + count) % FIFO_SIZE. This is not just a choice of code cleanliness, because having fewer variables in a Promela model actually helps with memory consumption and running time of the verification process.

Of course, if you really want to have wr_idx in your model you are free to add it yourself. (:

Second, if you look at the Promela manual for ltl properties, you find that:

The names or symbols must be defined to represent boolean expressions on global variables from the model.

So in other words, it's not possible to put local variables inside an ltl expression. If you want to use them, then you should take them out from the process's local space and put them in the global space.

So, to check fifo_no_write_when_full* you could:

  • move the declaration of count out in the global space
  • add a label fifo_write: here:
                :: command == PUSH ->
                    if
                        :: count >= FIFO_SIZE ->
                            outputs!IS_FULL(true, src_uid);
                        :: else ->
    fifo_write:
                            data[(head + count) % FIFO_SIZE] = tmp;
                            count = count + 1;
                            outputs!PUSH(data[(head + count - 1) % FIFO_SIZE], src_uid);
                    fi
  • check the property:
    ltl fifo_no_write_when_full { [] ( (count >= FIFO_SIZE) -> ! fifo@fifo_write) }

Third, before any attempt to verify any of your properties with the usual commands, e.g.

~$ spin -a fifo.pml
~$ gcc -o fifo pan.c
~$ ./fifo -a -N fifo_no_write_when_full

you should modify producer and consumer so that neither of them executes for an indefinite amount of time and therefore keep the search space at a small depth. Otherwise you are likely to get an error of the sort

error: max search depth too small

and have the verification exhaust all of your hardware resources without reaching any sensible conclusion.


*: actually the name fifo_no_write_when_full is quite generic and might have multiple interpretations, e.g.

  • the fifo does not perform a push when it is full
  • the producer is not able to push if the fifo is full

In the example I provided I chose to adopt the first interpretation of the property.