1. 首页
  2. EDA技术

ModelsimSE 10.5仿真教程

关于Modelsim的仿真,其实尚为网之前发布过“ModelSim6.3 使用教程”的文章,当时写的比较繁杂,这里再简单总结一下ModelsimSE 10.5的仿真过程。

一、仿真基本概念

FPGA的仿真实际就是一个验证设计的过程,验证在“模拟的输入”的情况下,设计文件的输出是否和我们期望是一致的。这里的“模拟的输入”就是“测试激励”,设计文件就是待测设计,最终通过对输出结果的分析来验证设计的正确性。这就构成了仿真的三个基本组成部分:测试激励(Test_bench)、待测设计(DUT)和最终结果的输出验证。

上面介绍可能还是有些抽象,我们通过一个简单的仿真例子来介绍仿真中的各个部分。(参考“FPGA的FIFO实现过程”中的代码)

1.1 待测设计(DUT)

module fifo_test
#(
	parameter N = 8, //数据宽度
	parameter M = 4  //fifo的地址宽度
)
//对队列的参数设置。建议这样写,便于以后代码的移植。
//如果以后要实现数据宽度为16,深度为2^8的FIFO。只需改N =16 M=8即可
(
	input clk, //输入时钟
	input rst_n, //输入复位信号
	input wr, //输入写使能
	input[N-1:0] w_data, //输入输入
	input rd, //输入读使能
	output empty, //输出fifo空标志
	output full, //输出fifo满标志
	output[N-1:0] r_data //输出读取的数据
);

//寄存器组,用来充当FIFO队列
reg [N-1:0] array_reg [2**M - 1:0];

//定义写指针,指示当前写的位置,下一个状态写的位置,写位置的下一个位置
reg [M-1:0] w_ptr_reg, w_ptr_next,w_ptr_succ;

//定义读指针,指示当前读的位置,下一个状态读的位置,读位置的下一个位置
reg [M-1:0] r_ptr_reg, r_ptr_next,r_ptr_succ;

//定义FIFO满和空的信号
reg full_reg, full_next;
reg empty_reg, empty_next;
wire wr_en;

//数据的写入,在数据的上升沿的时候,有写使能信号,将数据写入。
always@( posedge clk ) begin
	if( wr_en )
		array_reg[w_ptr_reg] <= w_data;
	else
		array_reg[w_ptr_reg] <= array_reg[w_ptr_reg];
end

// 数据的读取。数据读取是一直在读取的,不过读取的是之前的值。
assign r_data = array_reg[r_ptr_reg];
assign wr_en = wr & ~full_reg;

/*状态跳转
在复位信号有效,读/写指针都指向0地址。此时队列状态为空。
在复位不有效,且在时钟的上升沿,读/写指针的值,队列空,满状态的值又下一状态决定。否则保持 */

always@( posedge clk ) begin
	if( !rst_n )
		begin
			w_ptr_reg <= 0;
			r_ptr_reg <= 0;
			full_reg <= 1'b0;
			empty_reg <= 1'b1;
		end
	else
		begin
			w_ptr_reg <= w_ptr_next;
			r_ptr_reg <= r_ptr_next;
			full_reg <= full_next;
			empty_reg <= empty_next;
		end
end

//下一个状态的判定
always@ * begin
	w_ptr_next = w_ptr_reg;
	r_ptr_next = r_ptr_reg;
	full_next = full_reg;
	empty_next = empty;
	w_ptr_succ = w_ptr_reg + 1'b1;
	r_ptr_succ = r_ptr_reg + 1'b1;
	case( {wr,rd} )
	/*读命令:在读命令下,如果队列不为空,讲当前读指针的下一个指针赋值给读指针的下一个状态,同时将队列的满标志置0。
然后判断读指针的下一个指针是否和写指针的值一样。一样的话,说明,队列为空。否则不为空。 */
	2'b01:
	begin
		if( ~empty_reg )
		begin
			r_ptr_next = r_ptr_succ;
			full_next = 0;
			if( r_ptr_succ == w_ptr_reg )
				empty_next = 1'b1;
			else
				empty_next = 1'b0;
		end
	end
	/*写命令:在写命令下,如果队列不为满,将当前写指针的下一个指针赋值给读指针的下一个状态,同时将队列的空标志置0。
然后判断写指针的下一个指针是否和读指针的值一样。一样的话,说明,队列为满。否则不为满。
*/
	2'b10:
	begin
		if( ~full_reg )
		begin
				w_ptr_next = w_ptr_succ;
				empty_next= 0;
				if( w_ptr_succ == r_ptr_reg )
				full_next = 1'b1;
			else
				full_next = 1'b0;
		end
	end
	/*读写命令:在读写命令下, 直接改变对应指针的下一个状态值。
*/
	2'b11:
	begin
		if( ~full_reg && ~empty_reg )
		begin
			w_ptr_next = w_ptr_succ;
			r_ptr_next = r_ptr_succ;
		end
		else if( full_reg ) //在满的状态,不允许写
		begin
			r_ptr_next = r_ptr_succ;
			full_next = 0;
		end
		else if( empty_reg ) //在空的状态,不允许写
		begin
			w_ptr_next = w_ptr_succ;
			empty_next = 0;
		end
	end
	endcase
end

// 满/空输出信号的赋值。
assign full = full_reg;
assign empty = empty_reg;

endmodule

1.2 测试激励(Testbench)

`timescale 1 ns/ 1 ps
module fifo_test_tb();
// constants                                           
// general purpose registers
reg eachvec;
// test vector input registers
reg clk;
reg rd;
reg rst_n;
reg [7:0] w_data;
reg wr;
// wires                                               
wire empty;
wire full;
wire [7:0]  r_data;

// assign statements (if any)                          
fifo_test i1 (
// port map - connection between master ports and signals/registers   
	.clk(clk),
	.empty(empty),
	.full(full),
	.r_data(r_data),
	.rd(rd),
	.rst_n(rst_n),
	.w_data(w_data),
	.wr(wr)
);

 // 50MHz
 always #10 clk = ~clk;

initial                                                
begin                                                  
// code that executes only once                        
// insert code here --> begin                          
 #0 clk   = 1'b0;
    rst_n = 1'b0;
    rd = 1'b0;
    wr = 1'b0;
    w_data = 8'h00;
 #50 rst_n = 1'b1;                                         
// --> end                                             
$display("Running testbench");                       
end                                                    
always@(posedge clk or negedge rst_n)                                                 
// optional sensitivity list                           
// @(event1 or event2 or .... eventn)                  
begin                                                  
// code executes for every event on sensitivity list   
// insert code here --> begin 
  if(!rst_n) begin
    wr <= 0;
    rd <=0;
    w_data <=0;
  end
  else begin                   
    w_data <= w_data + 1; 
      wr <= 1;
      rd <= 1;
    end 
end                                                      
//@eachvec;                                              
// --> end                                                                                               
endmodule

Testbench是FPGA仿真的关键,Testbench可以理解为一个激励产生器。比方说,待测设计的输入是clk和rst_n,实际在开发板上,板子上的晶振输出50MHz激励时钟信号给FPGA,同样电路中的复位电路给FPGA提供了rst_n的激励信号。在仿真过程中,Testbench就代替了实际的电路,通过Verilog模拟实现这些外部电路的激励信号,提供给DUT,从而通过输出验证设计。fifo_test_tb.v就是已经编写好的Testbench。

下面从5个部分介绍Testbench基本写法。

1、`timescale 1ns/1ps 决定整个仿真中的时间单位信息,在文件中任何关于时间的信息都是基于此的,1ns表示仿真中的基本时间单位,1ps则表示仿真精度可以达到1ps。例如 #10.005表示的就是延时10.005ns。实际仿真中,精度是可以控制到0.005ns的,即5ps。

2、fifo_test_tb() 是实际testbench的名称,同样用module定义,但是注意testbenh是没有端口描述的,这与我们待测文件DUT是不一样的。

3、激励信号和输出信号的定义,细心的同学应该可以看出,激励信号就是我们DUT文件的输入,输出信号也就是我们DUT文件的输出。不同的是在Testbench中,我们将激励信号定义为reg类型,输出信号定义为了wire类型。

4、第四部分就是产生激励信号,具体详细实现我们就不介绍了,大家可以阅读我们的文档《Testbench常用语法及技巧》,阅读之后这个地方就很容易理解了。

5、最后一部分是待测设计的实例化,在FPGA设计中,模块的实例化就类似C语言中的函数调用,我们在其他模块中调用当前模块时,就需要以实例化的方式来实现。不同的是,Verilog文件模块实例化时,我们需要标明每一个端口信号。在我们设计的Testbench中,通过模块实例化,将我们的激励信号最终传递给了待测设计文件。

1.3 输出验证

下图是我们仿真的最终输出的波形文件,可以看出读写fifo的波形。

二、建立Modelsim仿真工程

上面主要介绍了modelsim仿真的一些基本概念,接下来就详细介绍如何建立Modelsim仿真工程,了解Modelsim仿真工具的使用。

第一步:打开Modelsim SE,点击菜单栏“File—>New—>Project”,准备新建工程。

第二步:弹出“Create Project”对话框,按下图填写仿真工程名称,以及工程的存储路径,以及默认库的的名称,这里默认库名为“work”,我们通常叫作工作库。设置好后点击OK。

这里介绍一下库的概念,即library。库是Modelsim仿真的载体,Modelsim会将仿真工程中的设计文件(DUT)和激励文件(Testbench)的编译(Compile)结果存放在work库中,在我们新建工程的时候就会带着生成一个work库,如下图在Modelsim工作区,选择Library选项卡,我们可以看到生成的work库,此时work库是空的,因为我们还没有添加并仿真设计文件和激励文件。

第三步:新建或添加设计文件,这里我们已经写好的testbench和待测模块,所以选择直接添加已存在文件即可。

第四步:依次添加testbench和待测模块文件。

第五步:编译我们的DUT和Testbench文件,如下图在工作区域选择Project选项卡,右键选择Compile—>Compile All,编译所有。

第六步:切回到Library,此时再看work库就不是空的了,work库里的fifo_test和fifo_test_tb分别是fifo_test.v(DUT)和fifo_test_tb.v(Testbench)的编译结果。选中Testbench仿真结果fifo_test_tb,右键—>Simulate without Optimization,启动无优化仿真。

第七步:弹出仿真波形窗口(wave窗口),但是窗口内没有任何信号波形,工作区域多了一个sim选项卡,进入sim选项页,可以看到仿真实例fifo_test和fifo_test_tb。选择相应的实例,右键—>add wave,添加信号到wave窗口。

第八步:切到wave 窗口,如下图,设置仿真运行时间为100ns,这个时间根据具体设计所需时间来决定,再点击旁边的图标,运行仿真。这样我们就可以看到输出的波形信号了,从而验证设计的正确性。

三、使用do文件进行Modelsim仿真

另外还有关于do文件进行的仿真,将另外介绍。

四、参考文章

https://www.cnblogs.com/qishui/p/5356887.html

扫码关注尚为网微信公众号

尚为网微信公众号
每天发布半导体汽车电子最新资讯和前沿技术,关注一波,没准就用上了。

原创文章,作者:sunev,如若转载,请注明出处:https://www.sunev.cn/eda/677.html

发表评论

电子邮件地址不会被公开。 必填项已用*标注