zoukankan      html  css  js  c++  java
  • 自己写CPU第四阶段(2)——验证该第一指令ori实现效果

    我们会继续上传新书《自己写CPU》(未公布),今天是12片,四篇

    书名又之前的《自己动手写处理器》改为《自己动手写CPU》


    4.3 验证OpenMIPS实现效果

    4.3.1指令存储器ROM的实现

          本节将验证我们的OpenMIPS是否实现正确,包括:流水线是否正确、ori指令是否实现正确。在验证之前,须要首先实现指令存储器,以便OpenMIPS从中读取指令。

    指令存储器模块是仅仅读的。其接口如图4-7所看到的,还是採用左边是输入接口,右边是输出接口的方式绘制。这样便于理解。

    接口含义如表4-12所看到的。



          指令存储器ROM模块在文件inst_rom.v中实现,代码例如以下,能够在本书附带光盘的CodeChapter4文件夹下找到源文件。

    module inst_rom(
    
    	input wire			ce,
    	input wire[`InstAddrBus]	addr,
    	output reg[`InstBus]		inst
    	
    );
           // 定义一个数组,大小是InstMemNum,元素宽度是InstBus
    	reg[`InstBus]  inst_mem[0:`InstMemNum-1];
    
           // 使用文件inst_rom.data初始化指令存储器
    	initial $readmemh ( "inst_rom.data", inst_mem );
    
           // 当复位信号无效时,根据输入的地址。给出指令存储器ROM中相应的元素
    	always @ (*) begin
    	  if (ce == `ChipDisable) begin
    	    inst <= `ZeroWord;
    	  end else begin
    	    inst <= inst_mem[addr[`InstMemNumLog2+1:2]];
    	  end
    	end
    
    endmodule
    

          代码非常好理解,有下面几点说明。

          (1)在初始化指令存储器时,使用了initial过程语句。

    initial过程语句仅仅运行一次,通经常使用于仿真模块中对激励向量的描写叙述,或用于给变量赋初值,是面向模拟仿真的过程语句。通常不能被综合工具支持。所以假设要将本章实现的OpenMIPS处理器使用综合工具进行综合,那么须要改动这里初始化指令存储器的方法。

          (2)在初始化指令存储器时,使用了系统函数$readmemh,表示从inst_rom.data文件里读取数据以初始化inst_mem,而inst_mem正是之前定义的数组。inst_rom.data是一个文本文件。里面存储的是指令。其每行存储一条32位宽度的指令(使用十六进制表示),系统函数$readmemh会将inst_rom.data中的数据依次填写到inst_mem数组中。

          (3)OpenMIPS是依照字节寻址的。而此处定义的指令存储器的每一个地址是一个32bit的字,所以要将OpenMIPS给出的指令地址除以4再使用。比方:要读取地址0xC处的指令,那么实际就是相应ROM的inst_mem[3],如图4-8所看到的。


          除以4也就是将指令地址右移2位。所以在读取的时候给出的地址是addr[`InstMemNumLog2+1:2]。当中InstMemNumLog2是指令存储器的实际地址宽度,比方:假设inst_mem有1024个元素,那么InstMemNum等于1024。InstMemNumLog2等于10。表示实际地址宽度为10。

    4.3.2 最小SOPC的实现

          为了验证。须要建立一个SOPC,当中仅包括OpenMIPS、指令存储器ROM,所以是一个最小SOPC。OpenMIPS从指令存储器中读取指令,指令进入OpenMIPS開始运行。

    最小SOPC的结构如图4-9所看到的。


          最小SOPC相应的模块是openmips_min_sopc。位于文件openmips_min_sopc.v中,读者能够在本书附带光盘的CodeChapter4文件夹下找到该文件。主要内容例如以下。

    在当中例化了处理器OpenMIPS、指令存储器ROM,并将两者依照图4-9的方式连接。

    module openmips_min_sopc(
    
    	input	wire		clk,
    	input  wire		rst
    	
    );
    
      	// 连接指令存储器
      	wire[`InstAddrBus] inst_addr;
      	wire[`InstBus]     inst;
           wire               rom_ce;
     
           // 例化处理器OpenMIPS
     	openmips openmips0(
                     .clk(clk),			.rst(rst),
                     .rom_addr_o(inst_addr),	.rom_data_i(inst),
                     .rom_ce(rom_ce)
    	);
    	
           // 例化指令存储器ROM
    	inst_rom inst_rom0(
    		.ce(rom_ce),
    		.addr(inst_addr),		.inst(inst)
    	);
    
    endmodule
    

    4.3.3 编写測试程序

          我们须要写一段測试程序,并将其存储到指令存储器ROM,这样当上一节建立的最小SOPC開始执行的时候,就会从ROM中取出我们的程序,送入OpenMIPS处理器执行。因为眼下的OpenMIPS仅仅实现了一条ori指令。所以測试程序非常easy,例如以下,相应本书附带光盘CodeChapter4TestAsm文件夹下的inst_rom.S文件。

      ori $1,$0,0x1100        # $1 = $0 | 0x1100 = 0x1100
      ori $2,$0,0x0020        # $2 = $0 | 0x0020 = 0x0020
      ori $3,$0,0xff00        # $3 = $0 | 0xff00 = 0xff00
      ori $4,$0,0xffff        # $4 = $0 | 0xffff = 0xffff
    

          共同拥有4条指令。都是ori指令。

          第1条指令将0x1100进行零扩展后与寄存器$0进行逻辑“或”运算。结果保存在寄存器$1中。

          第2条指令将0x0020进行零扩展后与寄存器$0进行逻辑“或”运算,结果保存在寄存器$2中。

          第3条指令将0xff00进行零扩展后与寄存器$0进行逻辑“或”运算,结果保存在寄存器$3中。

          第4条指令将0xffff进行零扩展后与寄存器$0进行逻辑“或”运算,结果保存在寄存器$4中。

          指令的凝视说明了指令的运行结果。接下来,依照正常的顺序应该是使用编译器编译我们的測试程序。但因为GCC编译器的安装、使用、Makefile文件的制作等内容还须要不少篇幅解说,而想必各位读者和笔者一样。急切地想知道OpenMIPS是否实现正确,所以本节採用手工编译的方式编译測试程序,4.4节将专题介绍GCC编译器的使用。

          手工编译仅仅需依照指令内容填充进图4-1所看到的的ori指令格式中,就可以得到相应的二进制字,比方:对于指令ori $1,$0,0x1100。相应的二进制字如图4-10所看到的。

          转化为十六进制即0x34011100,其余3条指令依照相同的方式能够得到相应的二进制字,依照$readmemh函数的要求。一行放一条指令。得到測试程序相应的isnt_rom.data文件例如以下,可在本书附带光盘的CodeChapter4TestAsm文件夹下找到同名文件。

    34011100
    34020020
    3403ff00
    3404ffff
    

    4.3.4 建立Test Bench文件

          本小节将建立Test Bench文件,当中给出最小SOPC执行所需的时钟信号、复位信号。代码例如以下,相应本书附带光盘CodeChapter4文件夹下的openmips_min_sopc_tb.v文件。

    // 时间单位是1ns,精度是1ps
    `timescale 1ns/1ps
    
    module openmips_min_sopc_tb();
    
      reg     CLOCK_50;
      reg     rst;
      
      // 每隔10ns。CLOCK_50信号翻转一次。所以一个周期是20ns。相应50MHz
      initial begin
        CLOCK_50 = 1'b0;
        forever #10 CLOCK_50 = ~CLOCK_50;
      end
          
      // 最初时刻。复位信号有效,在第195ns,复位信号无效,最小SOPC開始执行
      // 执行1000ns后,暂停仿真
      initial begin
        rst = `RstEnable;
        #195 rst= `RstDisable;
        #1000 $stop;
      end
      
      // 例化最小SOPC
      openmips_min_sopc openmips_min_sopc0(
    		.clk(CLOCK_50),
    		.rst(rst)	
    	);
    
    endmodule
    

    4.3.5使用ModelSim检验OpenMIPS实现效果

          万事俱备,仅仅欠东风了,本节是验证前的最后一步——建立ModelSimproject,进行仿真。參考第2章的介绍,新建一个ModelSimproject,project名能够为openmips_min_sopc。将上文创建的OpenMIPS全部源文件、Test Bench文件、指令存储器的源文件等(也就是本书附带光盘CodeChapter4文件夹下全部.v文件)加入到project中。然后编译。

          注意:还须要将上一小克制作的inst_rom.data文件拷贝到project文件夹下。

          编译通过后。将workspace切换到Library选项卡,打开work这个library,选中openmips_min_sopc_tb,右键点击。选择Simulate,如图4-11所看到的。


          在出现的波形显示界面中,加入要观察的信号,就可以開始仿真。此处我们选择寄存器$1-$4作为观察对象,如图4-12所看到的。通过观察寄存器$1-$4的终于值,可知OpenMIPS正确运行了測试程序,也就是正确实现了ori指令。


          加入很多其他要观察的信号,能够了解流水线运行情况。如图4-13所看到的。

    为了使流水线情况显示的更加直观。此处以第一条指令在流水线中的运行过程为例。而且图中去掉了其他指令运行时引起的信号变化。

          (1)在复位结束后的第一个时钟周期上升沿,rom_ce_o变为ChipEnable。表示指令存储器使能。開始取指。进入取指阶段,从指令存储器中取出第一条指令0x34011100。赋给IF/ID模块的输入portif_inst。下一个时钟周期,第一条指令进入译码阶段。

          (2)观察译码阶段。

    •  此时译码阶段的指令id_inst正是第一条指令0x34011100
    •  指令地址id_pc是0x00000000
    •  在ID模块对指令进行译码。得到指令运算类型alusel_o是3'b001。查询defines.h文件里的宏定义可知。相应宏EXE_RES_LOGIC,表示是逻辑运算
    •  得到运算子类型aluop_o是8'b00100101,查询defines.h文件里的宏定义可知,相应宏EXE_OR_OP。表示逻辑“或”运算
    •  译码得到參与运算的源操作数1是0x00000000。正是$0寄存器的值
    •  译码得到參与运算的源操作数2是0x00001100。正是指令中马上数零扩展后的值
    •  译码得到wreg_o的值为1,表示要写目的寄存器
    •  译码得到要写入的目的寄存器wd_o是5'b00001。正是$1寄存器

          (3)观察运行阶段。

    •  进行指定的运算。得到wdata_o为0x00001100,就是要写到目的寄存器的数据
    •  传递译码阶段wreg_o的值,为1,表示要写目的寄存器
    •  传递译码阶段wd_o的值。为5'b00001。表示要写入的目的寄存器是$1寄存器

          (4)观察訪存阶段

    •  传递运行阶段wdata_o的值,为0x00001100,表示要写到目的寄存器的数据
    •  传递运行阶段wreg_o的值。为1,表示要写目的寄存器
    •  传递运行阶段wd_o的值。为5'b00001,表示要写入的目的寄存器是$1寄存器

          (5)观察回写阶段

    •  得到訪存阶段wdata_o的值,为0x00001100,表示要写到目的寄存器的数据
    •  得到訪存阶段wreg_o的值,为1。表示要写目的寄存器
    •  得到訪存阶段wd_o的值,为5'b00001。表示要写入的目的寄存器是$1寄存器

          在回写阶段的最后,将依照要求写目的寄存器$1,使得$1的值为0x00001100。

    通过上面的观察。可知原始的OpenMIPS五级流水线实现正确。接下来。我们就能够以此为基础,不断充实,加入实现很多其它的MIPS指令,只是,在此之前,我们要先学习使用GNU工具链。本节的样例仅仅有4条指令,能够手工编译,以后会遇到比較复杂。拥有较多指令的程序。届时,手工编译就显得效率低下了,所以要使用GNU工具链。


    待续!

    版权声明:本文博客原创文章。博客,未经同意,不得转载。

  • 相关阅读:
    Spring 中出现Element : property Bean definitions can have zero or more properties. Property elements correspond to JavaBean setter methods exposed by the bean classes. Spring supports primitives, refer
    java定时器schedule和scheduleAtFixedRate区别
    hql语句中的select字句和from 字句
    使用maven搭建hibernate的pom文件配置
    Failure to transfer org.apache.maven:maven-archiver:pom:2.5 from http://repo.maven.apache.org/ maven2 was cached in the local repository, resolution will not be reattempted until the update interv
    对于文件File类型中的目录分隔符
    hibernate的事务管理和session对象的详解
    解决mac 中的myeclipse控制台中文乱码问题
    ibatis selectKey用法问题
    Java中getResourceAsStream的用法
  • 原文地址:https://www.cnblogs.com/blfshiye/p/4632909.html
Copyright © 2011-2022 走看看