测试文件testbench:是写输入激励的。模拟实际环境,从软件角度仿真进行分析与验证。
一、测试文件的书写流程:
定义时间标尺——定义信号类型——例化.v文件——编写输入驱动
二、固定写法:
1、时间标尺的格式:`timescale 仿真时间单位/时间精度,时间单位>=时间精度
`timescale 1ns/1ps
#100//延迟100ns,跟时间单位有关
#100.01//延迟100.01ns,若改为`timescale 1ns/1ns 则错误,因为精度还是ns无法达到小数点后面
2、信号类型的定义:原模块输入在testbench中为wire,输出在testbench中为reg。
3、几种固定的书写:
//时钟激励,这种写法可以在例化的时候修改参数大小,而不改变代码本身,减少了错误率。
parameter CYCLE = 20; //默认50MHz的信号,则脉冲周期T=1/f=20ns
initial begin
clk = 0;
forever
#(CYCLE/2)
clk = ~clk;
end
//修改参数例化例子,其中A和B分别为原程序中参数名,2与10为在例化模块中最终参数值
modulename #(.A(2),.B(10)) modulename1(
例化语句;
);
//时钟激励另一种书写形式
`define clock_period 20 //在时间标尺下面预编译——宏定义
initial begin
clk = 0;
always #(·clock_period /2)
clk = ~clk;
end
//复位信号
initial begin
rst_n = 1;
#2;
rst_n = 0;
#(CYCLE*RST_TIME) //延迟RST_TIME个脉冲周期
rst_n = 1;
end
//输入激励赋值
initial begin
#1; //每个输入信号赋值前需延迟1个时间单位,由D触发器决定
din0 = 0;
#TIME;
din0 = 1;
end
三、仿真激励常见写法
1、一种激励方式:
initial begin
a = 0;b = 0;
#200
a = 0;b = 1;
#200
a = 1;b = 0;
#200
a = 1;b = 1;
#200
$stop; //停止仿真
end
2、repeat造30次占空比为1:5的激励:
//在使用repeat时,需要注意前面必须加initial begin…end ,除此在任务函数的设定时需要有 task myname;begin repeat() …end endtask
initial begin
repeat(30)begin
cin = 1'b1;
#`clock_period;
cin = 1'b0;
#(`clock_period*5);
end
end
3、随机任务函数
<1> 随机函数:$ random 这一系统函数可以产生一个有符号的32bit随机整数。一般的用法是$ random%b,其中 b>0,这样就会生成一个范围在(-b+1):(b-1)中的随机数。如果只得到正数的随机数,可采用{$ random}%b。
tip:这里的32bit随机整数指的是$random为32位大小,而非b为32位大小。
//eg1:产生一个随机的数c
c=$random;
//eg2:产生一个[min,max]区间的随机数
reg [31:0] rand;
rand = min+{$random}%(max-min+1);
<2> 任务函数
task <任务名>;
<端口及数据 类型声明语句>
<语句 1>
<语句 2>
<语句 n>
endtask
tip:task只要里面的语句是可综合的,那么task就可综合!
//eg:后面7.8要调用这里的任务函数
reg [5:0]myrand;
task press_key; //建任务函数press_key(相当于module用于testbench的调用)
begin
repeat(50) begin // 重复五十次随机时间
myrand = {$random}%25; //取一次myrand为-24~24的一个随机数
#myrand key_in = ~key_in; // 延迟随机数myrand后,输入激励取反
end
end
endtask
initial begin
Rst_n = 1'b0;
key_in = 1'b1;
#(`clk_period*10) Rst_n = 1'b1;
#30000;
press_key; #10000; //调用任务函数press_key
press_key; #10000;
$stop;
end
4、一种仿真写法,内部信号产生输出
`timescale 1ns/1ns
module key_model(key);
output reg key;
reg [15:0]myrand;
initial begin
key = 1'b1;
press_key;
#10000;
press_key;
#10000;
press_key;
$stop;
end
task press_key;
begin
// 此部分同6(上面设计)
end
endtask
endmodule
5、一种仿真写法——内部信号产生输入输出
`timescale 1ns/1ns
module key_model(press,key);
input press;
output reg key;
reg [15:0]myrand;
initial begin
key = 1'b1;
end
always@(posedge press)
press_key;
task press_key;
begin
// 此部分同6(上面设计)
end
endtask
endmodule
6、当激励需要自动增加或者循环多次的时候,可以使用for循环在测试文件中
当要求发送的报文中报文长度随机(有上下限),报文之间间隔为随机(上下限),信号概率丢失,重复发多个报文时,使用随机函数$random和for循环来做。
/*要求:
1、报文长度在1~128字节随机
2、报文间隔0~3随机
3、报文只发送1
4、一共发送100个报文
5、sop与eop有1/8概率丢失,din_err有1/32概率为1(即错误报文)
*/
reg [31:0] len_rand;
reg [3:0] sop_rand;
reg [3:0] eop_rand;
reg [5:0] err_rand;
reg [3:0] delay_rand;
for(i=0;i<100;i=i+1)begin
len_rand = MIN + {$random} % (MAX - MIN + 1);
sop_rand = {$random} % 8;
eop_rand = {$random} % 8;
err_rand = {$random} % 32;
delay_rand = {$random} % 4;
for(j=0;j
完整的报文测试代码
tip:注意随机函数$ random的位置,重点理解每出现一次就会随机产生一个数。
7、一个串行输入的8bit数据
要点1:当我们输入英文字符的时候,一个字符代表8bit数据。
那么我们输入一串字符的时候,如 str = woshishei 那么定义[8*9-1:0](每个字符8bit,共9个,若中间有空格,感叹号等,也按照8bit算)
要点2:在仿真文件testbench中,语法比较随意,生成激励常用initial语句,但是也可以用组合逻辑和时序逻辑生成。
//在测试文件testbench中写入串行输入数据
wire [143:0] data_temp;
reg [143:0] reg_temp;
assign data_temp = "lssjlwjyhgggqsqqqa"; //有18个英文字符,8*18=144
always @ (posedge clk or negedge rst_n)begin
if(!rst_n)begin
reg_temp <= 0;
end
else if(din_vld)begin
reg_temp <= {reg_temp [135:0],reg_temp [143:136]}; //向左移位寄存器,位数多的向哪个方向移就是什么方向的移位寄存。
end
else begin
reg_temp <= data_temp ;
end
end
assign din = reg_temp[143:136]; //最后把高位的8bit数据传给输入激励din
8、仿真文件的并转串
下面的例子中task函数可不用,只是为了引出9,用了task
//16位并行数据转换为串行数据
input [15:0] vdata;
reg [4:0] cnt;
begin
cnt = 0;
wait(condition); //等待某个条件到来,不到来时则停滞在此
while(cnt<16)begin
@(negedge clk) dout = vdata[15-cnt];
cnt = cnt + 1;
end
end
9、仿真测试文件造rom
`define sin_data_file "./document_name.txt"
`timescale 1ns/1ps //把PC上txt格式的文件document_name给测试文件的sin_data_file
.............
reg [11:0] memory [4095:0]; //存储空间,4096(address)个12位的数据
reg [11:0] address; //存储地址
initial $readmemh(`sin_data_file,memory); //readmemh 中的h(16进制)可以用b(2进制)替换
initial begin
address = 0;
forever begin
for(address=0;address<4095;address=address+1)begin //从地址0取到地址4095
@(posedge clk); //等待clk上升沿到来,上升沿不到就停滞在这
memory[address];
end
end
end
tip! 1:把8中的程序封装成task dout文件,引:9中用dout(memory[address])代替memory[address]可直接得到rom中并转串的数据
2:仿真文件中,用@(posedge/negedge signal)可表沿触发
用wait(signal)可表信号电平触发
用信号 module.signal 可表示在某个例化模块中的某个信号(这个信号不用接端口即可表达)
用$display(“打印的数据=%0h”,signal),在仿真界面打印signal的值。