zl程序教程

您现在的位置是:首页 >  硬件

当前栏目

Verilog实现MIPS的5级流水线cpu设计(Modelsim仿真)[通俗易懂]

CPU 实现 通俗易懂 设计 仿真 流水线 Verilog MIPS
2023-06-13 09:11:03 时间

大家好,又见面了,我是你们的朋友全栈君。

Verilog实现IMPS的5级流水线cpu设计

本篇文章是在功能上实现cpu设计,而非结构上实现。结构上实现可以跳转到(此为个人推荐): Verilog流水线CPU设计(超详细) 此外有与本文章配套的资源,文章中不懂的地方可以跳转到(有工程源码): MIPS五级流水线cpu的制作

一、实验内容

1.1:实验目的 (1)CPU各主要功能部件的实现 (2)CPU的封装 (3)了解提高CPU性能的方法 (4)掌握流水线MIPS微处理器的工作原理 (5)理解并掌握数据冒险、控制冒险的概念以及流水线冲突的解决方法 (6)掌握流水线MIPS微处理器的测试仿真方法 1.2:实验要求 (1)至少实现MIPS中的三类指令,即R类,I内,J类指令 (2)采用5级流水线技术 (3)完成Lw指令的数据冒险的解决 (4)在ID段完成控制冒险的解决

二、实验环境

2.1:硬件平台 无,只进行仿真,未下载到FPGA

2.2:软件平台 (1)操作系统:WIN 10 (2)开发平台:Modelsim SE-64 10.4 (3)编程语言:VerilogHDL硬件描述语言

三、实验原理

3.1:IMPS流水线CPU原理 根据MIPS微处理器的特点,将整体的处理过程分为取指令(IF)、指令译码(ID)、指令执行(EX)、存储器访问(MEM)和寄存器写回(WB)五个阶段,对应着流水线的五级。这样,我们就可以定义没执行一条指令需要5个时钟周期,每个时钟周期的上升沿到来时,此指令的一系列数据和控制信息将转移到下一级处理。

3.2:数据流动

  • IF级:取指令部分 (1)根据PC值在指令存储器中取指令,将取得指令放在流水寄存器中。 (2)对PC寄存器的更新,更新有两类,一是直接PC值 +4 ,取下一相邻地址的指令;二是更新为跳转地址,该地址来自于ID段算出的分支地址或者跳转地址等。更新的选择取决于来自ID段的一个判断信号。将更新后的PC值放在流水线寄存器中。
  • ID级:指令译码部分 (1)进行指令译码,按照对应寄存器号读寄存器文件,并将读出结果放入临时寄存器A和B中。 (2)数据冒险和控制冒险的检测和处理都在此阶段解决。 (3)同时对位立即值保存在临时寄存器中。指令的低16位进行符号位的扩展,并存放在流水寄存器中。
  • EX级:执行部分 根据指令的编码进行算数或者逻辑运算或者计算条件分支指令的跳转目标地址。此外LW、SW指令所用的RAM访问地址也是在本级上实现。
  • MEM级:访存部分 只有在执行LW、SW指令时才对存储器进行读写,对其他指令只起到一个周期的作用。
  • WB级:写回部分 该级把指令执行的结果回写到寄存器文件中。

3.3:冒险策略

  • 数据冒险 (1)使用定向(旁路)解决数据冒险 在ID段对寄存器进行读数据时,要读取的数据可能是上一个指令要写入的结果,也就是当前结果在流水线中还没有写入寄存器,此时读取寄存器的数据是未更新的,也就是错误的。这种情况一般发生在MEM级与WB级的写数据与ID级的读数据发生冲突(未考虑LW指令),这样可以通过定向技术来解决数据冒险,因为在某条指令产生计算结果之前,其他指令并不是真正立即需要该计算结果,如果能够将该计算结果从其产生的地方直接送到其他指令需要的地方,那么就可以避免冲突。 (2)使用暂停机制解决Lw数据冒险 定向技术有显而易见的局限性,因为定向技术必须要求前一条指令在EX结束时更新,但是LW指令最早只能在WB级读出寄存器的值。因此无法及时提供给下一条指令的EX级使用。分析流水线时序图,可以发现lw指令的下一条指令,需要阻塞一个时钟周期,才能确保该指令能获得正确的操作数值,下面给出具体解决方法。在ID级需要进行数据冒险,ID级是进行译码的段,对操作码进行比较,当发现当前的指令是一条LW指令时,ID级会发出一条请求流水线暂停的信号,部件ctrl会根据信号产生一个6位的stall信号,6位的信号分别控制pc部件,IF级,ID级,EX级,MEM级,WEB级的暂停,当然在这里当出现LW冒险时只需要关闭PC部件、IF级、ID级即可避免冲突。暂停就相当于在流水图中添加一数列的气泡,将后面的指令都往后推一个时钟周期。
  • 控制冒险 控制冒险是因为由控制相关引起的,也就是当出现分支指令等能使pc值发生变化的时候就会出现控制冒险。下面从两个方面来说下如何解决控制冒险和降低分支延迟。 (1)在正常的数据流水线中分支指令时候成功以及分支地址的传送都是在MEM级完成的,这样就会造成3个时钟周期的延迟。现在我将这两个操作都放在ID级完成,这样分支延迟就会降低到一个时钟周期。 (2)再说下如何解决控制冲突。控制冲突都是分支指令,跳转指令等引起的,但我们提前知道这条指令是分支(跳转)指令时,我们就可以提前最好准备来解决冲突。这些也是在ID级来完成。ID是译码段,通过比较没一条指令来发觉哪些是分支跳转指令,但发现分子跳转指令时,ID段就会计算出跳转地址,并产生一个跳转信号,在下个时钟上沿到来后会将这两个信号传送到pc部件,同时阻止前面部件的输出。在这里是采用了延迟槽的技术,但出现分支指令是就会认为延迟槽中的指令作废,相当于一条空指令了。有点使用预测失败的思想。

3.4:指令格式 (1)MIPS有三类指令,分别为R型指令,I型指令,J型指令

(2)本实验用到的MIPS指令格式

四:模块设计

  • 展示下宏文件defines.v :
`define RstEnable 1'b1 `define RstDisable 1'b0 `define ENABLE 1'b1 `define DISABLE 1'b0
`define WriteEnable 1'b1 `define WriteDisable 1'b0 `define ReadEnable 1'b1 `define ReadDisable 1'b0

`define InsEnable 1'b1 `define InsDisable 1'b0 // common constant define `define ZeroWord 32'h00000000 // bus width define `define InsAddrWidth 31:0 `define InsWidth 31:0 `define InsAddrBus 31:0 `define InsDataBus 31:0 `define RegAddrBus 4:0 `define RegDataBus 31:0 `define DRegDataBus 63:0 // number of objects `define InsMemUnitNum 262143 `define InsMemUnitNumLog2 18 `define RegFilesNum 32 `define RegFilesNumLog2 5 // instructions encoding `define SPECIAL 6'b000000 `define SPECIAL2 6'b011100
`define REGIMM 6'b000001 // 0. NOP `define NOP 6'b000000 // SPECIAL `define PREF 6'b110011 //SPECIAL `define SYNC 6'b001111

// 1. LOGIC Insts
`define AND 6'b100100 // SPECIAL `define OR 6'b100101 // SPECIAL `define XOR 6'b100110 // SPECIAL `define NOR 6'b100111 // SPECIAL

`define ANDI 6'b001100 `define ORI 6'b001101 `define XORI 6'b01110 `define LUI 6'b001111

// 2. SHIFT Insts
`define SLL 6'b000000 // SPECAIL `define SRL 6'b000010 // SPECIAL `define SRA 6'b000011 // SPECIAL `define SLLV 6'b000100 //SPECIAL
`define SRLV 6'b000110 //SPECIAL `define SRAV 6'b000111 //SPECIAL // 3. MOVE Insts `define MOVN 6'b001011 // SPECIAL `define MOVZ 6'b001010 // SPECIAL
`define MFHI 6'b010000 // SPECIAL `define MFLO 6'b010010 // SPECIAL `define MTHI 6'b010010 // SPECIAL `define MTLO 6'b010011 // SPECIAL

// 4. Arith Insts
`define ADD 6'b100000 // SPECIAL `define ADDU 6'b100001 // SPECIAL `define SUB 6'b100010 // SPECIAL `define SUBU 6'b100011 // SPECIAL
`define SLT 6'b101010 // SPECIAL `define SLTU 6'b101011 // SPECIAL `define ADDI 6'b001000 `define ADDIU 6'b001001
`define SLTI 6'b001010 `define SLTIU 6'b001011 `define CLZ 6'b100000 // SPECIAL2 `define CLO 6'b100001 // SPECIAL2

`define MUL 6'b000010 // SPECIAL2 `define MULT 6'b011000 // SPECIAL `define MULTU 6'b011001 //SPECIAL // 5. branch insts `define JR 6'b001000 //SPECIAL
//`define JALR 6'b001001 // SPECIAL `define J 6'b000010 //`define JAL 6'b000011 `define BEQ 6'b000100
`define BGTZ 6'b000111 `define BLEZ 6'b000110 `define BNE 6'b000101 `define BLTZ 5'b00000   // REGIMM
//`define BLTZAL 5'b10000 // REGIMM `define BGEZ 5'b00001 // REGIMM //`define BGEZAL 5'b10001 // REGIMM // LOAD and STORE //`define LB 6'b100000
//`define LBU 6'b100100 //`define LH   6'b100001 //`define LHU 6'b100101 `define LW 6'b100011
//`define LWL 6'b100010 //`define LWR  6'b100110 //`define SB 6'b101000 //`define SH 6'b101001
`define SW 6'b101011 //`define SWL  6'b101010 //`define SWR 6'b101110 // ALU OP `define alu_op_nop 8'b00000000
`define alu_op_or 8'b00100101 `define ALU_OP_NOP 8'b00000000 `define ALU_OP_AND 8'b00100100 `define ALU_OP_OR 8'b00100101
`define ALU_OP_XOR 8'b00100110 `define ALU_OP_NOR  8'b00100111 `define ALU_OP_ANDI 8'b01011001 `define ALU_OP_ORI 8'b01011010
`define ALU_OP_XORI 8'b01011011 `define ALU_OP_LUI  8'b01011100 `define ALU_OP_SLL 8'b01111100 `define ALU_OP_SLLV 8'b00000100
`define ALU_OP_SRL 8'b00000010 `define ALU_OP_SRLV  8'b00000110 `define ALU_OP_SRA 8'b00000011 `define ALU_OP_SRAV 8'b00000111

`define ALU_OP_MOVZ 8'b00001010 `define ALU_OP_MOVN 8'b00001011 `define ALU_OP_MFHI 8'b00010000 `define ALU_OP_MTHI 8'b00010001
`define ALU_OP_MFLO 8'b00010010 `define ALU_OP_MTLO 8'b00010011 `define ALU_OP_SLT 8'b00101010 `define ALU_OP_SLTU 8'b00101011
`define ALU_OP_SLTI 8'b01010111 `define ALU_OP_SLTIU  8'b01011000 `define ALU_OP_ADD 8'b00100000 `define ALU_OP_ADDU 8'b00100001
`define ALU_OP_SUB 8'b00100010 `define ALU_OP_SUBU  8'b00100011 `define ALU_OP_ADDI 8'b01010101 `define ALU_OP_ADDIU 8'b01010110
`define ALU_OP_CLZ 8'b10110000 `define ALU_OP_CLO  8'b10110001 `define ALU_OP_MULT 8'b00011000 `define ALU_OP_MULTU 8'b00011001
`define ALU_OP_MUL 8'b10101001 `define ALU_OP_J  8'b01001111 //`define ALU_OP_JAL 8'b01010000 //`define ALU_OP_JALR 8'b00001001
`define ALU_OP_JR 8'b00001000 `define ALU_OP_BEQ  8'b01010001 `define ALU_OP_BGEZ 8'b01000001 //`define ALU_OP_BGEZAL 8'b01001011
`define ALU_OP_BGTZ 8'b01010100 `define ALU_OP_BLEZ  8'b01010011 `define ALU_OP_BLTZ 8'b01000000 //`define ALU_OP_BLTZAL 8'b01001010
`define ALU_OP_BNE 8'b01010010 `define ALU_OP_LW  8'b11100011 `define ALU_OP_SW 8'b11101011 // ALU SEL `define ALU_NOP 3'b000
`define ALU_LOGIC 3'b001 `define ALU_SEL_LOGIC 3'b001 `define ALU_SEL_SHIFT 3'b010 `define ALU_SEL_NOP 3'b000
`define ALU_SEL_MOVE 3'b011 `define ALU_SEL_ARITH 3'b100 `define ALU_SEL_MUL 3'b101 `define ALU_SEL_BRANCH 3'b110
`define ALU_SEL_LOADSTORE 3'b111 //ALU `define ALUOpBus 7:0
`define ALUSelBus 2:0 // DataMem `define DMDataBus 31:0
`define DMAddrBus 31:0 `define DMUnitNum 131071
`define DMunitNumLog2 17 `define ByteWidth 7:0
  • pc部件

(1)模块结构:

(2)模块功能: 作为pc寄存器的更新信号,即对pc值进行更新

(3)实现思路: pc值的更新有两类,一是没有遇到分支指令或者能改变pc值的指令时,程序顺序执行,pc值加4即可;二是当出现分支指令或者跳转指令时,程序不按顺序执行,此时pc值不在加4,而是等于ID段传来的跳转(分支)地址。而pc值的选择根据ID段传来的是否跳转的信号决定。出现冒险时Lw指令冒险的时候会被暂停信号的输出,即保持pc值不变。

(4)引脚及控制信号 rst:对部件进行复位操作,一位的输入端口 clk:时钟信号,一位的输入端口 branchEN:是否跳转信号,一位的输入端口 branchAddr:跳转地址,32位的输入端口 stall:暂停流水线控制信号,6位的输入端口 pc:pc值,32位的输出端口 insEn:使能信号,判断是否关闭指令存储器,一位的输出端口 (5)主要代码

`include "defines.v" module pc( // ---- input ----- input wire rst, input wire clk, input wire branchEN, input wire[`RegDataBus] branchAddr,	
	input wire[5:0] stall,
	// ---- output ----
	output reg[`InsAddrWidth] pc, output reg insEn ); always@(posedge clk) begin if(rst == `RstEnable) 
		begin
			insEn <= `InsDisable; pc <= `ZeroWord;
		end
		else
		begin
			insEn <= `InsEnable; end end always@(posedge clk) begin if(insEn == `DISABLE)
			pc <= `ZeroWord; else begin if(stall[0] == `DISABLE)
			begin
				if(branchEN == `ENABLE)
					pc <= branchAddr;
				else
				pc <= pc + 4;
			end
		end
	end
endmodule
  • insMem部件

(1)模块结构:

(2)模块功能: 从pc部件获取pc值,然后在insMe中取得指令。

(3)实现思路: 为了避免不必要的访存冲突(结构冲突),将指令存储器、数据存储器、存储器设为三个独立的部件。在发生冒险时会被pc部件关闭。

(4)引脚及控制信号: insEN:控制insMem的开启与关闭,一位的输入端口 insAddr:输入pc值,32位的输入端口 inst:取出的指令,32位的输出端口

(5)主要代码

`include "defines.v" module insMem(insEn, insAddr, inst); // ----- input ------ input wire insEn; input wire[`InsAddrWidth] insAddr;	
	// ----- output -----
	output reg[`InsWidth] inst; reg[`InsWidth] instM[0:`InsMemUnitNum]; initial Begin+ $readmemh("instructions.data",instM); end always@(*) begin if(insEn == `InsDisable) begin
			inst <= `ZeroWord; end else begin inst <= instM[insAddr[`InsMemUnitNumLog2+1:2]];
		end
	end
endmodule
  • IF_ID部件 (1)模块结构:

(2)模块功能: IF_ID模块是IF级与ID级之间的流水寄存器,用来隔离相邻两阶段的处理工作。

(3)实现思路: 储存IF级处理完工作后的数据并在时钟上升沿到来的时候将存储的数据传给ID级。在发生Lw互锁的时候会暂停数据的流出。

(4)引脚及控制信号: rst:对部件进行复位操作,一位的输入端口 clk:时钟信号,一位的输入端口 if_pc:来自insMem部件的pc信号,32位的输入端口 if_ins:来自insMen部件的指令代码信号,32位的输入端口 stall:暂停流水线控制信号,6位的输入端口 id_pc:传给ID段的pc信号,32位的输出端口 id_ins:传给ID段的指令代码,32位的输出端口

(5)主要代码

`include "defines.v" module IF_ID( // INPUT input wire rst, input wire clk, input wire[`InsAddrWidth] if_pc,
	input wire[`InsWidth] if_ins, input wire[5:0] stall, //OUTPUT output reg[`InsAddrWidth] id_pc,
	output reg[`InsWidth] id_ins ); always@(posedge clk) begin if(rst == `RstEnable)
		begin
			id_pc <= `ZeroWord; id_ins <= `ZeroWord;
		end
		else
		begin
			if(stall[1] == `ENABLE && stall[2] == `DISABLE)
			begin
				id_pc <= `ZeroWord; id_ins <= `ZeroWord;
			end
			else if(stall[1] == `DISABLE)
			begin
				id_pc <= if_pc;
				id_ins <= if_ins;
			end
		end
	end
endmodule
  • RegFiles部件

(1)模块结构:

(2)模块功能: RegFiles模块是通用寄存器组,用来存储数据用。在译码阶段,需要对寄存器进行数据读取,在这个模块中只实现数据的读取。

(3)实现思路: 通用寄存器组用在ID段的数据读取和WB段的数据写回。根据指令的不同rs与rt的读操作是不同的,有时候rt不做读操作,这个时候就需要两个使能信号来控制rs与rt的读操作当然在读数据时是需要提供数据地址的,也就是被读寄存器的地址。在写回操作中,需要写回地址以及写回数据,因为不同的指令是有可能没有写回操作的,所以写回动作也需要一个使能信号。输出就是读取的数据进行输出,有的指令在译码阶段只有一个数据输出。

(4)引脚及控制信号: rst:对部件进行复位操作,一位的输入端口 clk:时钟信号,一位的输入端口 wrDataAddr:WB段要写回的数据,32位输入端口 wrData:WB段需要写回的数据,32位的输入端口 reData1Addr:译码时需要读取的rs寄存器地址,5位的输入端口 reData2Addr:译码时需要读取的rt寄存器地址,5位的输入端口 ren1:控制读rs寄存器的使能信号,一位的输入端口 ren2:控制读rt寄存器的使能信号,一位的输入端口 wrn:控制写回的使能信号,一位的输入端口 reData1:读取的rs寄存器的数据,32位的输入端口 reData2:读取的rt寄存器的数据,32位的输入端口

(5)主要代码

`include "defines.v" module RegFiles( // INPUT input wire rst, input wire clk, input wire wrn, input wire[`RegAddrBus] wrDataAddr,
	input wire[`RegDataBus] wrData, input wire ren1, input wire[`RegAddrBus] reData1Addr,
	
	input wire ren2,
	input wire[`RegAddrBus] reData2Addr, // OUTPUT output reg[`RegDataBus] reData1,
	output reg[`RegDataBus] reData2 ); // define all registers reg[`RegDataBus] regFiles[0:`RegFilesNum-1]; integer i; // init all registers to be zero initial begin for(i = 0; i < `RegFilesNum; i = i + 1)
			regFiles[i] = `ZeroWord; end // write operation always@(posedge clk) begin if(rst == `RstDisable && wrn == `WriteEnable && wrDataAddr != `RegFilesNumLog2'b0)
		begin
			regFiles[wrDataAddr] <= wrData;
		end	
	end


	// read operation1
	always@(*)
	begin
		if(rst == `ENABLE) reData1 <= `ZeroWord;
		else if(wrn == `ENABLE && wrDataAddr == reData1Addr && ren1 == `ENABLE)
			reData1 <= wrData;
		else if(ren1 == `ENABLE) reData1 <= regFiles[reData1Addr]; else reData1 <= `ZeroWord;
	end
	
	// read operation2
	always@(*)
	begin
		if(rst == `ENABLE) reData2 <= `ZeroWord;
		else if(wrn == `ENABLE && wrDataAddr == reData2Addr && ren2 == `ENABLE)
			reData2 <= wrData;
		else if(ren2 == `ENABLE) reData2 <= regFiles[reData2Addr]; else reData2 <= `ZeroWord;
	end
endmodule
  • ID部件

(1)模块结构:

(2)模块功能: 对指令进行译码,相当于ID段的全部工作。

(3)实现思路: ID段除去流水寄存器,一共就两个部件,一个是通用寄存器组RegFiles一个就是这个ID部件。简单来说这两个部件仿佛从属关系,ID部件给RegFiles提供工作的信号,RegFiles输出的结果返还给ID。在ID部件中,他需要解决数据冒险和控制冒险,关于这两个冒险的解决方式在前面已经讲述,在这不在讲述。除去冒险的解决外,ID部件还需要对输入的指令进行译码,然后进行6位操作码的比较,也就是对32位指令进行分割等。其实在这个段中最主要的就是冒险的解决,其他的和正常的流水线没时候区别。 (4)引脚及控制信号: rst:复位信号,一位的输入端口 Inst:上一级传来的指令,32位的输入端口 Pc:上一级传来的pc值,32位的输入端口 reData1_in:RegFiles部件传来读取的寄存器数据,32位的输入端口 reData2_in:RegFiles部件传来读取的寄存器数据,32位的输入端口 ex_wrAddr:EX段数据要写回的地址,5位的输入端口 ex_wrData:EX段要写回的数据,32位的输入端口 mem_wrAddr:MEM段数据要写回的地址,5位的输入端口 mem_wrData:MEM段要写回的数据,32位的输入端口 Last_alu_op:指令的子类型,8位的输入端口 ex_wrn:EX段数据的写信号,一位的输入端口 mem_wrn:MEM段数据的写信号,一位的输入端口 delaylotEn:指出当前的指令是否位于延迟槽,一位的输入端口 ren1:送给RegFiles的rs读信号,一位的输出端口 Ren2:送给RegFiles的rt读信号,一位的输出端口 ReData1Addr:送给RegFiles的rs寄存器地址,5位的输出端口 reData2Addr:送给RegFiles的rt寄存器地址,5位的输出端口 wrDataAddr:往下一级传的写回地址,有ID段产生,5位的输出端口 regData1_out:rs寄存器数据的输出胡,32位的输出端口 regData2_out:rt寄存器数据的输出胡,32位的输出端口 Alu_op:译码阶段的指令要进行运算的子类型,8位的输出端口 Alu_sel:译码阶段的指令要进行运算的类型,3位的输出端口 branchAddr:分支转移地址,32位的输出端口 Inst_o:往下一级传的指令,32位的输出端口 branchEN:分支跳转信号,一位的输出端口 next_delayslotEn:标示下一条进入译码阶段的指令是否处于延迟槽,一位的输出端口 stall_rep:请求流水线暂停信号,一位的输出端口 wrn:写回地址的写信号,一位的输出端口

  • ID_EX部件

(1)模块结构:

(2)模块功能: ID段与EX段之间的流水线寄存器。

(3)实现思路: 保存上一级ID段产生的数据,包括读取寄存器的数据,分割指令得到的操作码数据,指令数据,写回地址和写回信号等数据。在这个模块中有两点值得一提。一是输入信号stall,这个是当出现流水线暂停时(LW指令数据冲突)关闭该寄存器,延迟一个时钟周期在视情况继续执行;二是id_next_delayloyEn信号,这个是用来实现控制冒险检测的,他在输出端有个ew_next_delaylotEn信号,这个实在下一时钟上升沿到来时将信号返回给ID段以达到解决控制冲突。

(4)引脚及控制信号: rst:复位信号,一位的输入端口 Inst:上一级传来的指令,32位的输入端口 Clk:时钟信号,一位的输入端口 id_reg1Data:读取的rs寄存器数据,32位的输入端口 id_reg2Data:读取的rt寄存器数据,32位的输入端口 id_alu_op:操作码,8位的输入端口 id_alu_sel:进行运算的类型,3位的输入端口 id_wrAddr:写回地址,5位的输入端口 Stall:暂停流水线控制信号,6位的输入端口 id_wrn:写回信号,一位的输入端口 id_next_delaylotEn:当前的指令是否处在延迟槽,一位的输入端口 ex_reg1Data:读取的rs寄存器数据,32位的输出端口 ex_reg2Data:读取的rs寄存器数据,32位的输出端口 ex_alu_op:操作码,8位的输出端口 ex_alu_sel:进行运算的类型,3位的输出端口 ex_wrAddr:回地址,5位的输出端口 ex_inst:上一级传来的指令,32位的输出端口 ew_wrn:写回信号,一位的输出端口 ex_next_delaylotEn:当前的指令是否处在延迟槽,一位的输出端口

(5)主要代码

`include "defines.v" module ID_EX( //input input wire clk, input wire rst, input wire[`InsDataBus] id_inst,
	
	input wire[`RegDataBus] id_reg1Data, input wire[`RegDataBus] id_reg2Data,
	
	input wire[`ALUOpBus] id_alu_op, input wire[`ALUSelBus] id_alu_sel,

	input wire[`RegAddrBus] id_wrAddr, input wire id_wrn, input wire id_next_delayslotEn, input wire[5:0] stall, //output output reg[`RegDataBus] ex_reg1Data,
	output reg[`RegDataBus] ex_reg2Data, output reg[`ALUOpBus] ex_alu_op,
	output reg[`ALUSelBus] ex_alu_sel, output reg[`RegAddrBus] ex_wrAddr,
	output reg ex_wrn,

	output reg ex_next_delayslotEn,
	output reg[`InsDataBus] ex_inst ); always@(posedge clk) begin if(rst == `ENABLE)
		begin
			ex_reg1Data <= `ZeroWord; ex_reg2Data <= `ZeroWord;
			ex_alu_sel <= `ALU_NOP; ex_alu_op <= `alu_op_nop;
			ex_wrn <= `DISABLE; ex_wrAddr <= 5'b00000; ex_next_delayslotEn <= `DISABLE;
			ex_inst <= `ZeroWord; end else begin if(stall[2] == `ENABLE && stall[3] == `DISABLE) begin ex_reg1Data <= `ZeroWord;
				ex_reg2Data <= `ZeroWord; ex_alu_sel <= `ALU_NOP;
				ex_alu_op <= `alu_op_nop; ex_wrn <= `DISABLE;
				ex_wrAddr <= 5'b00000;
				ex_next_delayslotEn <= `DISABLE; ex_inst <= `ZeroWord;
			end
			else if(stall[2] == `DISABLE)
			begin
				ex_reg1Data <= id_reg1Data;
				ex_reg2Data <= id_reg2Data;
				ex_alu_op <= id_alu_op;
				ex_alu_sel <= id_alu_sel;
				ex_wrn <= id_wrn;
				ex_wrAddr <= id_wrAddr;
				ex_next_delayslotEn <= id_next_delayslotEn;
				ex_inst <= id_inst;
			end
		end

	end
		
endmodule
  • EX部件

(1)模块结构

(2)模块功能 实现不同指令的执行。

(3)实现思路 接收来自上一级的寄存器数据和操作码类型,然后根据操作码类型判断做何种运算,将运算结果传给下一级。

(4)引脚及控制信号 rst:复位信号,一位的输入端口 Inst:上一级传来的指令,32位的输入端口 wrn_i:写回使能信号,一位的输入端口 reg1Data:来自上一级的第一个操作数,32位的输入端口 Reg2Data:来自上一级的第一个操作数,32位的输入端口 wrAddr_i:写回地址,5位的输入端口 alu_op:运算的子类型,8位的输入端口 alu_sel:运算类型,3位的输入端口 wrAddr_o:写回地址,5位的输出端口 wrn_o:写回使能信号,一位的输出端口 result:运算结果,32位的输出端口 alu_op_o:运算的子类型,8位的输出端口 mem_addr_o:运算结果要写入MEM的地址,32位的输出端口 mem_data_o:运算结果要写入MEM的数据,32位的输出端口

  • EX_MEM部件

(1)模块结构:

(2)模块功能: 该模块是EX段与MEM段之间的流水寄存器,用来隔开两段之间的处理工作。

(3)实现思路: 接收上一级EX段产生的数据结果。接收的数据有两类,一类是ALU行的计算结果,一类是访存数据。访存数据要包括要访问的地址以及要存储的数据,当然这个需要操作码来判断做何种操作。

(4)引脚及控制信号: rst:复位信号,一位的输入端口 clk:时钟信号,一位的输入端口 wrn_i:写回使能信号,一位的输入端口 wrAddr_i:要写回的寄存器地址,5位的输入端口 result_i:EX段计算结果,32位输入端口 stall:暂停流水线信号,6位的输入端口 alu_op_i:运算的子类型字段,8位的输入端口 mem_addr_i:访存指令要访问的地址,32位输入端口 mem_data_i:要存储在存储器中的数据,32位的输入端口 wrn_o:写回使能信号,一位的输出端口 wrAddr_o:要写回的寄存器地址,5位的输出端口 result_o:EX段计算结果,32位输出端口 alu_op_o:运算的子类型字段,8位的输出端口 mem_addr_o:访存指令要访问的地址,32位输出端口 mem_data_o:要存储在存储器中的数据,32位的输出端口

(5)主要代码

`include "defines.v" module EX_MEM( // input input wire clk, input wire rst, input wire wrn_i, input wire[`RegAddrBus] wrAddr_i,
	input wire[`RegDataBus] result_i, input wire wrn_HILO_i, input wire[`RegDataBus] wrData_HI_i,
	input wire[`RegDataBus] wrData_LO_i, input wire[5:0] stall, // mem input wire[`ALUOpBus] alu_op_i,
	input wire[`RegDataBus] mem_addr_i, input wire[`RegDataBus] mem_data_i,

	
	//output
	output reg wrn_o,
	output reg[`RegAddrBus] wrAddr_o, output reg[`RegDataBus] result_o,

	output reg wrn_HILO_o,
	output reg[`RegDataBus] wrData_HI_o, output reg[`RegDataBus] wrData_LO_o,

	//mem
	output reg[`ALUOpBus] alu_op_o, output reg[`RegDataBus] mem_addr_o,
	output reg[`RegDataBus] mem_data_o ); always@(posedge clk) begin if(rst == `ENABLE)
		begin
			wrn_o <= `DISABLE; wrAddr_o <= 5'b00000; result_o <= `ZeroWord;
			wrn_HILO_o <= `DISABLE; wrData_HI_o <= `ZeroWord;
			wrData_LO_o <= `ZeroWord; alu_op_o <= `ALU_OP_NOP;
			mem_addr_o <= `ZeroWord; mem_data_o <= `ZeroWord;
		end
		else
		begin
			if(stall[3] == `ENABLE && stall[4] == `DISABLE)
			begin
				wrn_o <= `DISABLE; wrAddr_o <= 5'b00000; result_o <= `ZeroWord;
				wrn_HILO_o <= `DISABLE; wrData_HI_o <= `ZeroWord;
				wrData_LO_o <= `ZeroWord; alu_op_o <= `ALU_OP_NOP;
				mem_addr_o <= `ZeroWord; mem_data_o <= `ZeroWord;
			end
			else if(stall[3] == `DISABLE)
			begin
				wrn_o <= wrn_i;
				wrAddr_o <= wrAddr_i;
				result_o <= result_i;
				wrn_HILO_o <= wrn_HILO_i;
				wrData_HI_o <= wrData_HI_i;
				wrData_LO_o <= wrData_LO_i;
				alu_op_o <= alu_op_i;
				mem_addr_o <= mem_addr_i;
				mem_data_o <= mem_data_i;
			end
		end
	end

endmodule
  • DATAMEM部件

(1)模块结构:

(2)模块功能: 该模块是作为数据存储器来使用的。

(3)实现思路: MEM段有两个模块,一个是dataMem模块,一个是MEM模块。dataMem模块收到来自MEM模块的访存地址信号和需要的存储数据,除此之外还需要一个信号来控制是存储数据还是读取数据。

(4)引脚及控制信号: clk:时钟信号,一位的输入端口 ce:使能信号,判断存储还是读取,一位的输入端口 wrn:使能信号,存储信号有效为1,一位的输入端口 mem_addr:要访存的地址,32位的输入端口 mem_datai:要存储的数据,32位的输入端口 mem_data_o:要读取的数据,32位的输出胡端口

(5)主要代码

`include "defines.v" module dataMem( input wire clk, input wire ce, input wire wrn, // write en input wire[`DMAddrBus] mem_addr,
	input wire[`DMDataBus] mem_data_i, // output output reg[`DMDataBus] mem_data_o
);

	reg[`ByteWidth] DataMEM[0:`DMUnitNum];

	integer i;
	initial
	begin
		for(i = 0; i < `DMUnitNum; i=i+1) begin DataMEM[i] = 8'b0; end end always@(clk) begin if(ce == `ENABLE)
		begin
			if(wrn == `ENABLE) begin { 
    DataMEM[mem_addr],DataMEM[mem_addr+1],DataMEM[mem_addr+2],DataMEM[mem_addr+3]} <= mem_data_i; end end end always@(*) begin if(ce == `DISABLE)
		begin
			mem_data_o <= `ZeroWord;
		end
		else
		begin
			mem_data_o <= { 
   DataMEM[mem_addr],DataMEM[mem_addr+1],DataMEM[mem_addr+2],DataMEM[mem_addr+3]};
		end
	end
endmodule
  • MEM部件

(1)模块结构:

(2)模块功能: 对数据存储器进行访问,包括控制存储数据和读取数据。

(3)实现思路: 收到上一级传来的数据信号,包括ALU计算的结果,要访问存储器的地址,以及要存储在数据存储器中的数据。除此之外还需要接收操作码信号,因为要用来判断是lw指令还是sw指令。在输入信号端有一个存储器读取的数据,是DATAMEM模块的返回数据。该模块的输出端则提供DATAMEM模块的输入信号。

(4)引脚及控制信号: rst:复位信号,一位的输入端口 wrn_i:写回的使能信号,一位的输入端口 result_i:上一级的ALU计算结果,32位的输入端口 wrAddr_i:写回的寄存器地址,5位的输入端口 alu_op_i:操作码字段,8位的输入端口 mem_wrdata_i:存储器写数据,32位的输入端口 mem_addr_i:要访问的存储器地址,32位的输入端口 mem_redata_i:读取的存储器数据,32位的输入端口 wrn_o:写回使能信号,一位的输出端口 result_o:上一级的ALU计算结果,32位的输出端口 wrAddr_o:写回的寄存器地址,5位的输出端口 mem_wrn:使能信号,存储信号有效为1,一位的输出端口 mem_ce:使能信号,判断存储还是读取,一位的输出端口 mem_wraddr:要访存的地址,32位的输出端口 mem_wrdata:要存储的数据,32位的输出端口

(5)主要代码

`include "defines.v" module MEM( //input input wire rst, input wire[`RegDataBus] result_i,
	input wire wrn_i,
	input wire[`RegAddrBus] wrAddr_i, input wire wrn_HILO_i, input wire[`RegDataBus] wrData_HI_i,
	input wire[`RegDataBus] wrData_LO_i, input wire[`ALUOpBus] alu_op_i,
	input wire[`RegDataBus] mem_wrdata_i, input wire[`RegDataBus] mem_addr_i,


	// input from dataMem
	input wire[`RegDataBus] mem_redata_i, //output output reg wrn_o, output reg[`RegDataBus] result_o,
	output reg[`RegAddrBus] wrAddr_o, output reg wrn_HILO_o, output reg[`RegDataBus] wrData_HI_o,
	output reg[`RegDataBus] wrData_LO_o, //output to dataMem output reg mem_wrn, output reg mem_ce, output reg[`RegDataBus] mem_wraddr,
	output reg[`RegDataBus] mem_wrdata ); always@(*) begin if(rst == `ENABLE)
		begin
			wrn_o <= `DISABLE; wrAddr_o <= 5'b00000; result_o <= `ZeroWord;
			wrn_HILO_o <= `DISABLE; wrData_HI_o <= `ZeroWord;
			wrData_LO_o <= `ZeroWord; // about mem mem_wrn <= `DISABLE;
			mem_ce <= `DISABLE; mem_wraddr <= `ZeroWord;
			mem_wrdata <= `ZeroWord; end else begin wrn_o <= wrn_i; wrAddr_o <= wrAddr_i; result_o <= result_i; wrn_HILO_o <= wrn_HILO_i; wrData_HI_o <= wrData_HI_i; wrData_LO_o <= wrData_LO_i; // about mem mem_wrn <= `DISABLE;
			mem_ce <= `DISABLE; mem_wraddr <= `ZeroWord;
			mem_wrdata <= `ZeroWord; case(alu_op_i) `ALU_OP_SW:
					begin
						mem_wrn <= `ENABLE; mem_ce <= `ENABLE;
						mem_wraddr <= mem_addr_i;
						mem_wrdata <= mem_wrdata_i;
					end
				`ALU_OP_LW: begin mem_wrn <= `DISABLE;
						mem_ce <= `ENABLE;
						mem_wraddr <= mem_addr_i;
						result_o <= mem_redata_i;
					end
				default:
					begin
					end
			endcase
		end

	end
endmodule
  • MEM_WB部件

(1)模块结构:

(2)模块功能: 该模块是MEM段与WB段之间的流水线寄存器。

(3)实现思路: 为WB段的写回做储备,所以接收的是MEM段的写回数据,写回地址以及写回使能信号。

(4)引脚及控制信号: clk:时钟信号,一位的输入端口 rst:复位信号,一位的输入端口 wrn_i:写回的使能信号,一位的输入端口 wrAddr_i:写回的使能信号,一位的输入端口 wrData_i:写回的数据,32位的输入端口 stall:暂停流水线的信号,6位的输入端口 wrn_o:写回的使能信号,一位的输出端口 wrAddr_o:写回的使能信号,一位的输出端口 wrData_o:写回的数据,32位的输出端口

(5)主要代码

`include "defines.v" module MEM_WB( //input input wire clk, input wire rst, input wire wrn_i, input wire[`RegAddrBus] wrAddr_i,
	input wire[`RegDataBus] wrData_i, input wire wrn_HILO_i, input wire[`RegDataBus] wrData_HI_i,
	input wire[`RegDataBus] wrData_LO_i, input wire[5:0] stall, //output output reg wrn_o, output reg[`RegAddrBus] wrAddr_o,
	output reg[`RegDataBus] wrData_o, output reg wrn_HILO_o, output reg[`RegDataBus] wrData_HI_o,
	output reg[`RegDataBus] wrData_LO_o ); always@(posedge clk) begin if(rst == `ENABLE)
		begin
			wrn_o <= `DISABLE; wrAddr_o <= 5'b00000; wrData_o <= `ZeroWord;
			wrn_HILO_o <= `DISABLE; wrData_HI_o <= `ZeroWord;
			wrData_LO_o <= `ZeroWord; end else begin if(stall[4] == `ENABLE && stall[5] == `DISABLE) begin wrn_o <= `DISABLE;
				wrAddr_o <= 5'b00000;
				wrData_o <= `ZeroWord; wrn_HILO_o <= `DISABLE;
				wrData_HI_o <= `ZeroWord; wrData_LO_o <= `ZeroWord;
			end
			else if(stall[4] == `DISABLE)
			begin
				wrn_o <= wrn_i;
				wrAddr_o <= wrAddr_i;
				wrData_o <= wrData_i;
				wrn_HILO_o <= wrn_HILO_i;
				wrData_HI_o <= wrData_HI_i;
				wrData_LO_o <= wrData_LO_i;
			end
		end
	end
endmodule
  • ctrl部件

(1)模块结构:

(2)模块功能: 提供6位的暂停信号,作用于pc模块以及五段流水。

(3)实现思路: 当在ID段出现LW指令的数据冒险时,ID段会发出一个请求暂停信号,ctrl模块接收这个信号后就会产生一个6位的暂停信号。stall初始值是000000,在接收到请求暂停信号id_stall后就会变为111000,作用于前面各个模块。

(4)引脚及控制信号: rst:复位信号,一位的输出端口 id_stall:请求暂停信号,一位的输出端口 stall:暂停信号,6位的输出端口

(5)主要代码

`include "defines.v" module ctrl( input wire rst, input wire id_stall, //input wire ex_stall, output reg[5:0] stall ); always@(*) begin if(rst == `ENABLE)
		begin
			stall <= 6'b000000; end else begin if(id_stall == `ENABLE) begin stall <= 6'b000111;
			end
			else
			begin
				stall <= 6'b000000;
			end
		end
	end

endmodule
  • MIPS_32CPU部件

(1)模块结构:

(2)模块功能: 这个文件是封装了除指令存储器INSMEN模块和数据存储区DATAMEM模块之外的所有模块。

(3)实现思路: 将cpu分成三个部件,一个是MIPS_32CPU,一个是DATAMEM,一个是INSMEM,这三个模块之间的信号是互连的。

(4)引脚及控制信号: clk:时钟信号,一位的输入端口 rst:复位信号,一位的输入端口 inst:指令代码,32位IDE输入信号 datamem_mem_redata: insAddr_o:pc值,32位的输出信号 insEn:控制INSMEM模块的使能信号,一位的输出端口 mem_datamem_wrn:数据存储器写数据,32位的输出端口 mem_datamem_ce:使能信号,判断数据存储器是存储还是读取,一位的输出端口 mem_datamem_addr:数据存储器的访存地址,32位的输出端口 mem_datamem_wrdata:写入数据存储器的数据,32位的输出端口

  • MIN_SOPC部件

(1)模块结构:

(2)模块功能: 对MIPS_32CPU模块、INSMEM模块和DATAMEM模块的封装。

(3)实现思路: 简单的封装操作。

(4)引脚及控制信号: clk:时钟信号,一位的输出端口 rst:复位信号,一位的输出端口

(5)主要代码

`include "defines.v" module min_sopc( // input input wire clk, input wire rst ); wire insEn; wire[`InsDataBus] inst;
	wire[`InsAddrBus] instAddr; wire[`RegDataBus] datamem_mem_redata;
	wire mem_datamem_wrn;
    wire mem_datamem_ce;
    wire[`RegDataBus] mem_datamem_addr; wire[`RegDataBus] mem_datamem_wrdata;

	//OpenMips OpenMips_0
	MIPS_32CPU MIPS32
	(
		// input
		.clk(clk),
		.rst(rst),
		.inst(inst),
		
		// output
		.insAddr_o(instAddr),
		.insEn(insEn),
		.datamem_mem_redata(datamem_mem_redata),
	    .mem_datamem_wrn(mem_datamem_wrn),
        .mem_datamem_ce(mem_datamem_ce),
        .mem_datamem_addr(mem_datamem_addr),
        .mem_datamem_wrdata(mem_datamem_wrdata)
	);

	insMem insMem_0
	(
		//input
		.insAddr(instAddr),
		.insEn(insEn),
		
		// output
		.inst(inst)
	);

	// dataMemory
	dataMem DATAMEM
	(
		.clk(clk),.ce(mem_datamem_ce), .wrn(mem_datamem_wrn),
		.mem_addr(mem_datamem_addr), .mem_data_i(mem_datamem_wrdata),
		.mem_data_o(datamem_mem_redata)
	);
endmodule
  • 仿真代码
`include "defines.v" `timescale 1ns/1ns

module testBench();
	reg CLOCK;
	reg rst;

	initial
	begin
		CLOCK = 1'b0;
		forever #12 CLOCK = ~CLOCK;
	end
	
	initial
	begin
		rst = `ENABLE; #20 rst = `DISABLE;
		#1000 $stop;
	end

	min_sopc min_sopc_0
	(	
		.clk(CLOCK),
		.rst(rst)
	);
endmodule

六、测试程序

七、结果分析

7.1:数据结果分析 以下跑的是第一个测试程序: (1)pc值的变化 选择观察信号:时钟信号clk 、pc部件的输出端口pc信号 结果:

分析:根据实验数据可以发现,pc值都是随着clk正常更新的。比如在第二张图中。第一个红色箭头指的地方是测试程序中的第5条指令,该指令是当条件满足时发生跳转,从程序中可以看出条件是满足的要发生跳转。第五条指令的pc是00000010,当发生跳转时,下一条指令00000014是不执行的,但是在程序中单判断出现跳转时,第六条指令以及取出来了,pc值已经加4,但是他是不执行的状态。随意pc值在下个时钟周期上升沿应该更新加8,也就是跳转到0000001C处开始执行,从图中可以看出确实是这样的。

(2)指令的流动 为了更好的证实上面pc变化的正确性,现在来观察下指令的流动。 选择观察信号:时钟信号clk 、insMem部件的输出端inst信号 结果:

分析:可以看出指令是正常流动的,在第15个周期后既没有指令流出了,因为我没有设置指令的循环。在看第二张图红色箭头标注的地方,这是对上面pc值变动的验证。第5条指令的指令码是10640002,可以看到正好对应第一个箭头所指的地方。然后发生跳转,第6条指令虽然被取出但是没有被执行,第7条指令直接没有被取出来,调到了第8条指令00223022那执行,这个和图中的信息都是对应的。

(3)计算的结果 观察下每条指令的执行结果。 选择观察信号:时钟信号clk 、EX部件的输出端result信号 、DATAMEM 部件的输入端mem_data_i信号以及输出端mem_data_o 信号。 结果: 图一:

图二:

图三:

分析:先看图一,在图一中有两个箭头,一个指向时钟信号,一个指向EX段的result端口的输出值。可以看到,result端口在是在第三个时钟周期输出数据的,这正好是第一条指令EX段散发出数据的时间,在测试程序中第一条指令就是给寄存器$1赋值为10,仿真数据是对的,对比后面的数据也都是正确的。在看图二,在图二中可以看到EX段的result端口是有一段没有数据输出的,这是因为在测试程序中这段是在执行访存指令,即LW和SW指令,在指令执行完后就出现了最后一条指令的计算结果,因此可以看出EX段的功能是正确的。然后看图三,图三中主要是针对访存指令的分析,看蓝色箭头指向,这是说明在没有执行访存指令时,数据存储器DATAMEM是不在工作的,在测试程序中先出现了SW写入存储器指令,写入值为3,在仿真图中可以看到DATAMEM的mem_data_i输入端有信号值为3,接着是LW取值信号,取数据为3,在图中可以看到mem_data_o输出端的信号值是3,再过一个周期后,值被传递走就回复为0状态,这些都说明了MEM段功能的正确性。

7.2:冒险处理分析 以下跑的是第一个测试程序: (1)定向技术结果分析 观察WB段与ID段的数据冒险。 选择观察信号:时钟信号clk 、ID部件的输入端inst信号、EX部件的输出 端result信号 结果:

分析:在测试程序中,第四条指令(指令码为00222020)与第二条指令(指令码为20020003)发生数据冲突,也就是在上面的左边的箭头所指的位置。第二个冲突就是在第四条指令与第五条指令(指令码为10640002)发生数据冲突,也就是上面的右边箭头所指的位置。当初想数据冲突时,如果没有旁路的话,他会出现错误的结果,但是对比EX部件的输出端result信号的值会发现值是正确的,也不存在延迟产生的情况,所以说旁路的设计很好的解决了数据冲突问题。 但是旁路解决数据冲突的功能是有限的,当出现LW数据冲突时,旁路是解决不了问题的,因为在ID段就需要数据,但是LW取出的数据在MEM段快结束时才会有,旁路并不能将数据指向ID段,这个时候就需要暂停机制来解决这个问题。

(2)LW数据冒险分析 上面已经说明,当出现LW数据冒险时,会产生一个stall信号来暂停相应的部件。 选择观察信号:时钟信号clk 、IF_ID部件的输出端id_ins信号、DATAMEM 部件的输出端mem_data_o信号 结果:

分析:在测试程序中的第11条指令是LW取数据指令,然后在第12条指令要用到LW取出的数据,这样就出现了旁路所不能解决的数据冲突。看图中,当出现LW指令时(指令码为8D04000A),后面的一条指令因为要用到lw取出的数据而被暂停一个周期,从图中就可以看出来,10440001的指令码占用了两个字段。

(3)控制冒险结果分析 选择观察数据:时钟信号clk 、ID部件输入端delayslotEn信号以及输出 端next_delayslotEn信号、ID_EX部件输出端 ex_next_delayslotEn信号,insMem部件的输出端inst信 号。 结果: 图一:

图二:

分析:在ID段检测到跳转指令后会发出一个信号,该信号会进入ID_EX流水寄存器,但是在下一时钟周期上升沿到来后又会返回到ID部件,作用是通知当前在延迟槽的指令无用,使其变为空操作不输出。在仿真图中就可以看出当出现成功跳转时,next_delayslotEn信号会变成高电平(测试程序中一共是三条跳转指令,其中只有两条发生了跳转,有一条判断条件不成立所以未进行跳转)。当出现跳转时已经进入到延迟槽中的指令就会被作为空指令来执行,然后pc值会被更改为跳转地址,在仿真图中也有体现。

7.3:J跳转分析 以下跑的是第一个测试程序: 选择观察数据:时钟信号clk 、insMem部件的输入端insAddr信号以及输 出端inst信号。 结果:

分析:跳转指令J是无条件跳转,在第5跳转指令执行完后(该指令为有条件跳转)会去执行第8条指令,也就是J型跳转指令,指令会直接跳转到第三条指令处执行,之后会进入这个循环的执行环境中,仿真结果与理论是一样的。

八:参考资料

参考课本有《自己动手写CPU》

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/171498.html原文链接:https://javaforall.cn