注意:从评论来看,您尝试解决的问题看起来非常复杂,并且可能会受益于被分解成更小更容易的部分。
片断 01:简单的生产者/消费者超过 FIFO + ltl 属性
恕我直言,这是处理模型检查和形式验证时的首选方法:您应该从要建模的系统中抽象出实现和语言相关的特性,并专注于其行为的核心方面。
考虑到这种方法,不言而喻的是,尽管在Promela中将生产者和消费者等活动代理建模为进程是有意义的,但对SystemC代码的类 FIFO建模毫无意义,尤其是考虑到Promela已经提供了可以作为同步和异步FIFO队列的chan 。
在这里,我提供了一个非常基本的系统模型,只有一个生产者和一个客户,它允许进行一些简化:
/**
* Producer / Consumer
*/
mtype = { PAYLOAD_MSG };
// Asynchronous Channel
chan fifo = [1] of { mtype, int };
bool data_read = true;
active [1] proctype producer()
{
int v;
do
:: nfull(fifo) ->
select(v : 0..16);
printf("P[%d] - produced: %d\n", _pid, v);
access_fifo:
atomic {
fifo!PAYLOAD_MSG(v);
data_read = false;
}
od
}
active [1] proctype consumer()
{
int v;
do
:: nempty(fifo) ->
access_fifo:
atomic {
fifo?PAYLOAD_MSG(v);
data_read = true;
}
printf("P[%d] - consumed: %d\n", _pid, v);
od
}
#define prod_uses_fifo (producer@access_fifo)
#define cons_uses_fifo (consumer@access_fifo)
#define fair_production ([]<> (_last == 0))
#define cons_not_starving ([]<> (_last == 1))
#define no_overwrite ([] (prod_uses_fifo -> data_read))
#define no_invalid_read ([] (cons_uses_fifo -> !data_read))
// Mutual Exclusion
ltl p1 { [] (!prod_uses_fifo || !cons_uses_fifo) }
// Non-Starvation
ltl p2 { fair_production && fair_production -> cons_not_starving }
// Producer-Consumer
ltl p3 { no_overwrite && no_invalid_read }
请注意,我选择将fifo声明为带有 的异步通道,chan fifo = [1]
而不是带有 的同步通道,chan fifo = [0]
因为您的互斥属性要求生产者和消费者不能同时访问fifo。在同步先进先出中,消费者和生产者总是[并且仅]同时访问先进先出,但没有竞争条件,因为消息直接从生产者移交给消费者。
片断02:作为进程的fifo
现在让我们将生产者、消费者和ltl 属性抛在脑后,关注如何将FIFO 队列建模为Process。
同样,重点应该放在抽象所需的行为上,而不是原始SystemC源代码的一对一映射。我会冒昧地丢弃时钟输入信号并合并所有输入[resp。输出] 成单线。由于在您的评论中您声明您想要一个同步的fifo,我也将禁止write-after-write事件并假设fifo的大小为1。
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;
}
我选择让push/pop尝试失败安全:根据设计,不可能覆盖或读取无效数据。
把所有东西放在一起。
然后可以更新生产者和消费者进程以使用这个fifo进程而不是内置的 chan:
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;
}
你应该注意到这个实现依赖于繁忙的轮询,从性能的角度来看这不是一个明智的想法,尽管它是最简单的建模方法。
另一种方法是使用共享变量和互斥体,但这会破坏FIFO作为具有输入和输出的组件的概念,这显然是您想要建模的。
组件可以像这样连接在一起:
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 属性:
#define prod_uses_fifo (producer@access_fifo)
#define cons_uses_fifo (consumer@access_fifo)
#define fair_production ([]<> (prod_uses_fifo && _last == 2))
#define cons_not_starving ([]<> (cons_uses_fifo && _last == 3))
// Mutual Exclusion
ltl p1 { [] (!prod_uses_fifo || !cons_uses_fifo) }
// Non-Starvation
ltl p2 { fair_production && fair_production -> cons_not_starving }
// Producer-Consumer
// assertions already guarantee that there is no attempt to
// overwrite or read invalid data
现在,我不确定这种方法是否朝着您想要的方向发展,因此我将等待进一步的反馈,以防更新我的答案。