原文网站:Verilog testbench 编写进阶(2)–$monitor – 芯片天地
$monitor(…)过程调用
Testbench 主要目的是提供激励,用于对实体模块或系统进行仿真与验证,其中波形显示在modelsim仿真中提供了很好的接口,但如果对于一些有规律的输入输出,提供格式化函数打印输出,无疑会得到更好的效果。上节中的内容介绍了$display 函数用于打印输出,可以得到好的格式化输出,本节将介绍另一个非常有用的格式化打印$monitor过程,该过程在自动化测试领域应用广泛,因此本章将详细介绍$monitor过程的使用。
1.$monitor 的格式
$monitor (“format_string”, parameter1, parameter2, … );
其中“format_string”是格式化字符串,主要指定后面的参数或表达式如何显示。
格式字符输出,如输出以10进制显示还是16进制显示等。这些格式是由%引导,后面紧跟表示进制的字符标识,如2进制为%b,8进制为%o,16进制为%h,10进制为%d,时间%t等。
例:
$monitor(” data0= %b;data1= %o ; data2= %d; data3= %h” ,4, 6, 12, 13);
显示如下:
data0= 00000000000000000000000000000100;data1= 00000000006 ; data2= 12; data3= 0000000d
在格式中还可以插入宽度,如:
$monitor(“data0= %3b;data1= %3o ; data2= %2d; data3= %2h”, 4, 6, 12, 13);
运行结果如下:
data0= 100;data1= 006 ; data2= 12; data3= 0d
可见给定宽度后,按照给定的宽度显示,如果数据的实际宽度小于格式中给出的宽度,左边补零。如data1= 006 ;
如果格式给出的宽度小于实际数据的宽度,则按照实际数据的有效位数显示。
例:
$monitor(” data0= %2b;data1= %3o ; data2= %2d; data3= %1h” ,4, 33 ,12,19);
运行结果如下:
data0= 100;data1= 041 ; data2= 12; data3= 13
可见并没有因为格式中给定的宽度不够剪裁数据的显示位数。
上面以$monitor为例介绍了格式化输出的详细内容,给部分内容对$display函数也同样适用。如果对C/C++熟悉的同学可以参看C语言中scanf, printf格式化函数,用法与$display, $monitor非常类似。
那么有了$display函数,为什么还要有$monitor过程呢?它们有何不同?
2. $display 函数与 $monitor过程的区别
从格式化显示上看$display与$monitor函数非常类似,但也有重要的区别。
(1)$display 表现函数或任务,而$monitor表现为过程,虽然这里统称为函数。
(2)$display 只在调用时显示,而$monitor监测参数列表,一旦参数列表中任何一个参数发生变化,$monitor将会被自动执行,这也是为什么称$monitor为过程的原因。特别像always过程的敏感表,一旦敏感量发生变化,always过程就会自动执行一样。
例1:用$monitor 监测Verilog实体模块的输入、输出结果
实体文件:
设计4位比较器,逻辑描述如下:
当a>b时, s=2’b10;当a==b时 ,s=2’b01;当a<b时,s=2’b00;
实体程序如下:
`timescale 1 ns /1 ps
module comparator
(
input [3:0] a,
input [3:0] b,
output [1:0] s
);
assign s = a > b ? 2'b10 : a == b ? 2'b01 : 2'b00;
endmodule
仿真程序如下:
`timescale 1ns / 1ps
module stimulus;
// Inputs
reg [3:0] a;
reg [3:0] b;
// Outputs
wire [1:0] s;
// Instantiate the Unit Under Test (UUT)
comparator uut (
.a(a),
.b(b),
.s(s)
);
initial begin
// $dumpfile("test.vcd");
// $dumpvars(0,stimulus);
// Initialize Inputs
a = 0;
b = 0;
#20 a = 3;
#20 b = 4;
#20 b = 2;
#20 a = 1;
#40 ;
end
initial begin
$monitor("t=%3d a=%d,b=%d,s=2'b%2b \n",$time,a,b,s );
end
endmodule
仿真结果如图1,
图1
从上面的例子可以看出,$monitor过程适用比较简单,只要在initial过程中设置一次,就可以根据其后的参数(变量或表达式)的变化不断打印出格式化输出,这一点的确比$display函数使用起来方便。
(3). $monitor过程更适合打印列表输出,而$display更适合观察细节。在仿真时可以用$monitor列表观察总体运行结果,一旦发现异常,$monitor 可以粗略定位,然后用$display一步一步的追踪,直到找出错误原因。
例2: 将例1修改列出a,b分别从0-9的变化所有值,查看比较器的输出结果。
保持实体不变,仅修改仿真程序testbench.v, 修改后的程序如下:
`timescale 1ns / 1ps
module stimulus;
// Inputs
reg [3:0] a;
reg [3:0] b;
// Outputs
wire [1:0] s;
reg clk;
// Instantiate the Unit Under Test (UUT)
comparator uut (
.a(a),
.b(b),
.s(s)
);
initial begin
clk = 0;
a = 0;
b = 0;
end
always #5 clk = ~clk;
always @(posedge clk) begin
a = a + 1;
if(a == 9) begin
a = 0;
b = b + 1;
if(b == 9)begin
$stop;
end
end
end
initial begin
$monitor("t=%3d a=%d,b=%d,s=2'b%2b \n",$time,a,b,s );
end
endmodule
截取部分仿真结果图2:
图2
从图2中可以看出,在t=255时, a=8,b=2,s=2’b10是正确结果;但当t=265时,a应该等于9,结果却是a=0,这与设计思想不符,而$monitor只能定位于此,却不能提供更详细的信息。修改上面的程序,在a=9处插入$display观察细节,程序修改如下:
`timescale 1ns / 1ps
module stimulus;
// Inputs
reg [3:0] a;
reg [3:0] b;
// Outputs
wire [1:0] s;
reg clk;
// Instantiate the Unit Under Test (UUT)
comparator uut (
.a(a),
.b(b),
.s(s)
);
initial begin
clk = 0;
a = 0;
b = 0;
end
always #5 clk = ~clk;
always @(posedge clk) begin
a = a + 1;
if(a == 9) begin
$display(" display t=%3d a=%d,b=%d,s=2'b%2b \n",$time,a,b,s );
a = 0;
$display(" display t=%3d a=%d,b=%d,s=2'b%2b \n",$time,a,b,s );
b = b + 1;
$display(" display t=%3d a=%d,b=%d,s=2'b%2b \n",$time,a,b,s );
if(b == 9)begin
$stop;
end
end
end
initial begin
$monitor("t=%3d a=%d,b=%d,s=2'b%2b \n",$time,a,b,s );
end
endmodule
重新仿真,观察结果如图3:
图3
从图3可以看出在t=265时刻,a的确曾经有等于9的时刻,只是在同一时刻被随后的a=0所替代,最终的结果a=0,b=3,s=2’b00正是$monitor所显示的结果。分析程序最终可以得出结论如下:a=8后,再行a=a+1 得到a=9, 此时满足if(a==9)的条件,因此a=0被执行,可见在同一时刻被修改,这正符合阻塞赋值语句的特点。关于阻塞赋值语句与顺序语句的特点及注意事项请参考本教材相关文章。
注意:$display显示的结果,由于在不同的过程中执行的顺序不同,因此显示的结果可能不同。
修改程序如下:
`timescale 1ns / 1ps
module stimulus;
// Inputs
reg [3:0] a;
reg [3:0] b;
// Outputs
wire [1:0] s;
reg clk;
// Instantiate the Unit Under Test (UUT)
comparator uut (
.a(a),
.b(b),
.s(s)
);
initial begin
clk = 0;
a = 0;
b = 0;
end
always #5 clk = ~clk;
always @(posedge clk) begin
if(a == 9) begin
a = 0;
if(b == 9)begin
b = 0;
$stop;
end
else
b = b + 1;
end
else
a = a + 1;
end
initial begin
$monitor("t=%3d a=%d,b=%d,s=2'b%2b \n",$time,a,b,s );
end
endmodule
仿真结果如图4,
图4
从图4可以看出,经过修改后仿真结果是正确的。
总结: $monitor过程适合列表评估被仿真的总体变化,只要参数列表中有变化,$monitor就会被自动执行。而$display函数在调用时只执行一次。合理配合使用$monitor 和$display可以定位程序中逻辑错误细节,快速修正错误。