当我们构建好atomic action以后,接下来还需要将它们组合在一起,形成一个组合动作(compound action)。组合动作会包含多个action,且使用活动(activity)来执行这些动作,同时规定好它们之间的时序关系。在接下来学习activity的过程中,希望读者一方面可以将它与sequence/item之间的关系进行对比,另一方面也要注意在调度(scheduling)安排时,action之间依赖关系会受到哪些方面的影响。
简单来理解动作调度,首先需要认识动作遍历(action traversal),它的目的即在于使得action被初始化、随机化再到执行。对应到UVM sequence,则类似于我们通过类似的宏`uvm_do/`uvm_do_with来完成某个sequence/item从创建、随机化再到执行的完整过程。
我们可以看到下面这段示例代码,action A为atomic acton,action B中置有activity,它用来对a1和a2做调度,action C中的activity对max和b1做调度。
component activity_traversal_statement {
action A {
rand bit [3:0] f1;
};
action B {
A a1, a2;
activity {
a1;
a2 with {
f1 < 10;
};
};
};
action C {
action bit [3:0] max;
B b1;
activity {
max;
b1 with {
<= max;
};
};
};
};
到了C的activity,先通过action bit[3:0] max来声明一个random action域,这种方式相比较于先定义action,再在其内声明一个向量要更快捷,注意这种方式在PSS中是合法的,但无论是action类型还是数据类型,都应该在上层action中用action来声明。在C的activity中,先对变量max做了随机遍历(即产生一个随机后的变量),而后再对b1进行随机遍历,且在遍历的过程中,将上一步随机后的max传入作为外部约束的一部分。DVT产生的图中,a_2/a_3即为b1展开后的内容,但并未体现出max变量,读者在这里可以将以上声明max的方式理解为只是为了需要从一个最小的合法声明容器中获得一个随机值,而在acitvity中随机遍历的对象都需要是action,所以才使用了上述的那种简明方式。
在对以上代码有简单认识以后,希望读者可以注意这些事项:
对action的调度除了上述方式即声明action变量以外,也可以使用匿名方式,例如将上面的action B可以修改为以下代码,即通过do的方式来直接调度action。
action B {
activity {
do A;
do A with {f1 < 10;};
};
};
如果按照串行调度的方式,那么上一个action在执行完之后,才会执行下一个action。这个action有可能是atomic action,也可能是compound action。这种串行调度方式,也可以与其它action之间的依赖关系组合,继而由PSS工具推断最终的执行先后顺序。
action my_test {
A a1, a2, a3;
B b1, b2, b3;
activity {
a1;
b1;
b2;};
b3;};
};
};
在上面提供的代码中,既可以采用默认方式执行串行调度,也可以使用花括号{ },还可以使用sequence关键词来安排。
并行调度类似于SV fork-join,即会要求其内部的action并行执行,并且在全部action执行完之后,才会退出。在PSS 2.0推出之后,又增加了更细致的并行控制(join_branch/join_select/join_none/join_first)。我们从基础掌握的角度来看,只需要理解parallel对action执行的调度即可。
我们可以来看下面这段示例,在这个串行与并行调度混合的action test中,既采用了默认的调度方式,也使用了sequence和parallel的调度方式。
component activity_sequential_mix_parallel {
action A {};
action B {};
action C {};
action test{
A a1, a2;
B b1, b2;
C c1, c2;
activity {
a1;
b1;
sequence {
a1;
b1;
};
parallel {
c1;
c2;
};
};
};
};
最终在生成的activity图上可以体现出这些动作之间的串行关系和并行关系。
除了调度意图更明显的顺序调度和并行调度,我们还可以使用关键词schedule来实现“自由”调度。执行schedule内的子一级动作只需要让他们在符合其它调度要求的同时,由PSS工具自由调度即可。PSS 1.2标准手册相比于PSS 1.0a,在调度说明上面更直白易懂,因为它也把对应的可能发生的调度顺序都以图的形式加以体现。
比如这个在标准中提供的示例代码,在my_test::activity中,先执行了a,接下来将会自由调度b和c两个动作。
action my_test {
A a;
B b;
C c;
activity {
a;
schedule {
b;
c;
}
}
};
由于自由调度的可能性较多,例如b和c如果按照顺序执行的时序,又比如b和c也可能按照并行顺序执行。那么,它们的调度图用遍历的方式来描绘,就可能是以下这样:
而如果按照时序的方式来描述,当b和c在并行执行时,它们可能是以下的执行时序(注意,下图只能表示某些调度可能,并不代表全部的时序可能):
又比如下面这个例子,在schedule中有2个子一级的activity,那么各自都需要按照顺序方式来执行,如果此外再没有其它调度的要求,那么它们之间的调度可能性也较多。
component activity_schedule_advanced {
action A {};
action B {};
action C {};
action D {};
action test1 {
activity {
schedule {
A; do B;}
C; do D;}
};
};
};
};
如果在已经要求A先执行于B,以及C先执行于D的情况下,假设还有其它调度要求使得A先执行于D的话,那么它们之间的执行顺序将如以下的时序:
如果并不存在以上的A先执行于D的话,那么它们之间的调度可能性会更多。在这里我们可以同样适用DVT来观察每次它所生成的调度图(注意,如果约束较为宽松的情况下,在接下来我们很多的例子中,每次activity产生的图都将会在满足所有约束的情况下随机产生):
可以轻松观察到,以上的3中调度可能都是DVT所生成的(还有更多其它的调度可能,这里就不再一一贴出)。当我们将自由调度做了解释以后,想必大家已经能够感受到一点PSS相比于UVM所不同的地方,那就是它也在随机,只不过它不但有能力可以将每个atomic action中的域在执行时随机,也可以对多个action构成的activity以及层次化的activity进行满足约束条件的随机调度。