我有以下程序在 PROMELA 中使用进程为 FIFO 建模:
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
}
我想在程序中添加wr_ptr
和rd_ptr
,以指示执行PUSH
更新时相对于FIFO深度的写和读指针:
wr_ptr = wr_ptr % depth;
empty=0;
if
:: (rd_ptr == wr_ptr) -> full=true;
fi
和POP
更新的类似机会
你能帮我把这个添加到这个程序吗?
或者我应该将它设为ltl属性并使用它来检查它?
来自评论:我希望验证此属性,例如如果fifo已满,则应该没有写入请求,这是正确的语法?full表示fifo已满且wr_idx是写指针,我不知道如何访问属性ltl fifo_no_write_when_full {[](full - >!wr_idx)}中的fifo进程的full,empty,wr_idx,rd_idx,depth。
答案 0 :(得分:1)
以下是我为您提供here适用于任意尺寸的基于流程的FIFO 的示例,其中1
适用于可以使用FIFO_SIZE
进行配置。出于验证的目的,我会保持这个值尽可能小(例如3
),因为否则你只是在扩大状态空间而不再包含< em>重要行为。
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;
}
无需更改producer
,consumer
或init
:
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
}
现在您应该有足够的材料来处理并准备好编写自己的属性。在这方面,你写的问题是:
我不知道如何在属性ltl fifo_no_write_when_full {[](完整 - &gt;!wr_idx)}
中访问fifo进程中的full,empty,wr_idx,rd_idx深度
首先,请注意我的代码rd_idx
对应head
,depth
(应该)对应count
并且我没有使用明确的wr_idx
,因为后者可以从前两个派生而来:它由(head + count) % FIFO_SIZE
给出。这不仅仅是代码清洁度的选择,因为 Promela 模型中的变量较少实际上有助于验证内存消耗和运行时间过程
当然,如果您真的想在模型中使用wr_idx
,您可以自由添加它。 (:
第二次,如果你看 Promela manual了解 ltl属性,你会发现:
必须定义名称或符号,以表示模型中全局变量的布尔表达式。
换句话说,不可能将局部变量放在 ltl表达式中。如果您想使用它们,那么您应该从进程的本地空间中取出它们并将它们放在全局空间中。
因此,要检查fifo_no_write_when_full
* ,您可以:
count
的声明移出全局空间fifo_write:
: :: 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
ltl fifo_no_write_when_full { [] ( (count >= FIFO_SIZE) -> ! fifo@fifo_write) }
第三次,在尝试使用常规命令验证任何属性之前,例如
~$ spin -a fifo.pml
~$ gcc -o fifo pan.c
~$ ./fifo -a -N fifo_no_write_when_full
您应该修改producer
和consumer
,以便它们都不会无限期地执行,从而使搜索空间保持较小的深度。否则,您可能会收到排序错误
error: max search depth too small
并且验证会耗尽所有硬件资源而不会得出任何明智的结论。
*:实际上名称fifo_no_write_when_full
非常通用,可能有多种解释,例如。
fifo
已满时,push
不会执行producer
push
为fifo
,则{li> full
无法{{1}}
在我提供的例子中,我选择采用属性的第一种解释。