将陆续上传本人写的新书《自己动手写处理器》(尚未出版),今天是第九篇,我尽量每周四篇
2.8 仿真
上一节实现了一个简化的处理器取指电路,需要通过仿真以验证其功能是否正确,直观的仿真思路就是:给出一个时钟信号,上述电路会在每个时钟信号上升沿将取指地址加1,同时从指令存储器中取出一条指令,观察取指地址是否依次递增,同时观察取出的指令是否是存储器中取指地址对应的指令,如果都符合,那么上述取指电路就实现正确。此处涉及到两个问题。
1、如何在指令存储器中存储指令,也就是指令存储器初始化问题。
2、如何给出时钟信号?
本节将分别解答上述问题,在此基础上,使用ModelSim进行仿真。
2.8.1 系统函数
初始化存储器有两种方法,一种是对存储器中每个存储单元依次给出初值,如下。
rom[0] = 32'h00000000; //存储器rom的第0个元素初始化为0x00000000 rom[1] = 32'h01010101; //存储器rom的第1个元素初始化为0x01010101 rom[2] = 32'h02020202; //存储器rom的第2个元素初始化为0x02020202 rom[3] = 32'h03030303; //存储器rom的第3个元素初始化为0x03030303 ......
另一种方法是使用系统函数$readmemh,这样更加方便,但是后者只能用于仿真中。
除了$readmemh外,在Verilog HDL中还定义了很多系统函数,比如显示当前仿真时间的函数$time、显示信号值的函数$display、暂停仿真过程的函数$stop、结束仿真过程的函数$finish等。本书主要用到了$stop、$readmemh这两个系统函数。
1、$stop
$stop用于对仿真过程进行控制,暂停仿真,其使用格式如下。
$stop(); // 使用格式一,不带参数 $stop(n); // 使用格式二,带参数n,n可以等于0、1、2等值,含义如下: // 0:不输出任何信息; // 1:给出仿真时间和位置 // 2:给出仿真时间和位置,还有其它一些运行统计数据
当仿真程序执行到$stop语句时,将暂时停止仿真,此时设计者可以输入命令,对仿真器进行交互控制。
2、$readmemh
$readmemh函数用于读取文件,其作用是从外部文件中读取数据并放入存储器中。使用格式如下。
$readmemh("数据文件名", 存储对象);
将第1个参数指定文件的数据读入第2个参数指定的存储器中。例如。
reg[31:0] rom[63:0]; initial $readmemh ( "rom.data", rom ); // 读入文件rom.data的数据到rom中
此处对数据文件的格式有一定要求,要求使用十六进制记录数据,且每一行记录一个地址的数据。例如:rom.data的内容如下,每一行是一个32位的数据。
00000000 01010101 02020202 03030303 ......
使用$readmemh ( "rom.data", rom )函数后,rom的内容就会初始化为如下。
rom[0]: 32'h00000000; //存储器rom的第0个元素初始化为0x00000000 rom[1]: 32'h01010101; //存储器rom的第1个元素初始化为0x01010101 rom[2]: 32'h02020202; //存储器rom的第2个元素初始化为0x02020202 rom[3]: 32'h03030303; //存储器rom的第3个元素初始化为0x03030303 ......
回到本节最开始提出的两个问题,现在可以回答第一个问题了,为了实现对指令存储器的初始化,只需要创建一个数据文件,其内容如上面的rom.data所示,然后在指令存储器rom.v中,增加代码$readmemh ("rom.data", rom )即可。完整代码可以参考本书光盘Code\Chapter2目录下的rom.v文件。
2.8.2 Test Bench
现在回答本节最开始提出的第二个问题,通过创建Test Bench文件以给出时钟信号。
Test Bench为测试或仿真一个Verilog HDL程序搭建了一个平台,我们给被测试的模块施加激励信号,通过观察被测试模块的输出响应,从而判断其逻辑功能实现的正确与否。如图2-17所示。
Test Bench的结构如图2-18所示,与2.4节介绍的Verilog HDL模块的结构没有根本区别,但有自身的一些特点。
- Test Bench只有模块名,没有端口列表;激励信号(输入到待测试模块的信号)必须定义为reg类型,以保持信号值;从待测试模块输出的信号(用户观察的信号)必须定义为wire类型。
- 在Test Bench中要调用被测试模块,也就是元件例化。
- Test Bench中一般会使用initial、always过程块来定义、描述激励信号。
为简单取指令电路设计的Test Bench如下,完整代码位于本书光盘Code\Chapter2目录下的inst_fetch_tb.v文件。
module inst_fetch_tb; // Test Bench名为inst_fetch_tb /**************************************************************** *********** 第一段:数据类型说明 ********* *****************************************************************/ reg CLOCK; // 激励信号CLOCK,这是时钟信号 reg rst; // 激励信号rst,这是复位信号 wire[31:0] inst; // 显示信号inst,取出的指令 /**************************************************************** *********** 第二段:激励向量定义 ********* *****************************************************************/ // 定义CLOCK信号,每隔10个时间单位,CLOCK的值翻转,由此得到一个周期信号。 // 在仿真的时候,一个时间单位默认是1ns,所以CLOCK的值每10ns翻转一次,对应 // 就是50MHz的时钟 initial begin CLOCK = 1'b0; forever #10 CLOCK = ~CLOCK; end // 定义rst信号,最开始为1,表示复位信号有效,过了195个时间单位,即195ns, // 设置rst信号的值为0,复位信号无效,复位结束,再运行1000ns,暂停仿真 initial begin rst = 1'b1; #195 rst= 1'b0; #1000 $stop; end /**************************************************************** *********** 第三段:待测试模块例化 ********* *****************************************************************/ inst_fetch inst_fetch0( .clk(CLOCK), .rst(rst), .inst_o(inst) ); endmodule
2.8.3 ModelSim仿真
指令存储器初始化问题解决了、时钟信号也给出了,现在可以使用ModelSim进行仿真了。
1、建立ModelSim工程
打开ModelSim,选择File->New->Project,出现新建工程对话框,其中填写工程名,选择保存目录,注意保存目录中不要有中文,如图2-19所示。
点击OK后,会出现图2-20所示的界面,这里点击Add Existing File,也就是添加已有文件,出现图2-21所示的添加文件对话框。
点击Browse按钮,出现选择文件对话框,找到本书光盘的Code\Chapter2目录,添加其中所有的.v文件,如图2-22所示。
选择要添加的文件后,点击“打开”按钮,即完成添加,此时显示图2-23所示界面,在其中选中copy to project directory,这样就会将刚才选中的文件复制到新的工程目录下。
文件添加完成后,会在ModelSim的主界面中显示所有文件的状态,其中问号表示对应文件没有编译。任意选中一个文件,鼠标右键单击,在弹出菜单中选择Compile->Compile All,即开始编译所有文件,如图2-24所示。稍等几秒钟就编译结束了。编译结束后,所有的文件状态都应该是一个绿色的“√”。
2、开始仿真
切换到Library这个Tab,然后展开work目录,在inst_fetch_tb文件上点击右键,在弹出菜单中选择Simulate,如图2-25所示。
此时会增加一个Tab,名称为sim,展开其中的inst_fetch_tb节点,选择inst_fetch0,会在Objects窗口中显示inst_fetch模块的所有信号,如图2-26所示,如果没有出现Objects窗口,可以通过菜单View->Objects调出该窗口。
选择Objects窗口中的所有信号,然后点击右键,在弹出菜单中选择Add to->Wave->Selected Signals,如图2-27所示,将所有信号都添加到Wave窗口中。这些都是要观察的信号。如图2-28所示。
点击工具栏中的Run-All按钮,就可开始仿真,如图2-29所示,仿真结果如图2-30所示。从仿真结果可知,处理器取指令电路实现正确。
2.9 小结
本章花了比较大的篇幅介绍了可编程逻辑器件的基本知识,以及基于可编程逻辑器件的数字系统设计流程,包括设计输入、综合、布局布线、下载、仿真等几步,这与传统的数字系统设计流程还是有很大不同的。然后介绍了Verilog HDL这样一种硬件编程语言,这也是将要用来实现OpenMIPS处理器的语言。在此基础上,设计实现了一个简化的处理器取指令电路,并使用ModelSim仿真验证该电路实现的正确性。在后期教学版OpenMIPS的设计实现过程中,也主要是使用ModelSim仿真验证,步骤都是一样的。
从下一次开始,就正式进入OpenMIPS处理器的设计实现阶段了。未完待续!