一、Always语句分化
1. always_comb
- 只能被综合成组合逻辑,若逻辑综合为时序逻辑(触发器、锁存器)则编译过程中会报错
- always_comb可以自动嵌入敏感列表,并可以将调用的函数中的可能参与运算的信号声明到敏感列表
- always_comb会限制其他过程对于同一变量进行赋值
- always_comb在0时刻会自动触发,无论信号在0时刻是否有变化
2. always_latch
- 表示锁存逻辑,并且会自动嵌入敏感列表
- 在编译的过程中会自动检查是否生成了锁存逻辑
3. always_ff
- 敏感列表必须指明posedge、negedge,从而使得EDA工具实现同步或异步逻辑
- 在编译的过程中会自动检查是否实现了时序逻辑
二、赋值操作
1. SV可以使用'0 '1 'z 'x来快速填充1、0、z、x
2.
- == 对于带有z、x的变量会是一个unknow的状态 === 也会将z 、x进行比较
- ==?对于x位可以匹配成任意的数字,而对于z只能匹配成unknow状态
三、unique case 和 priority case
unique case
- 要求case有并且只能有一条符合匹配条件的语句
- unique case选项可以并行执行,并且选项必须完备
priority case
- 要求case语句中至少要有一个满足匹配条件,若多个条件满足,则会执行第一条语句
- priority case语句和if。。。else。。。等同
四、接口(interface)
将多个信号集束在一个大的信号中,可以非常方便的在各个模块中进行例化
接口的声明与模块类似:
接口与模块之间的区别:
- 模块的一些端口可以在例化时候不连接信号,但是若模块的接口声明为interface,在例化的时候必须连接到一个端口实例
- 接口声明时需要在模块或程序之外进行声明。
- 在声明中可以使用modport指定信号的方向
- 在接口中可以使用时钟块(clocking block)来指定同步信号相对于时钟的时序。测试平台可以使用@interf.cb表达式来驱动时钟
五、程序块(Program block)
在verilog各个事件的调度顺序如下:
从图中可以看出各个事件的调度顺序,阻塞赋值与非阻塞赋值的调度是不一样的,而#0延时处于中间状态。
由于各个事件的调度顺序不同,就有可能导致程序执行过程中,相应事件采集到错误的值。
例如:
module counter(input clk);
bit [3:0] cnt;
always @(posedge clk) begin
cnt <= cnt + 3;
$display("%0t DUT cnt = %0d", $time, cnt);
end
endmodule
module tb1;
bit clk;
bit [3:0] cnt;
initial begin
forever #5ns clk <= ~clk;
end
counter counter_inst(clk);
always @(posedge clk) begin
$display("%0t TB cnt = %0d", $time, counter_inst.cnt);
end
endmodule
运行结果:
tb中$dispaly的目的是监视cnt的变化,但是由于系统函数优先调度,所以$display打印出来的一直是cnt变化之前的值,也就是采样错误。
System Verilog 多添加了几个调度区域
前三个是verilog的调度区域,observed是为断言准备的,而在reactive区域正式进行断言判断
要解决前一个例子出现的问题,可以使用程序块
program block主要是将验证与设计分离,让他们处于不同的时间调度中
module counter(input clk);
bit [3:0] cnt;
always @(posedge clk) begin
cnt <= cnt + 3;
$display("%0t DUT cnt = %0d", $time, cnt);
end
endmodule
program sample(input clk);
initial begin
forever begin
@(posedge clk)
$display("%0t TB cnt = %0d", $time, counter_inst.cnt);
end
end
endprogram
module tb1;
bit clk;
bit [3:0] cnt;
initial begin
forever #5ns clk <= ~clk;
end
counter counter_inst(clk);
sample(clk);
endmodule
仿真结果
加入了program block块后,tb中的$display在reactive的时间区域进行调度,避免了和设计的竞争问题。
program需要注意的问题
- program中不能例化其他program和module
- 不能出现interface和always,可以使用initial forever替代always
- program内部可以发起多个initial块
- program中内部定义的变量最好采用阻塞赋值,当然采用非阻塞仿真器也不会产生error,驱动外部信号则应该采用非阻塞赋值
- program中的initial块和module中的initial块执行位置不同,前者在reactive,后者在active块中执行。
- program中存在的多个initial块中,如果有一个initial采用了退出系统函数$exit(),则会结束该program,而不仅仅是该initial块。