自己动手写处理器之第二阶段(5)——ModelSim电路仿真

将陆续上传本人写的新书《自己动手写处理器》(尚未出版),今天是第九篇,我尽量每周四篇

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处理器的设计实现阶段了。未完待续!

时间: 2024-10-08 20:27:20

自己动手写处理器之第二阶段(5)——ModelSim电路仿真的相关文章

自己动手写处理器之第二阶段(1)——可编程逻辑器件与PLD电路设计流程

将陆续上传本人写的新书<自己动手写处理器>(尚未出版),今天是第五篇,我尽量每周四篇 通过上一章的介绍,读者应该知道CPU内部有一些基本的电路,比如:译码电路.运算电路.控制电路,此外还有一些寄存器等.这些电路怎么实现呢?当然可以通过一大堆分立的元器件实现,实际上在2008年,美国加州的游戏开发人士Steve Chamberlin就自己制造了一款8位CPU,耗时18个月,花费1000美元,总共使用了1253条线缆,如图2-1所示,Steve Chamberlin为它起了一个十分贴切的名字--B

自己动手写处理器之第二阶段(3)——Verilog HDL行为语句

将陆续上传本人写的新书<自己动手写处理器>(尚未出版),今天是第七篇,我尽量每周四篇 2.6 Verilog HDL行为语句 2.6.1 过程语句 Verilog定义的模块一般包括有过程语句,过程语句有两种:initial.always.其中initial常用于仿真中的初始化,其中的语句只执行一次,而always中语句则是不断重复执行的.此外,always过程语句是可综合的,initial过程语句是不可综合的.       1.always过程语句 always过程语句的格式如图2-10所示.

自己动手写处理器之第二阶段(2)——Verilog HDL简介

将陆续上传本人写的新书<自己动手写处理器>(尚未出版),今天是第六篇,我尽量每周四篇 2.3 Verilog HDL简介 本书实现的OpenMIPS处理器是使用Verilog HDL编写的,所以本章接下来的几节将介绍Verilog HDL的一些基本知识,包括语法.结构等.因为本书并不是一本讲授Verilog HDL的专门书籍,所以此处介绍的内容并不是Verilog HDL的全部,只是一些基础知识,以及在OpenMIPS处理器实现过程中会使用到的知识.读者如果对Verilog HDL有进一步了解

自己动手写处理器之第二阶段(4)——电路设计举例

将陆续上传本人写的新书<自己动手写处理器>(尚未出版),今天是第八篇,我尽量每周四篇 2.7电路设计举例 本节将设计一个简化的处理器取指令电路,通过这个例子体会Verilog HDL的使用. 处理器内部一般有一个PC寄存器,其中存储指令地址,正常运行过程中,PC的值会随时间增加,同时从指令存储器中取出对应地址的指令.所以,本节实现的处理器取指令电路,包含两部分:PC模块.指令存储器.       1.PC模块的设计与实现 PC模块的功能就是给出取指令地址,同时每个时钟周期取指令地址递增.其接口

自己动手写处理器之第一阶段(3)——MIPS32指令集架构简介

将陆续上传本人写的新书<自己动手写处理器>(尚未出版),今天是第四篇,我尽量每周四篇 1.4 MIPS32指令集架构简介 本书设计的处理器遵循MIPS32 Release 1架构,所以本节介绍的MIPS32指令集架构指的就是MIPS32 Release 1. 1.4.1 数据类型 指令的主要任务就是对操作数进行运算,操作数有不同的类型和长度,MIPS32提供的基本数据类型如下. 位(b):长度是1bit. 字节(Byte):长度是8bit. 半字(Half Word):长度是16bit. 字(

自己动手写处理器之第一阶段(2)——MIPS指令集架构的演变

将陆续上传本人写的新书<自己动手写处理器>(尚未出版),今天是第三篇,我尽量每周四篇 MIPS指令集架构自上世纪80年代出现后,一直在进行着更新换代,从最初的MIPS I到MIPS V,发展到可支持扩展模块的MIPS32.MIPS64系列,再到集成代码压缩技术的microMIPS32.microMIPS64.每个MIPS ISA都是其前一个的超集,没有任何遗漏,只有增加新的功能.       1.MIPS Ⅰ 提供加载/存储.计算.跳转.分支.协处理及其它特殊指令.该指令集架构用于最初的MIP

自己动手写处理器之第一阶段(1)——计算机的简单模型、架构、指令集

将陆续上传本人写的新书<自己动手写处理器>(尚未出版),今天是第二篇,我尽量每周四篇 第1章 处理器与MIPS 时间开始了! --胡风 · 1949 让我们以一句诗意的话,开始本书的阅读. 时间从1971年11月15日开始,那一天,Intel发布了世界上第一款单芯片微处理器4004. 1.1 计算机的简单模型 计算机很复杂,可以听歌.看电影.上网.玩游戏,内部是怎么工作的,这个问题太可怕了,太复杂了. 计算机很简单,只有加.减.乘.除.逻辑.移位.转移.存储.加载等几类可以做的操作,太简单了.

自己动手写处理器之第四阶段(1)——第一条指令ori的实现

将陆续上传本人写的新书<自己动手写处理器>(尚未出版),今天是第11篇,我尽量每周四篇 第4章 第一条指令ori的实现 前面几章介绍了很多预备知识,也描绘了即将要实现的OpenMIPS处理器的蓝图,各位读者是不是早已摩拳擦掌,迫切希望一展身手了,好吧,本章我们将实现OpenMIPS处理器的第一条指令ori,为什么选择这条指令作为我们实现的第一条指令呢?答案就两个字--简单,指令ori用来实现逻辑"或"运算,选择一条简单的指令有助于我们排除干扰,将注意力集中在流水线结构的实现

自己动手写编译器之序

<自己动手写编译器.链接器> 因为工作的关系,我经常和各企业的技术负责人交流.话题谈着谈着常常会转到他们目前共同的难题--技术人员招聘.这时不少人都会感慨,中国能做系统软件开发的技术人员太少,这方面的人太难找了.随着中国企业的发展,做系统和平台的需求不断增加,这种供需矛盾将越来越明显. 究其原因,很容易想到的是我们的高校教育.课程设置.美国顶尖大学计算机系基础课程教学里都非常重视项目实践,操作系统课往往要真的开发一个像模像样的操作系统原型,编译器课也真的要自己设计并实现一门有创新性的小语言--