记录几个SystemVerilog的语法——时钟块和进程通信
1. 时钟块(clocking block)
时钟块的声明和例化是一体化,也就是在声明的时候,其实就实例化了,不需要再例化了。时钟块不能嵌套,且只能声明再module、interface、checker或program里。时钟块有三大用法:
- Synchronous events:同步事件
- Input sampling:输入采样
- Synchronous drives:同步驱动
时钟块中指定为input方向的信号只能被read,不能被write;指定为output方向的信号只能被write,不能被read;指定为inout方向的信号既能被read,也能被write,因为inout拥有input和output两种属性,它在本质上会同时定义两个相同名字的input和output信号。
2. 时钟偏离(Clocking skew)
Clocking skew决定了一根信号在距离时钟事件多远时被采样或驱动的。Input skew隐含为负数的,也就是总是在时钟事件之前发生的,output skew总是在时钟事件之后发生的。同一个时钟块中不同信号可以采用不同的input/output skew,也可以采用default将一个clocking skew应用到同一个时钟块中。除非有特定指定,不然默认的input skew 是1step和默认的output skew是0。对于不带input skew或者input skew为1step的,输入采样值会在时钟事件之前的postponed region或当前时钟事件的preponed region采样的。
如果input指定#0 skew,按道理应该在对应的时钟事件发生时马上采样,但是SystemVerilog为了避免冲突,把它们放在Observed region采样。类似的,如果output带有#0 skew或没有skew,那re-NBA region驱动。
有个注意点是:如果时钟事件是在program里的执行程序触发的,那么时钟事件和采样值之间存在竞争关系,只有时钟事件是在module里更新的才不会出现竞争关系的。
3. 时钟块同步驱动(synchronous drive)
时钟块中信号的写(赋值)只能使用synchronous drive语法(<=),用其它方式赋值会报错。对于时钟块中信号的赋值可能在非时钟事件时被执行到,这样的驱动语句暂时不会阻塞执行,但驱动的值应该是在下一个时钟事件到来时才生效的。也就是说右边的值在执行到时马上计算评估出来,但是驱动的处理是被延迟了,直到下一个时钟事件到来时。例如:
default clocking cb @(posedge clk); // Assume clk has a period of #10 unitsoutput v;
endclockinginitial begin#3 cb.v <= expr1; // Matures in cycle 1; equivalent to ##1 cb.v <= expr1
end
时钟块synchronous drive给inout信号时不改变时钟块中该信号的input值,这是因为input值是刚被采样更新的,而不是在驱动时重新更新的。例子如下:
clocking cb @(posedge clk);inout a;output b;
endclockinginitial begincb.a <= c; // The value of a will change in the Re-NBA regioncb.b <= cb.a; // b is assigned the value of a before the change
end
上述时钟块a信号在re-NBA时才会被驱动,b表达式右边的a在上一个time step时已经采样了,在当前time step还没有被更新,因此b仍然用的是旧值(看input skew,#0则在observed时,非#0则在更之前采样的(如#1step则是在上一个time step的postponed采样))。
4. 进程间同步和通信
SystemVerilog给进程间同步和通信提供了三种方式:
- named event type(->, @):命名事件类型;
- semaphore:旗语;
- mailbox:邮箱;
其中旗语和邮箱是SV内建的class类型,而且可以被作为基类拓展为更高层级的子类。这些内建类都是放在std package里,可以在任何范围内使用。
旗语就像是一把锁,只有获取到这把锁的钥匙(key)的进程才能继续执行,因此它常用语互斥锁、对共享资源的访问控制或基本的同步。
邮箱是一种通讯机制,可以用于进程间进行消息的交换,数据可以通过邮箱从一个进程送到另一个进程。邮箱有有界和无界两种。对于有界的邮箱,如果邮箱满了,那么put()方法会被阻塞住。无界邮箱不会满,因此put()方法不可能会被阻塞住,任何时候数据都可以放进邮箱中。
邮箱有一个要提的是,它分为通用邮箱和参数化邮箱,这两种有什么区别呢?通用邮箱就是它里面可以放各种类型的数据,因此在运行时需要做类型检查,如果put()和get()两测的类型不匹配,那么会报错。参数化邮箱只能放特定类型的数据,仿真器可以在编译时就进行检查。
命名事件提供了同步对象的句柄。当一个进程在等待事件触发时,该进程会被放在事件同步对象维护的队列中。进程可以通过@操作符或检查触发状态的wait()方法来等待命名事件。另外,wait_order还可以用于检测几个event发生的顺序。事件可以通过赋值为null来释放掉的,如:
event E1 = null;
@ E1; // undefined: might block forever or not at all
wait( E1.triggered ); // undefined
-> E1; // no effect
还有就是事件变量的句柄可以用logically equality(==, !=)和case equality(===, !==)来进行比较的,如果比较相同,返回1,反之返回0。例子如下:
event E1, E2;
if ( E1 )E1 = E2; // same as if ( E1 != null )
if ( E1 == E2 )$display( "E1 and E2 are the same event" );