自己写CPU第四阶段(2)——验证该第一指令ori实现效果

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

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


4.3 验证OpenMIPS实现效果

4.3.1指令存储器ROM的实现

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

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

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



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

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中,读者能够在本书附带光盘的Code\Chapter4\文件夹下找到该文件。主要内容例如以下。

在当中例化了处理器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,例如以下,相应本书附带光盘Code\Chapter4\TestAsm文件夹下的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文件例如以下,可在本书附带光盘的Code\Chapter4\TestAsm文件夹下找到同名文件。

34011100
34020020
3403ff00
3404ffff

4.3.4 建立Test Bench文件

      本小节将建立Test Bench文件,当中给出最小SOPC执行所需的时钟信号、复位信号。代码例如以下,相应本书附带光盘Code\Chapter4\文件夹下的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文件、指令存储器的源文件等(也就是本书附带光盘Code\Chapter4文件夹下全部.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工具链。


待续!

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

posted @ 2015-07-09 13:29  blfshiye  阅读(936)  评论(0编辑  收藏  举报