您的位置:首页 > 其它

Verilog实现IMPS的5级流水线cpu设计(Modelsim仿真)

2020-03-01 11:19 309 查看

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

本篇文章是在功能上实现cpu设计,而非结构上实现。结构上实现可以跳转到(此为个人推荐):
Verilog流水线CPU设计(超详细)
此外有与本文章配套的资源,文章中不懂的地方可以跳转到:
IMPS五级流水线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指令格式

四:模块设计

  • 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

注:defines.v 是一个宏文件。

  • 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)主要代码

  • 代码太多,不易粘贴,详情见代码文件,在文章首部有说

  • 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:当前的指令是否处在延迟槽,一位的输出端口

  • 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位的输出端口

  • DATAMEM部件

(1)模块结构:

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

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

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

  • 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位的输出端口

  • 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位的输出端口

  • ctrl部件

(1)模块结构:

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

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

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

  • 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:复位信号,一位的输出端口

六、测试程序

七:参考资料

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

  • 点赞 1
  • 收藏
  • 分享
  • 文章举报
please tell me 发布了11 篇原创文章 · 获赞 2 · 访问量 385 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: