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

2.8 仿真

 

      上一节实现了一个简化的处理器取指电路,需要通过仿真以验证其功能是否正确,直观的仿真思路就是:给出一个时钟信号,上述电路会在每个时钟信号上升沿将取指地址加1,同时从指令存储器中取出一条指令,观察取指地址是否依次递增,同时观察取出的指令是否是存储器中取指地址对应的指令,如果都符合,那么上述取指电路就实现正确。此处涉及到两个问题。

      1、如何在指令存储器中存储指令,也就是指令存储器初始化问题。

      2、如何给出时钟信号?

      本节将分别解答上述问题,在此基础上,使用ModelSim进行仿真。

2.8.1 系统函数

      初始化存储器有两种方法,一种是对存储器中每个存储单元依次给出初值,如下。

 

  1.  
    rom[0] = 32'h00000000; //存储器rom的第0个元素初始化为0x00000000
  2.  
    rom[1] = 32'h01010101; //存储器rom的第1个元素初始化为0x01010101
  3.  
    rom[2] = 32'h02020202; //存储器rom的第2个元素初始化为0x02020202
  4.  
    rom[3] = 32'h03030303; //存储器rom的第3个元素初始化为0x03030303
  5.  
    ......

 

      另一种方法是使用系统函数$readmemh,这样更加方便,但是后者只能用于仿真中。

      除了$readmemh外,在Verilog HDL中还定义了很多系统函数,比如显示当前仿真时间的函数$time、显示信号值的函数$display、暂停仿真过程的函数$stop、结束仿真过程的函数$finish等。本书主要用到了$stop、$readmemh这两个系统函数。

      1、$stop

      $stop用于对仿真过程进行控制,暂停仿真,其使用格式如下。

 

  1.  
    $stop(); // 使用格式一,不带参数
  2.  
    $stop(n); // 使用格式二,带参数n,n可以等于0、1、2等值,含义如下:
  3.  
    // 0:不输出任何信息;
  4.  
    // 1:给出仿真时间和位置
  5.  
    // 2:给出仿真时间和位置,还有其它一些运行统计数据

 

 

      当仿真程序执行到$stop语句时,将暂时停止仿真,此时设计者可以输入命令,对仿真器进行交互控制。

      2、$readmemh

      $readmemh函数用于读取文件,其作用是从外部文件中读取数据并放入存储器中。使用格式如下。

 

$readmemh("数据文件名", 存储对象);

 

      将第1个参数指定文件的数据读入第2个参数指定的存储器中。例如。

 

  1.  
    reg[31:0] rom[63:0];
  2.  
     
  3.  
    initial $readmemh ( "rom.data", rom ); // 读入文件rom.data的数据到rom中

      此处对数据文件的格式有一定要求,要求使用十六进制记录数据,且每一行记录一个地址的数据。例如:rom.data的内容如下,每一行是一个32位的数据。

 

 

  1.  
    00000000
  2.  
    01010101
  3.  
    02020202
  4.  
    03030303
  5.  
    ......

 

      使用$readmemh ( "rom.data", rom )函数后,rom的内容就会初始化为如下。

  1.  
    rom[0]: 32'h00000000; //存储器rom的第0个元素初始化为0x00000000
  2.  
    rom[1]: 32'h01010101; //存储器rom的第1个元素初始化为0x01010101
  3.  
    rom[2]: 32'h02020202; //存储器rom的第2个元素初始化为0x02020202
  4.  
    rom[3]: 32'h03030303; //存储器rom的第3个元素初始化为0x03030303
  5.  
    ......

      回到本节最开始提出的两个问题,现在可以回答第一个问题了,为了实现对指令存储器的初始化,只需要创建一个数据文件,其内容如上面的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文件。

  1.  
    module inst_fetch_tb; // Test Bench名为inst_fetch_tb
  2.  
     
  3.  
    /****************************************************************
  4.  
    *********** 第一段:数据类型说明 *********
  5.  
    *****************************************************************/
  6.  
     
  7.  
    reg CLOCK; // 激励信号CLOCK,这是时钟信号
  8.  
    reg rst; // 激励信号rst,这是复位信号
  9.  
    wire[31:0] inst; // 显示信号inst,取出的指令
  10.  
     
  11.  
    /****************************************************************
  12.  
    *********** 第二段:激励向量定义 *********
  13.  
    *****************************************************************/
  14.  
     
  15.  
    // 定义CLOCK信号,每隔10个时间单位,CLOCK的值翻转,由此得到一个周期信号。
  16.  
    // 在仿真的时候,一个时间单位默认是1ns,所以CLOCK的值每10ns翻转一次,对应
  17.  
    // 就是50MHz的时钟
  18.  
    initial begin
  19.  
    CLOCK = 1'b0;
  20.  
    forever #10 CLOCK = ~CLOCK;
  21.  
    end
  22.  
     
  23.  
    // 定义rst信号,最开始为1,表示复位信号有效,过了195个时间单位,即195ns,
  24.  
    // 设置rst信号的值为0,复位信号无效,复位结束,再运行1000ns,暂停仿真
  25.  
    initial begin
  26.  
    rst = 1'b1;
  27.  
    #195 rst= 1'b0;
  28.  
    #1000 $stop;
  29.  
    end
  30.  
     
  31.  
    /****************************************************************
  32.  
    *********** 第三段:待测试模块例化 *********
  33.  
    *****************************************************************/
  34.  
     
  35.  
    inst_fetch inst_fetch0(
  36.  
    .clk(CLOCK),
  37.  
    .rst(rst),
  38.  
    .inst_o(inst)
  39.  
    );
  40.  
     
  41.  
    endmodule
  42.  
     

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

posted @ 2020-11-26 10:53  erinfeng  阅读(747)  评论(0)    收藏  举报