CH02 FPGA设计Verilog基础笔记(三)
1、一个完整的设计,除了好的功能描述代码,对于程序的仿真验证是必不可少的。学会如何去验证自己所写的程序,即如何调试自己的程序是一件非常重要的事。而RTL逻辑设计中,学会根据硬件逻辑来写测试程序,即Testbench显得尤其重要。
编写Testbench的目的是为了对使用硬件描述语言设计的电路进行仿真验证,测试设计电路的功能、性能与设计的预期是否符合。通常过程如下:
*产生模拟激励(波形);
*将产生的激励加入到被测试模块中并观察其响应;
*将输出相应与预期值相比较。
2、Testbench文件结构为:
module Test_bench(); //通常无输入无输出
信号或变量声明定义
逻辑设计中输入对应reg型
逻辑设计中输出对应wire型
使用initial或always语句产生激励
例化待测试模块
监控和比较输出响应
endmodule
1、 时钟激励设计
/*************时钟激励产生方法一:50%占空比时钟************/
parameter ClockPeriod=10;
initial
begin
clk_i=0;
forever
#(ClockPeriod/2) clk_i=~clk_i;
end
/*****************时钟激励产生方法二:50%占空比时钟*************/
initial
begin
clk_i=0;
always #(ClockPeriod/2) clk_i=~clk_i;
end
/*****************时钟激励产生方法三:产生固定数量的时钟脉冲*************/
initial
begin
clk_i=0;
repeat(6)
#(ClockPeriod/2) clk_i=~clk_i;
end
/*****************时钟激励产生方法四:产生非占空比为50%的时钟*************/
initial
begin
clk_i=0;
forever
begin
#((ClockPeriod/2)-2) clk_i=0;
#((ClockPeriod/2)+2) clk_i=1;
end
end
2、 复位信号设计
/*****************复位信号产生方法一:异步复位*************/
initial
begin
rst_n_i=1;
#100;
rst_n_i=0;
#100;
rst_n_i=1;
end
/*****************复位信号产生方法二:同步复位*************/
initial
begin
rst_n_i=1;
@ (negedge clk_i)
rst_n_i=0;
#100; //固定时间复位
repeat(10) @ (negedge clk_i); //固定周期数复位
@ (negedge clk_i)
rst_n_i=1;
end
/*****************复位信号产生方法三:复位任务封装*************/
task reset;
input [31:0] reset_time; //复位时间可调,输入复位时间
RST_ING=0; //复位方式可调,低电平或高电平
begin
rst_n=RST_ING; //复位中
#reset_time; //复位时间
rst_n_i=~RST_ING; //撤销复位,复位结束
end
endtask
3、 双向信号设计
/*****************双向信号描述一:inout在testbench中定义为wire型变量*************/
//为双向端口设置中间变量inout_reg作为inout的输出寄存,
//其中inout变量定义为wire型,使用输出使能控制传输方向
//inout bir_port;
wire bir_port;
reg bir_port_reg;
reg bi_port_oe;
assign bi_port=bi_port_oe?bir_port_reg:1’bz;
/*****************双向信号描述二:强制force**************/
//当双向端口作为输出口时,不需要对其初始化,只需要开通三态门
//当双向端口作为输入口时,只需要对其初始化并关闭三态门,初始化赋值
//需要使用wire型数据,通过force命令来对双向端口进行输入赋值
//assign dinout=(!en) din: 16’hz; 完成双向赋值
initial
begin
force dinout=20;
#200;
force dinout=dinout-1;
end
4、 特殊信号设计
/*****************特殊激励信号产生描述一:输入信号任务封装**************/
task i_data;
input [7:0] dut_data;
begin
@(posedge data_en); send_data=0;
@(posedge data_en); send_data=dut_data[0];
@(posedge data_en); send_data=dut_data[1];
@(posedge data_en); send_data=dut_data[2];
@(posedge data_en); send_data=dut_data[3];
@(posedge data_en); send_data=dut_data[4];
@(posedge data_en); send_data=dut_data[5];
@(posedge data_en); send_data=dut_data[6];
@(posedge data_en); send_data=dut_data[7];
@(posedge data_en); send_data=1;
end
endtask
//调用方法: i_data(8’hXX);
/*****************特殊激励信号产生描述二:多输入信号任务封装**************/
task more_input;
input [7:0] a;
input [7:0] b;
input [31:0] times;
output [8:0] c;
begin
repeat(times) //等待times个时钟上升沿
@(posedge clk_i)
c=a+b; //时钟上升沿a,b相加
end
entask
/*****************特殊激励信号产生描述三:输入信号产生,一次SRAM写信号产生**************/
initial
begin
cs_n=1; //片选无效
wr_n=1; //写使能无效
rd_n=1; //读使能无效
addr=8’hxx; //地址无效
data=8’hzz; //数据无效
#100;
cs_n=0; //片选有效
wr_n=0;
addr=8’Hf1;
data=8’h2C;
#100;
cs_n=1;
wr_n=1;
#10;
addr=8’hxx;
data=8’hzz;
end
/****************Testbench中@与wait**************/
//@使用沿触发
//wait语句使用电平触发
initial
begin
start=1’b1;
wait(en=1’b1);
#10;
start=1’b0;
end
5、 仿真控制语句及系统任务描述
6、 /****************仿真控制语句及系统任务描述**************/
$stop //停止运行仿真,modelsim中可继续仿真
$stop(n) //带参数系统任务,根据参数0,1或2不同,输出仿真信息
$finish //结束运行仿真,不可继续仿真
$finish(n) //带参数系统任务,根据参数0,1或2不同,输出仿真信息
//0:不输出任何信息
//1:输出当前仿真时刻和位置
//2:输出当前仿真时刻、位置和仿真过程中用到的memory以及CPU时间的统计
$random //产生随机数
$random % n //产生范围-n到n之间的随机数
{$random} % n //产生范围0到n之间的随机数
/****************仿真终端显示描述**************/
$monitor //仿真打印输出,打印出仿真过程中的变量,使其终端显示
/*
$monitor($time,,,”clk=%d reset=%d out=%d”,clk,reset,out);
*/
$display //终端打印字符串,显示仿真结果等
/*
$display(“Simulation start !”);
$display(“At time %t,input is %b%b%b,output is %b”,$time,a,b,en,z);
*/
$time //返回64位整型时间
$stime //返回32位整型时间
$realtime //实行实型模拟时间
/****************文本输入方式:$readmemb/$readmemh**************/
//激励复杂的数据结构
//verilog提供了读入文本的系统函数
$readmemb/$readmemh(“<数据文件名>”<存储器名>);
$readmemb/$readmemh(“<数据文件名>”<存储器名>,<起始地址>);
$readmemb/$readmemh(“<数据文件名>”<存储器名>,<起始地址>,<结束地址>);
readmemb:/*
读取二进制数据,读取文件内容只能包含:空白位置,注释行,二进制数。
数据中不能包含位宽说明和格式说明,每个数字必须是二进制数字。
当地址出现在数据文件中,格式为@hh...h。地址与数字间不允许空白位置,
可出现多个地址。
*/
module
reg [7:0] memory[0:3]; //定义了4个8位存储单元
integer i;
initial
begin
$readmemh(“mem.dat”,memory); //读取系统文件到存储器中给定的地址
//显示此时存储器的内容
for(i=0;i<4;i=i+1)
$display(“Memory[%d]=%h”,i,memory[i]);
end
endmodule
/*
mem.dat文件内容
@001
AB CD
@003
A1
*/
//仿真输出为:
Memory[0]=xx;
Memory[1]=AB;
Memory[2]=CD;
Memory[3]=A1;
9、 加法器的仿真测试文件编写
/*******************Design Source文件*****************************/
module add(a,b,c,d,e);//模块端口
input [5:0] a; //输入信号a
input [5:0] b;
input [5:0] c;
input [5:0] d;
output [7:0] e; //求和信号
wire [6:0] outa1,outa2; //定义输出线网型
assign e=outa1+outa2;
/*
通常,我们模块调用写法如下:
被调用的模块名字-自定义名字-括号内信号
这里比如括号内的信号, .ina(ina1)
这种写法最常用,信号的顺序可以调换
另外还有一种写法可以直接这样写
adder u1 (ina1,inb1,outa1);
这种写法必须确保信号的一致性,所以几乎没人采用
*/
adder u1 (.ina(a),.inb(b),.outa(outa1)); //调用adder模块,自定义名字为u1
adder u2 (.ina(c),.inb(d),.outa(outa2));
endmodule
//adder模块
module adder(ina,inb,outa); //模块接口
input [5:0] ina; //ina-输入信号
input [5:0] inb; //inb-输入信号
output [6:0] outa; //outa-输入信号
assign outa=ina+inb; //求和
endmodule //模块结束
/*******************Simulation Sources文件*****************************/
`timescale 1ns/1ps
module add_tv();
reg [5:0] a;
reg [5:0] b;
reg [5:0] c;
reg [5:0] d;
wire [7:0] e;
reg [5:0] i; //中间变量
//调用模块
add uut (.a(a),.b(b),.c(c),.d(d),.e(e));
initial begin //initial仿真初始化用的关键词
a=0;b=0;c=0;d=0; //必须初始化输入信号
for(i=1;i<31;i=i+1)
begin
#10;
a=i;
b=i;
c=i;
d=i;
end
end
initial begin
$monitor($time,,,"%d+%d+%d+%d={%d}",a,b,c,d,e);
#500
$finish;
end
endmodule
/*******************仿真时序图如下*****************************/