版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Times_poem/article/details/52036592
需求说明:Verilog设计基础
内容 :testbench的设计 读取文件 写入文件
来自 :时间的诗
十大基本功之 testbench
1. 激励的产生
对于 testbench 而言,端口应当和被测试的 module 一一对应。 端口分为 input,output 和 inout 类型产生激励信号的时候, input 对应的端口应当申明为 reg, output 对应的端口申明为 wire, inout 端口比较特殊,下面专门讲解。
1)直接赋值
一般用 initial 块给信号赋初值,initial 块执行一次,always 或者 forever 表示由事件激发反复执行。 举例,一个 module
`timescale
1ns/
1ps
module exam();
reg rst_n;
reg clk;
reg data;
initial
begin
clk =
1
rst =
1
#10
rst =
1
#500
rst =
1
end
always
begin
#10 clk = ~clk;
end
endmodule
大家应该注意到有个#符号,该符号的意思是指延迟相应的时间单位。该时间单位由 timscale 决定.
一般在 testbench 的开头定义时间单位和仿真精度,比如`timescale 1ns/1ps
前面一个是代表时间单位,后面一个代表仿真时间精度。
以上面的例子而言,一个时钟周期是 20 个单位,也就是 20ns。
而仿真时间精度的概念就是,你能看到 1.001ns 时对应的信号值,
而假如 timescale 1ns/1ns,1.001ns 时候的值就无法看到。
对于一个设计而言,时间刻度应该统一,如果设计文件和 testbench 里面的时间刻度不一致,
仿真器默认以 testbench 为准。
一个较好的办法是写一个 global.v 文件,然后用 include 的办法,可以防止这个问题。
对于反复执行的操作,可写成 task,然后调用,比如
task load_count;
input [
3:
0] load_value;
begin
@(negedge clk_50);
$display($time,
" << Loading the counter with %h >>", load_value);
load_l =
1’b0;
count_in = load_value;
@(negedge clk_50);
load_l =
1’b1;
end
endtask
initial
begin
load_count(
4’hA);
end
其他像 forever,for,function 等等语句用法类似,虽然不一定都能综合,但是用在 testbench 里面很方
便,大家可以自行查阅参考文档
2) 文件输入
有时候,需要大量的数据输入,直接赋值的话比较繁琐,可以先生成数据,再将数据读入到寄存器中, 需要时取出即可。 用 $readmemb 系统任务从文本文件中读取二进制向量(可以包含输入激励和输出期望值)。 $readmemh 用于读取十六进制文件。例如:
reg [
7:
0] mem[
256:
1]
initial $readmemh (
"E:/readhex/mem.dat", mem )
initial $readmemh (
"E:/readhex/mem.dat", mem,
128,
1 )
文件调入和打印中,我们 $fread $fwrite 用的更多一些, 大家可以自行查阅参考文档
2. 查看仿真结果
对于简单的 module 来说,要在 modelsim 的仿真窗口里面看波形,就用 add wave ..命令 比如, testbench 的顶层 module 名叫 tb,要看时钟信号,就用 add wave tb.clk 要查看所有信号的时候,就用 add wave /* 当然,也可以在 workspace 下的 sim 窗口里面右键单击 instance 来添加波形 对于复杂的仿真,免不了要记录波形和数据到文件里面去。
1)波形文件记录(这部分暂时我没用到呢!)
常见的波形文件一般有两种, vcd 和 fsdb, debussy 是个很好的工具,支持 fsdb,所以最好是 modelsim+debussy 的组 合默认情况下, modelsim 不认识 fsdb,所以需要先装 debussy,再生成 fsdb 文件。 $dumpfile 和$dumpvar 是 verilog 语言中的两个系统任务, 可以调用这两个系统任务来创建和将指定信息导入 VCD 文件. 对于 fsdb 文件来说,对应的命令是 fsdbDumpfile,dumpfsdbvars (什么是 VCD 文件? 答: VCD 文件是在对设计进行的仿真过程中,记录各种信号取值变化情况的信息记录文件。 EDA 工具通过读取 VCD 格式的文件,显示图形化的仿真波形,所以,可以把 VCD 文件简单地视为波形记录文件.)下面分别 描述它们的用法并举例说明之。
$dumpfile 系统任务:为所要创建的 VCD 文件指定文件名。
举例(
"//"符号后的内容为注释文字):
initial
$dumpfile (
"myfile.dump");
$dumpvar 系统任务:指定需要记录到 VCD 文件中的信号,可以指定某一模块层次上的所有信号,也可以单独指定某一
个信号。
典型语法为$dumpvar(level, module_name); 参数 level 为一个整数, 用于指定层次数, 参数 module 则指定要记录的模块。
整句的意思就是,对于指定的模块,包括其下各个层次(层次数由 level 指定)的信号,都需要记录到 VCD 文件中去。
举例:
initial
$dumpvar (
0, top);
initial
$dumpvar (
1, top);
initial
$dumpvar (
2, top);
假设模块 top 中包含有子模块 module1,而我们希望记录 top.module1 模块以下两层的信号,则语法举例如下:
initial
$dumpvar (
2, top.module1);
假设模块 top 包含信号 signal1 和 signal2(注意是变量而不是子模块), 如我们希望只记录这两个信号,则语法举例如下:
initial
$dumpvar (
0, top.signal1, top.signal2);
//即指定层次数和单独指定的信号无关 我们甚至可以在同一个$dumpvar 的调用中,同时指定某些层次上的所有信号和某个单独的信号,假设模块 top 包含信 号 signal1,同时包含有子模 块 module1,如果我们不但希望记录 signal1 这个独立的信号,而且还希望记录子模块 module1 以下三层的所有信号,则语法举例如下:
initial
$dumpvar (
3, top.signal1, top.module1);
top.signal1 无关
上面这个例子和下面的语句是等效的:
initial
begin
$dumpvar (
0, top.signal1);
$dumpvar (
3, top.module1);
end
$dumpvar 的特别用法(不带任何参数):
initial
$dumpvar;
//无参数,表示设计中的所有信号都将被记录
最后,我们将$dumpfile 和$dumpvar 这两个系统任务的使用方法在下面的例子中综合说明,假设我们有一个设计实例, 名为 i_design,此设计中包含模块 module1,模块 module1 下面还有很多层次,我们希望对这个设计进行仿真,并将仿 真过程中模块 module1 及其以下所有层次中所有信号的变化情况,记录存储到名为 mydesign.dump 的 VCD 文件中去, 则例示如下:
initial
begin
$dumpfile (
"mydesign.dump");
//指定 VCD 文件名为 mydesign.dump
$dumpvar (
0, i_design.module1);
//记录 i_design.module1 模块及其下面层次中所有模块的所有信号
end
对于生成 fsdb 文件而言,也是类似的
initial
begin
$fsdbDumpfile(
"tb_xxx.fsdb");
$fsdbDumpvars(
0,tb_xxx);
end
2)文件输出结果
integer out_file; // out_file 是一个文件描述,需要定义为 integer 类型 out_file = $fopen ( " cpu.data " ); // cpu.data 是需要打开的文件,也就是最终的输出文本 设计中的信号值可以通过$fmonitor, $fdisplay,$fwrite 其中$fmonitor 只要有变化就一直记录, $fdisplay 和$fwrite 需要触发条件才记录 例子:
initial begin
$fmonitor(file_id,
"%m: %t in1=%d o1=%h", $time, in1, o1);
end
always@(a
or b)
begin
$fwrite(file_id,
"At time%t a=%b b=%b",$realtime,a,b);
end
3 参考“A Verilog HDL Test Bench Primier.pdf”
1) DUT(Design Under Test)部分
`timescale
1 ns /
100 ps
module count16 (
clk,
rst_n,
load_l,
enable_l,
cnt_in,
oe_l,
count,
count_tri
);
input clk;
input rst_n;
input load_l;
input enable_l;
input [
3:
0] cnt_in;
input oe_l;
output [
3:
0]
count;
output [
3:
0] count_tri;
reg [
3:
0]
count;
assign count_tri = (!oe_l) ?
count :
4'bZZZZ;
always @ (posedge clk or negedge rst_n)
if (!rst_n) begin
count <=
4'd0;
end
else begin
if (!load_l) begin
count <= cnt_in;
end
else
if (!enable_l) begin
count <=
count +
1;
end
end
endmodule
2) Test Bench
`timescale 1ns / 100ps
module tb ();
reg clk_50;
reg rst_n;
reg load_l;
reg enable_l;
reg [
3:
0] count_in;
reg oe_l;
wire [
3:
0] cnt_out;
wire [
3:
0] count_tri;
always #
10 clk_50 = ~clk_50;
initial
begin
$display($time,
" << Starting the Simulation >>");
clk_50 =
1'd0;
rst_n =
0;
enable_l =
1'd1;
load_l =
1'd1;
count_in =
4'h0;
oe_l =
4'b0;
#
20 rst_n =
1'd1;
$display($time,
" << Coming out of reset >>");
@(negedge clk_50);
load_count(
4'hA);
@(negedge clk_50);
$display($time,
" << Turning ON the count enable >>");
enable_l =
1'b0;
wait (cnt_out ==
4'b0001);
$display($time,
" << count = %d - Turning OFF the count enable >>",cnt_out);
enable_l =
1'b1;
#
40;
$display($time,
" << Turning OFF the OE >>");
oe_l =
1'b1;
#
20;
$display($time,
" << Simulation Complete >>");
$stop;
end
initial begin
$monitor(
$time,
" clk_50=%b, rst_n=%b, enable_l=%b, load_l=%b, count_in=%h, cnt_out=%h, oe_l=%b, count_tri=%h",
clk_50, rst_n, enable_l, load_l, count_in, cnt_out, oe_l, count_tri
);
end
task load_count;
input [
3:
0] load_value;
begin
@(negedge clk_50);
$display($time,
" << Loading the counter with %h >>", load_value);
load_l =
1'b0;
count_in = load_value;
@(negedge clk_50);
load_l =
1'b1;
end
endtask
count16 count16_m0 (
.clk (clk_50),
.rst_n (rst_n),
.load_l (load_l),
.cnt_in (count_in),
.enable_l (enable_l),
.oe_l (oe_l),
.
count (cnt_out),
.count_tri (count_tri)
);
reg [
7:
0] mem[
10:
1];
initial $readmemh (
"F:/IC/prj/testbench/prj0/data/mem.dat", mem );
integer file_out;
initial file_out = $fopen(
"F:/IC/prj/testbench/prj0/data/wr_mem.dat",
"w");
always @(posedge clk_50)
if (
enable_l ==
1'd0) begin
$fwrite (file_out,
"%h\n", cnt_out[
3:
0]);
end
endmodule
3) sim.do文件
quit -sim
cd F:
/IC/prj
/testbench/prj
0
if [file
exists work] {
vdel -all
}
vlib work
vlog ./*.v
vlog ./src/*.v
vsim -t ps -novopt work.tb
log -r /*
do wave.do
run -all
<script>
(function(){
function setArticleH(btnReadmore,posi){
var winH = $(window).height();
var articleBox = $("div.article_content");
var artH = articleBox.height();
if(artH > winH*posi){
articleBox.css({
'height':winH*posi+'px',
'overflow':'hidden'
})
btnReadmore.click(function(){
articleBox.removeAttr("style");
$(this).parent().remove();
})
}else{
btnReadmore.parent().remove();
}
}
var btnReadmore = $("#btn-readmore");
if(btnReadmore.length>0){
if(currentUserName){
setArticleH(btnReadmore,3);
}else{
setArticleH(btnReadmore,1.2);
}
}
})()
</script>
</article>