• 自己写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工具链。


    待续!

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

  • 相关阅读:
    python获取目录下文件夹名称
    【转载】robocopy的用法
    python使用windows注册表
    统计文本词频
    搞定:找不到该项目,请确认该项目的位置的办法
    set集合
    print显示设置
    用户登录接口(BATE)
    深浅拷贝
    C程序设计-----第2次作业
  • 原文地址:https://www.cnblogs.com/blfshiye/p/4632909.html
Copyright © 2020-2023  润新知