1. 激励的产生

对于 testbench 而言,端口应当和被测试的 module 一一对应。
端口分为 input,output 和 inout 类型产生激励信号的时候,
input 对应的端口应当申明为 reg,
output 对应的端口申明为 wire,
inout 端口比较特殊,下面专门讲解。

1)直接赋值

一般用 initial 块给信号赋初值,initial 块执行一次,always 或者 forever 表示由事件激发反复执行。
举例,一个 module

[plain] view plain copy

`timescale 1ns/1ps

module exam();
reg rst_n;
reg clk;
reg data;

initial
begin
clk = 1'b0;
rst = 1'b1;
#10
rst = 1'b0;
#500
rst = 1'b1;
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 //of load_count

initial
begin
load_count(4’hA); // 调用 task
end

其他像 forever,for,function 等等语句用法类似,虽然不一定都能综合,但是用在 testbench 里面很方
便,大家可以自行查阅参考文档

2) 文件输入

有时候,需要大量的数据输入,直接赋值的话比较繁琐,可以先生成数据,再将数据读入到寄存器中,
需要时取出即可。
用 $readmemb 系统任务从文本文件中读取二进制向量(可以包含输入激励和输出期望值)。
$readmemh 用于读取十六进制文件。例如:

reg [7:0] mem[256:1] // a 8-bit, 256-word 定义存储器 mem
initial $readmemh ( "E:/readhex/mem.dat", 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"); //指定 VCD 文件的名字为 myfile.dump,仿真信息将记录到此文件
$dumpvar 系统任务:指定需要记录到 VCD 文件中的信号,可以指定某一模块层次上的所有信号,也可以单独指定某一
个信号。
典型语法为$dumpvar(level, module_name); 参数 level 为一个整数, 用于指定层次数, 参数 module 则指定要记录的模块。
整句的意思就是,对于指定的模块,包括其下各个层次(层次数由 level 指定)的信号,都需要记录到 VCD 文件中去。

举例:
initial
$dumpvar (0, top); //指定层次数为 0,则 top 模块及其下面各层次的所有信号将被记录

initial
$dumpvar (1, top); //记录模块实例 top 以下一层的信号
//层次数为 1,即记录 top 模块这一层次的信号
//对于 top 模块中调用的更深层次的模块实例,则不记录其信号变化

initial
$dumpvar (2, top); //记录模块实例 top 以下两层的信号

//即 top 模块及其下一层的信号将被记录
假设模块 top 中包含有子模块 module1,而我们希望记录 top.module1 模块以下两层的信号,则语法举例如下:
initial
$dumpvar (2, top.module1); //模块实例 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); //指定层次数和单独指定的信号无关
//所以层次数 3 只作用于模块 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 文件而言,也是类似的
[plain] view plain copy

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)部分

//-------------------------------------------------
// File: count16.v
// Purpose: Verilog Simulation Example
//-------------------------------------------------
`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;

// tri-state buffers
assign count_tri = (!oe_l) ? count : 4'bZZZZ;

// synchronous 4 bit counter

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 //of count16

2) Test Bench

//-------------------------------------------------
// File: tb.v
// Purpose: Verilog Simulation Example
// Test Bench
//-----------------------------------------------------------
`timescale 1ns / 100ps
module tb ();

//---------------------------------------------------------
// inputs to the DUT are reg type
reg clk_50;
reg rst_n;
reg load_l;
reg enable_l;

reg [3:0] count_in;
reg oe_l;

//--------------------------------------------------------
// outputs from the DUT are wire type
wire [3:0] cnt_out;
wire [3:0] count_tri;

//----------------------------------------------------------
// create a 50Mhz clock
always #10 clk_50 = ~clk_50; // every ten nanoseconds invert

//-----------------------------------------------------------
// initial blocks are sequential and start at time 0
initial
begin
$display($time, " << Starting the Simulation >>");
clk_50 = 1'd0;

// at time 0
rst_n = 0;

// reset is active
enable_l = 1'd1;

// disabled
load_l = 1'd1;

// disabled
count_in = 4'h0;
oe_l = 4'b0;

// enabled
#20 rst_n = 1'd1;

// at time 20 release reset
$display($time, " << Coming out of reset >>");

@(negedge clk_50); // wait till the negedge of
// clk_50 then continue
load_count(4'hA);

// call the load_count task

@(negedge clk_50);
$display($time, " << Turning ON the count enable >>");

enable_l = 1'b0;
// turn ON enable
// let the simulation run,
// the counter should roll
wait (cnt_out == 4'b0001); // wait until the count

// equals 1 then continue
$display($time, " << count = %d - Turning OFF the count enable >>",cnt_out);
enable_l = 1'b1;
#40;

// let the simulation run for 40ns
// the counter shouldn't count
$display($time, " << Turning OFF the OE >>");
oe_l = 1'b1;

// disable OE, the outputs of
// count_tri should go high Z.
#20;
$display($time, " << Simulation Complete >>");
$stop;

// stop the simulation
end

//--------------------------------------------------------------
// This initial block runs concurrently with the other
// blocks in the design and starts at time 0

initial begin
// $monitor will print whenever a signal changes
// in the design
$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

//--------------------------------------------------------------
// The load_count task loads the counter with the value passed
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 //of load_count

//---------------------------------------------------------
// instantiate the Device Under Test (DUT)
// using named instantiation
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)
);

//---------------------------------------------------------
// read and write data
reg [7:0] mem[10:1];//read data from file

initial $readmemh ("F:/IC/prj/testbench/prj0/data/mem.dat", mem ); // 将.dat 文件读入寄存器 mem 中

//设计中的信号值可以通过$fmonitor, $fdisplay,$fwrite
//其中$fmonitor 只要有变化就一直记录, $fdisplay 和$fwrite 需要触发条件才记录

integer file_out; // out_file 是一个文件描述,需要定义为 integer 类型
initial file_out = $fopen("F:/IC/prj/testbench/prj0/data/wr_mem.dat", "w");

// wr_mem.dat 是需要打开的文件,也就是最终的输出文本

always @(posedge clk_50)
if (/*tb.count16_m0.*/enable_l == 1'd0) begin
$fwrite (file_out, "%h\n", cnt_out[3:0]);
// $fdisplay(file_out, "%h", cnt_out[3:0]);
end

endmodule //of cnt16_tb

3) sim.do文件

#Time: 2016-07-26
#By : times_poem

quit -sim

cd F:/IC/prj/testbench/prj0

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

3 testbench 的技巧

1)如果激励中有一些重复的项目,可以考虑将这些语句编写成一个 task,这样会给书写和仿真带来很大方便。例如,
一个存储器的 testbench 的激励可以包含 write, read 等 task。

2)如果 DUT 中包含双向信号(inout),在编写 testbench 时要注意。需要一个 reg 变量来表示其输入,还需要一个 wire
变量表示其输出。

3)如果 initial 块语句过于复杂,可以考虑将其分为互补相干的几个部分,用数个 initial 块来描述。在仿真时,这些
initial 块会并发运行。这样方便阅读和修改。

4)每个 testbench 都最好包含$stop 语句,用以指明仿真何时结束。

5)加载测试向量时,避免在时钟的上下沿变化,比如数据最好在时钟上升沿之前变化,这也符合建立时间的要求。
4 一个简单的例子
DUT:  测试对象DUT(Device Under Test),dut: device under test.这个只是表示你要调用单元的例化名而已,此名字可以改成任何verilog可识别的字符。

module counter (
clk,
reset,
enable,
count
);

input clk;
input reset;
input enable;

output [3:0] count;
reg [3:0] count;

always @ (posedge clk)
if (reset == 1'b1) begin
count <= 0;
end else if ( enable == 1'b1) begin
count <= count + 1;
end
endmodule

testbench:

module counter_tb;
reg clk;
reg reset;
reg enable;
wire [3:0] count;

counter U0 (
.clk (clk),
.reset (reset),
.enable (enable),
.count (count)
);

initial begin
clk = 0;
reset = 0;
enable = 0;
end

always #5 clk = ! clk;

initial begin
$dumpfile ("counter.vcd");
$dumpvars;
end

initial begin
$display("\t\ttime,\tclk,\treset,\tenable,\tcount");

$monitor("‰d,\t‰b,\t‰b,\t‰b,\t‰d",$time, clk,reset,enable,count);
end

initial #100 $finish;
//Rest of testbench code after this line
endmodule

4. 高级用法

比如 PLI 之类。有需要的,大家再讨论
总体感觉, testbench 是个很难的事情, 这里讨论的只是一些最基本的东西。 真正有技术含量的是 testcase 的设计,设计
阶段合理层次设计以及模块划分等等,我没有做过很大的项目,所以这方面也没有办法提供更多的帮助。经验丰富的
大牛不妨出来讲讲经验, ^_^

testbench 用的较多的东西是:
1、输入数据文件的产生,一般由 matlab 产生,这方面经常涉及浮点到定点的转换、进制的转换的问题;
2、输入数据文件的输出文件的设置;
3、 VCD、 fsdb 等和其他 eda 软件接口的文件的输出;
4、一定范围内的随机数的产生
5、双向端口的仿真
6、与上层 dsp 等 cpu 接口的时序仿真。
....

panwest: testbench 设计的掌握的技术可多可少,模块级的 HDL 就可以搞定了:
激励产生:高级语言实现( C, C++, Java 等)
DUT: HDL 实现
参考单元:高级语言实现( C, C++, Java 等)
初始化:脚本语言( perl, Tcl/TK)
波形输出:一般脚本自动对比了
数据通过 PLI 进行联系

日后 systemVerilog, e 等语言完善后会减轻一些建立平台的工作量。

synopsys vera support verification.
bench : vera produce

DUT:

ref module: vera
monitor: vera
coverage analysis : VCS or vera
vera can load c, C++ file .

systemverilog 应该是以后的趋势
一些 random scenario 与 tlm base model 都可以用systemverilog 完成
而一个好的 framework 很重要现在有两各 framework 可以选择vmm, ovm
不过要用上述的 framework 需要很大的 oop 基础要花很长时间学习
关于 systemC 或者 system verilog,大家可以查找相关文档。

Verilog中testbench的设计,文件读取和写入操作相关推荐

  1. testbench的设计 文件读取和写入操作 源代码

    十大基本功之 testbench 1. 激励的产生 对于 testbench 而言,端口应当和被测试的 module 一一对应. 端口分为 input,output 和 inout 类型产生激励信号的 ...

  2. C语言的文件读取与写入操作

    学了一年的C语言了,现在回过头来做一下总结.并且博客开了挺长一段时间却没有谢什么实际的东西.现在做下总结,对之后的学习帮助应该挺大的.与大家共勉! 现在,我先来介绍一下C语言的文件读取与写入的原理和具 ...

  3. 在Python中使用pandas进行文件读取和写入方法详解

    Pandas 是 Python 的一个功能强大且灵活的三方包,可处理标记和时间序列数据.还提供统计方法.启用绘图等功能.Pandas 的一项重要功能是能够编写和读取 Excel.CSV 和许多其他类型 ...

  4. python对文件的读操作有哪些方法-Python中文件的读取和写入操作

    从文件中读取数据 读取整个文件 这里假设在当前目录下有一个文件名为'pi_digits.txt'的文本文件,里面的数据如下: 3.1415926535 8979323846 2643383279 wi ...

  5. python对文件的读操作方法有哪些-Python中文件的读取和写入操作

    从文件中读取数据 读取整个文件 这里假设在当前目录下有一个文件名为'pi_digits.txt'的文本文件,里面的数据如下: 3.1415926535 8979323846 2643383279 wi ...

  6. vivado仿真 文件读取和写入

    vivado仿真 文件读取和写入 读取文件 首先创建一个TXT文件. $readmemb和$readmemh用来从文件中读取数据到存储器中.其中readmemb要求每个数字是二进制数,readmemh ...

  7. c#大文件读取和写入数据库

    c#大文件读取和写入数据库(带进度条的源代码) 最近一个项目需要将大文件写入和读取到数据库,觉得可能很多人也需要相关得东西,所以就将代码帖出来 protected int state = 0; //表 ...

  8. C#中创建、打开、读取、写入、保存Excel的一般性代码

    1 Excel对象 微软的Excel对象模型包括了128个不同的对象,从矩形,文本框等简单的对象到透视表,图表等复杂的对象.下面我们简单介绍一下其中最重要,也是用得最多的四个对象. (1) Appli ...

  9. java struts2 excel上传_文件上传方法,使用Struts2,实现Excel文件读取并写入数据库技术...

    文件上传方法,使用Struts2,实现Excel文件读取并写入数据库技术 如题:文件信息的批量导入-- 项目中经常会遇到客户的一些单表信息的数据批量导入,也就是提供定制Excel表,再把Excel表中 ...

最新文章

  1. iOS Xcode 项目重命名
  2. 文本分类入门(四)训练Part 1
  3. 史上最牛空姐,从飞机上掉下愣是没摔死
  4. mysql 学习笔记07日期相关函数01
  5. Kelvin’s Ship-Wave Pattern
  6. C++ set 排序 修改元素之后不会改变原来的排序
  7. 2017年5月—信息安全工程师—上午综合知识(11-15)
  8. 二进制换算成八进制、十进制、十六进制
  9. 一看就懂手机里的PDF转换成PPT的方法
  10. 甲醇合成技术的研究进展
  11. 各种笔记本、台式和主板品牌,进入BIOS设置的方法大全
  12. 求逆矩阵计算机方法,求逆矩阵的快速方法(用于编程).pdf
  13. DeFi之道丨三分钟了解dYdX空投、交易及LP奖励规则
  14. sql2000不显示服务器失败,安装sql server 2000数据库提示程序配置服务器失败.(解决方法)...
  15. 01背包问题(动态规划解决)
  16. [opencv完整项目详解] 传统图像算法解决路标的检测和识别(改进升级版)
  17. npm 内部机制的理解
  18. firebug离线安装方法:把firebug下载了,直接拖到firefox浏览器中即可。
  19. 输入n行的杨辉三角java,杨辉三角 Java代码 可以根据输入 输出相应行数的杨辉三角...
  20. 以企业IM为例,谈谈To B与To C有何不同

热门文章

  1. 如何实现MARKDOWN网页内跳转?
  2. 东南大学 | 12年,热爱始终如一
  3. 山东省2021年普通高考成绩录取去向查询,2018年山东高考录取去向查询时间及入口...
  4. php中strtotime(date,js模仿php中strtotime()与date()函数实现方法
  5. html 二叉树模式,重建二叉树.html
  6. php 图片处理类,分享php多功能图片处理类
  7. 数字电路技术可能出现的简答题_技术货:模拟电路和数字电路PCB设计的区别
  8. 精确微妙计时minicom 源码节选
  9. linux怎样创建硬链接,Linux下创建软、硬链接
  10. Vivado 随笔(3) 其他综合属性 dont_touch、fsm_encoding?