CPU设计实战 第七章例外和中断的支持

引言

引入例外是为了解决处理器运行过程中的一些意外情形,比如非法指令,访问了没有映射的虚拟地址等。中断的引入则是提供一种I/O设备请求处理器服务的一种通讯机制,往往是由外部设备给处理器的中断引脚一个信号,处理器接收到信号后跳转到特定入口处执行。MIPS统称例外和中断为例外。

  1. 外中断:狭义上的中断指的就是外中断,就是外设请求或者人为干预等当前执行指令之外的因素所引起的中断(与当前执行指令无关)。
  2. 内中断:也常被称为例外,指的是CPU执行当前指令时所发生的中断,一般分为两种,一是自愿中断,比如当前执行的指令为陷阱(trap)指令。二是强迫中断,即当前指令在执行时发生的错误所引起的中断,例如地址越界、算术溢出、整数除0等等。
    例外处理的绝大部分工作是由例外处理程序(软甲)完成的,但是例外的开始和结束阶段必须由硬件来完成。
    (1)开始阶段
    例外触发条件的判断由硬件完成,例外的类型、触发例外的指令的PC等供例外处理程序使用的信息由硬件自动保存。此外,硬件需要跳转到例外处理程序的入口执行,并保证跳转到例外入口后,处理器处于高特权等级。随后,例外处理程序接管后续处理过程。
    (2)结束阶段
    例外处理程序完成所有处理之后,需要返回发生例外的指令(或者一个事前指定的程序人口)处重新开始执行,这个过程除了要执行一次跳转外,还需要将处理器的特权等级调整回最初发生例外的指令所处运行环境的特权等级。

1. 实现的例外种类

1. 1 地址错例外

地址错例外的检测逻辑与取指和访存两部分相关。我们可以在取指级对取指所用的PC的最低两位进行判断,如果不是2’b00的话,说明取指出现了地址错例外(AdEL)。同样,我们可以在访存级对访存操作类型以及访存地址的低位进行判断。如果是LW操作且地址最低两位不等于 2’b00,或者是LH、LHU操作且地址最低位不等于1’b0,那么意味着出现了地址错例外(AdEL);如果是SW操作且地址最低两位不等于2’b00,或者是SH操作且地址最低位不等于1’b0,那么表示出现了地址错例外(AdES)。

1.2 整型溢出例外

ADD、ADDI和SUB这三条指令在计算过程中发生溢出

1.3 系统调用例外

执行SYSCALL指令就会触发系统调用例外,所以一旦在译码级识别出SYSCALL指令,就可以认为发生了系统调用例外。

1.4 断点例外

执行BREAK 指令就会触发断点例外,所以一旦在译码级识别出BREAK指令,就可以认为发生了断点例外。

1.5 保留指令例外

如果译码时发现指令码不是一条指令系统规范中定义的指令,那么就认为发生了保留指令例外。

1.6 中断例外

包括硬件中断、软件中断和计时器中断。

2. 精确例外

当系统软件处理完例外返回后,对于发生异常的指令前面的所有指令都要被执行到流水线的最后一个阶段,也就是正常执行完成,但是该指令及该指令之后的指令都要被取消,就像从来没有执行过一样。以下图为例,第2条指令 add在执行阶段发生溢出例外,在这种情况下,已经到达访存阶段的第 1 条指令 ori 会继续执行完成,而第2、3、4条指令都会被取消,不会有任何影响处理器的情况发生,就像没有进过流水线一样。
在这里插入图片描述
为了实现精确异常,必须要求例外发生的顺序与指令的顺序相同,但是对于拥有流水线的处理器,不同类型的例外会在流水线的不同阶段发生。例如下图,LW指令在时钟周期4发生地址错例外,而第二条指令在时钟周期3发生了保留指令例外,如果立即处理例外,就违背了精确例外的要求。故我们考虑先发生的例外并不立即处理,只是被标记,并继续运行流水线,到达指定流水级再报出例外。
在这里插入图片描述
选择报出例外的流水级的原则是,在该级之后的流水级不能产生新的例外(比如报出例外的流水级设定为执行级,则要求访存级和写回级不能产生或标记上新的例外),否则就违反了精确例外的要求。本书设计指定在写回级报出例外,由于例外发生的判断逻辑分布在各流水级,发现例外后将例外信息附着在指令上沿流水线一路携带下去,直至写回级才真正报出例外,此时才会根据所携带的例外信息更新CPO寄存器;写回指令报出例外的同时,清空所有流水级缓存的状态,并将下一拍的PC置为例外入口地址。
通过以上方法就可以在流水线处理器中实现“按指令执行的顺序处理异常,而不是按异常发生的顺序处理异常处理”。

3. CP0寄存器

例外是一个软硬件协同的处理过程。在这个过程中,硬件逻辑电路和例外处理软件需要进行必要的信息交互,MIPS指令系统中定义了一组独立的寄存器用于这类信息的交互。据本章实现的例外种类来说,相关的CP0寄存器有Cause、EPC、Status、BadVAddr、Compare、Count。这些寄存器的详细定义如下

3.1 BadVAddr寄存器

在这里插入图片描述

3.2 Count寄存器

在这里插入图片描述

3.3 Compare寄存器

在这里插入图片描述

3.4 Stause寄存器

在这里插入图片描述

3.5 Cause寄存器

在这里插入图片描述

3.6 EPC寄存器

在这里插入图片描述
在这里插入图片描述

4. 处理器硬件响应例外的一般过程

1)当CPO.Status.EXL为0时,更新CPO.EPC:将例外处理返回后重新开始执行的指令PC填入CPO.EPC寄存器中。如果发生例外的指令不在分支延迟槽中,则重新开始执行的指令PC就等于发生例外的指令的PC,否则重新开始执行的指令PC等于发生例外的指令的PC-4(即该延迟槽对应的分支指令的PC)。
2)当EXL为0时,更新CPO.Cause寄存器的BD位:如果发生例外的指令在分支延迟槽中,则将CP0.Cause.BD置为1,否则将CPO.Cause.BD置为0。
3)更新CP0.Status.EXL位:将其置为1。
4)进入约定的例外入口重新取指,软件开始执行例外处理程序。
当软件完成例外处理程序的执行后,通常会使用ERET指令从例外处理程序返回,ERET指令的作用是:
1)将CPO.Status.EXL清零。
2)从CPO.EPC寄存器取出重新执行的PC值,然后从该PC值处继续取指执行。

5. 中断例外

处理器响应中断的必要条件:

  1. Status.IE = 1,表示全局中断使能开启
  2. Status.EXL = 0,表示没有例外正在处理
  3. 某个中断源产生中断且该中断源未被屏蔽(个中断源屏蔽位为Status.IM7~0)
    处理器支持2个软件中断(SW0 ~ SW1)、6个硬件中断(HW0 ~ HW5)和1个计时器中断。其中计时器中断复用HW5硬件中断。软件中断的中断源即为Cause.IP[1:0]两位,仅可以通过软件对Cause.IP[1:0]位写1进行触发,软件写0进行清除。
    计时器中断的中断源记录在Cause.TI位中,在Count[31:0]等于Compare[31:0]的情况下由硬件置1,软件只可通过写Compare寄存器间接清除CauseTI位记录的中断。
    硬件中断的中断源来自处理器外部,由硬件逐拍采样处理器接口上的6个中断输入引脚,软件需要反向遍历系统的中断路由路径,清除终端设备或路由路径上的中断状态,以此来清除处理器的硬件中断。除全局中断使能外,每个中断源各自有一个中断屏蔽位。

6. 相关指令

6.1 自陷指令(用户态→内核态)

(1) BREAK:触发断点例外
(2) SYSCALL:触发系统调用例外

6.2 特权指令

(1)ERET指令
在这里插入图片描述
(2)MFC0指令
在这里插入图片描述
(3) MTC0
在这里插入图片描述
有了以上的认知基础,我们接下来以CP0寄存器中的域为单位对CP0寄存器进行设计。

7. CP0模块

7.1 Stause寄存器

(1)BEV域

BEV这个域值恒为1,故可以把它看作一根具有固定值的线,即可写成:

wire cp0_status_bev;
assign cp0_status_bev = 1'b1;
(2)IM7 ~ IM0域

此域可读可写,意味着一方面这8个信号需要接入MFC0指令读CP0的数据通路中,另一个方面MTC0指令需要写这个寄存器,我们需要在回写阶段生成一个写CP0寄存器的写使能信号mtc0_we,并且假设MTC0指令写CP0寄存器的写地址为cp0_addr[7:0],写数据为cp0_wdata[31:0],代码如下

wire mtc0_we;
assign mtc0_we = wb_valid &&wb_ inst_mtc0 &&!wb_ex //放在回写级

reg [7:0] cp0_status_im;
always @ (posedge clk) begin
  if(mtc0_we && cp0_addr == `CR_STATUS)
      cp0_status_im <= cp0_wdata[15:8];
end
(3)EXL域

在下面四种情况下会发生写操作:
1)复位为0
2)MTC0指令写Staus寄存器
3)遇到ERET指令
4)任何指令报例外的时候
假设ERET指令修改EXL域的使能信号时eret_flush,写回级指令报例外的信号时wb_ex,EXL域维护代码如下:

reg cp0_status_exl;
always @ (posedge clk) begin
   if(reset)
      cp0_status_exl <= 1'b0;
   else if( wb_ex)
      cp0_status_exl <= 1'b1;
   else if(eret_flush)
      cp0_status_exl <= 1'b0;
   else if (mtc0_we && cp0_addr == `CR_STATUS)
      cp0_status_exl <= cp0_wdata[1];
end
(4)IE域

写来源有两个:
1)复位0
2)MTC0指令写Status寄存器
代码如下:

reg cp0_status_ie;
always @ (posedge clk) begin
 if(reset)
   cp0_status_ie <= 1'b0;
 else if (mtc0_we && cp0_addr == `CR_STATUS)
   cp0_status_ie <= cp0_wdata[0];
end

7.2 Cause寄存器

(1)BD域

该域软件不可写,只能在复位和指令报例外时更新,BD域仅在Status寄存器EXL域不为1时更新(当前没有正在处理的例外)。新增信号wb_bd,用于标识发生例外的指令是否在转移指令延迟槽中。

reg cp0_cause_bd; 
always @ (posedge clk) begin
  if(reset)
    cp0_cause_bd <= 1'b0;
 else if (wb_ex && !cp0_status_exl)
    cp0_cause_bd <= wb_bd;
end
(2)TI域

软件不可写,有3个更新来源:
1)复位清0
2)Count寄存器和Compare寄存器相等时置1
3)MTC0指令写Compare寄存器时清0
新增count_eq_compare信号作为Count寄存器与Compare寄存器相等比较的结果,那么TI域维护逻辑如下;

wire count_eq_compare;
assign count_eq_compare = (cp0_count == cp0_compare);
reg cp0_cause_ti; //计时器中断指示
always @ (posedge clk) begin
  if(reset) 
    cp0_cause_ti <= 1'b0;
  else if (mtc0_we && cp0_addr == `CR_COMPARE)
     cp0_cause_ti <= 1'b0;
  else if (count_eq_compare)
     cp0_cause_ti <= 1'b1;
end
(3)IP7 ~ IP2域

更新源只有两个:复位和采样中断信号输入,处理器核顶层的6个中断输入信号为ext_int_in[5:0],维护代码如下:

reg [7:0] cp0_cause_ip; //第0、1位为软件中断,只能通过软件更新。其余位为硬件中断且软件不可写
always @ (posedge clk) begin
   if(reset)
      cp0_cause_ip[7:2] <= 6'b0;  //待处理硬件标识
   else begin
      cp0_cause_ip[7] <= ext_int_in[5] | cp0_cause_ti;//硬件中断5号、计时器中断
      cp0_cause_ip[6:2] <= ext_int_in[4:0];
   end
end
(4)IP1 ~ IP0域

只能通过软件来更新,代码如下:

always @ (posedge clk) begin
   if(reset)
      cp0_cause_ip[1:0] <= 2'b0;  //待处理软件中断标识
   else if (mtc0_we && cp0_addr == `CR_CAUSE)
      cp0_cause_ip[1:0] <= cp0_wdata[9:8];
end
(5)Excode域

软件不可写,仅在报例外的时候填入例外编码,假设写回级的信号wb_excode[4:0]为例外编码,则代码如下:

reg [4:0] cp0_cause_excode;//指令报例外时填入例外编码
always @ (posedge clk) begin
   if(reset)
      cp0_cause_excode <= 5'b0;
   else if(wb_ex)
      cp0_cause_excode <= wb_excode;
end

7.3 EPC

EPC寄存器只有一个域,更新来源有两个:
1)MTC0指令写
2)报例外时硬件写入例外返回地址
对于第二点需要注意两个方面,一是Status寄存器EXL域等于1(当前有正在处理的例外),即是报出新的例外也不更新EPC寄存器以及Cause寄存器的BD域;二是报例外指令如果是转移延迟槽指令,那么需要记录到EPC寄存器的PC为该延迟槽对应的转移指令的PC。
假设写回级指令的PC存放在wb_pc中,EPC维护代码如下:

reg  [31:0] c0_epc;
always @ (posedge clk)begin
   if(wb_ex && !cp0_status_exl)
      c0_epc <= wb_bd ? wb_pc - 3'h4 : wb_pc;
   else if(mtc0_we && cp0_addr == `CR_EPC)
      c0_epc <= cp0_wdata;
end

7.4 BadVAddr寄存器

BadVAddr寄存器整体只有一个域,其更新来源只有一个,即报地址错例外时将导致出错的虚地址写入该寄存器。假设经过流水线缓存传递到写回级的导致出错的虚地址存放在wb_badvaddr中,那么BadVAddr寄存器的维护代码如下:

reg [31:0] c0_badvaddr;
always @(posedge clk) begin
    if((wb_ex && wb_excode == `EX_ADEL)||(wb_ex && wb_excode == `EX_ADES))
        c0_badvaddr <= wb_badvaddr;
end

7.5 Count寄存器

Count寄存器整体只有一个域,其更新来源有两个:
1)MTC0指令写CP0寄存器
2)硬件计数器
维护逻辑示意如下:

reg tick;
reg [31:0] c0_count;
always @ (posedge clk) begin
   if(reset) 
      tick <= 1'b0;
   else  tick = ~tick; 
end
always @ (posedge clk) begin
   if(reset)
      c0_count <= 32'b0;
   else if(mtc0_we && cp0_addr == `CR_COUNT)
      c0_count <= cp0_wdata;
   else if (tick)
      c0_count <= cp0_count + 1'b1;//每两个时钟周期加一
end

7.6 Compare寄存器

只有一个域,更新来源为MTC0指令写CP0寄存器,代码如下:

reg [31:0]c0_compare;
always @(posedge clk) begin
    if(mtc0_we && cp0_addr == `CR_COMPARE)
        c0_compare <= cp0_wdata;
end

整合上述寄存器以及寄存器各个域的代码加以完善,我们可以得到CP0寄存器模块,整体模块代码如下供读者参考。
cp0_reg.v

`include "mycpu.h"
module cp0_reg(
input          clk,
input          reset,
input          mtc0_we,//MTC0指令写cp0寄存器的写使能信号
input [7:0]    cp0_addr,//MTC0指令写CP0寄存器的地址
input [31:0]   cp0_wdata,//MTC0指令写CP0寄存器的数据
input          eret_flush,//eret指令修改EXL域使能信号
input          wb_ex, //写回级报例外
input          wb_bd, //流水线每一级新增1bit标识指令是否在转移指令延迟槽中,对应到写回级为wb_bd
input [4:0]    wb_excode,//写回级所报例外编码
input [31:0]   wb_pc,//写回级指令的pc
input [31:0]   wb_badvaddr, //各级流水传递到写回级的导致出错的虚地址
input [5:0]    ext_int_in,//处理器核顶层的6个中断输入信号


output [31:0]  cp0_status,
output [31:0]  cp0_cause,
output [31:0]  cp0_count,
output [31:0]  cp0_compare,
output [31:0]  cp0_badvaddr,
output [31:0]  cp0_epc,
output [31:0]  cp0_rdata          
    );

//Status寄存器    
wire cp0_status_bev;
assign cp0_status_bev = 1'b1;

reg [7:0] cp0_status_im;
always @ (posedge clk) begin
  if(mtc0_we && cp0_addr == `CR_STATUS)
      cp0_status_im <= cp0_wdata[15:8];
   end
   
reg cp0_status_exl;
always @ (posedge clk) begin
   if(reset)
     cp0_status_exl <= 1'b0;
   else if (wb_ex)
      cp0_status_exl <= 1'b1;
   else if (eret_flush)
      cp0_status_exl <= 1'b0;
   else if (mtc0_we && cp0_addr == `CR_STATUS)
      cp0_status_exl <= cp0_wdata[1];
 end
 
reg cp0_status_ie;
always @ (posedge clk) begin
  if(reset)
    cp0_status_ie <= 1'b0;
  else if (mtc0_we && cp0_addr == `CR_STATUS)
    cp0_status_ie <= cp0_wdata[0];
 end
 
assign cp0_status = 
{
    9'b0          ,     //31:23
    cp0_status_bev,     //22:22
    6'b0          ,     //21:16
    cp0_status_im ,     //15:8
    6'b0          ,     //7:2
    cp0_status_exl,     //1:1
    cp0_status_ie       //0:0
};

//Cause寄存器  描述最近一次例外的原因,除此之外还对软件中断进行了控制
reg cp0_cause_bd; //最近发生例外的指令是否处于分支延迟槽
always @ (posedge clk) begin
   if(reset)
     cp0_cause_bd <= 1'b0;
  else if (wb_ex && !cp0_status_exl)
     cp0_cause_bd <= wb_bd;
end

wire count_eq_compare;
assign count_eq_compare = (cp0_count == cp0_compare);
reg cp0_cause_ti; //计时器中断指示
always @ (posedge clk) begin
   if(reset) 
     cp0_cause_ti <= 1'b0;
   else if (mtc0_we && cp0_addr == `CR_COMPARE)
      cp0_cause_ti <= 1'b0;
   else if (count_eq_compare)
      cp0_cause_ti <= 1'b1;
end

reg [7:0] cp0_cause_ip; //第0、1位为软件中断,只能通过软件更新。其余位为硬件中断且软件不可写
always @ (posedge clk) begin
    if(reset)
       cp0_cause_ip[7:2] <= 6'b0;  //待处理硬件标识
    else begin
       cp0_cause_ip[7] <= ext_int_in[5] | cp0_cause_ti;//硬件中断5号、计时器中断
       cp0_cause_ip[6:2] <= ext_int_in[4:0];
    end
end
always @ (posedge clk) begin
   if(reset)
      cp0_cause_ip[1:0] <= 2'b0;  //待处理软件中断标识
   else if (mtc0_we && cp0_addr == `CR_CAUSE)
      cp0_cause_ip[1:0] <= cp0_wdata[9:8];
end

reg [4:0] cp0_cause_excode;//指令报例外时填入例外编码
always @ (posedge clk) begin
   if(reset)
      cp0_cause_excode <= 5'b0;
   else if(wb_ex)
      cp0_cause_excode <= wb_excode;
end

assign cp0_cause = 
{
   cp0_cause_bd,     //31:31
   cp0_cause_ti,     //30:30
   14'b0       ,     //29:16
   cp0_cause_ip,     //15:8
   1'b0        ,     //7:7
   cp0_cause_excode, //6:2
   2'b0              //1:0
};

//EPC寄存器 包含例外处理完成后继续开始执行的指令的PC
reg  [31:0] c0_epc;
always @ (posedge clk)begin
   if(wb_ex && !cp0_status_exl)
      c0_epc <= wb_bd ? wb_pc - 3'h4 : wb_pc;
   else if(mtc0_we && cp0_addr == `CR_EPC)
      c0_epc <= cp0_wdata;
end
assign cp0_epc = c0_epc;

//Count寄存器
reg tick;
reg [31:0] c0_count;
always @ (posedge clk) begin
   if(reset) 
      tick <= 1'b0;
   else  tick = ~tick; 
end
always @ (posedge clk) begin
   if(reset)
      c0_count <= 32'b0;
   else if(mtc0_we && cp0_addr == `CR_COUNT)
      c0_count <= cp0_wdata;
   else if (tick)
      c0_count <= cp0_count + 1'b1;//每两个时钟周期加一
end
assign cp0_count = c0_count;

//Compare寄存器
reg [31:0]c0_compare;
always @(posedge clk) begin
    if(mtc0_we && cp0_addr == `CR_COMPARE)
        c0_compare <= cp0_wdata;
end
assign cp0_compare = c0_compare;

//BadVAddr寄存器 报地址错例外时将导致出错的虚地址写入该寄存器
reg [31:0] c0_badvaddr;
always @(posedge clk) begin
    if((wb_ex && wb_excode == `EX_ADEL)||(wb_ex && wb_excode == `EX_ADES))
        c0_badvaddr <= wb_badvaddr;
end
assign cp0_badvaddr = c0_badvaddr;
assign cp0_rdata = 
    (cp0_addr == `CR_STATUS)? cp0_status :
    (cp0_addr == `CR_CAUSE)? cp0_cause :
    (cp0_addr == `CR_EPC)? cp0_epc :
    (cp0_addr == `CR_BADVADDR)? cp0_badvaddr :
    (cp0_addr == `CR_COUNT)? cp0_count :
    (cp0_addr == `CR_COMPARE)? cp0_compare :
    32'b0;
endmodule

将cp0_reg.v在写回模块进行例化,实现对cp0寄存器的读写。此外,上述我们所假设的关于写回阶段提供的虚地址、PC、例外编码、例外指令是否处于延迟槽、MTC0指令写地址、MTC0指令写数据等信号,可能由流水线的各个阶段产生经由流水线缓存一路传递到回写阶段,所以我们需要对流水线上的各个阶段进行分析,增加例外相关信号。

8. 例外的具体实现

8.1 取值阶段

取指情况考虑两种不同的情况,首先若写回阶段报出例外,如果回写阶段报出例外,那么取指阶段要取的下一条指令即为例外处理程序的入口地址0xbfc00380,若回写阶段为ERET指令,则恢复断点处的PC。其次需要判断取指阶段是否发生地址错例外。这里需要注意的一个问题就是,判断取指阶段发生例外的指令是否为延迟槽指令时,由于无论跳转指令是否跳转,转移延迟槽指令都要执行,所以不能直接用br_taken(仅为满足跳转条件的转移指令)信号。IF_stage.v修改与增加的代码如下:

    input         ds_is_branch,
    input         ws_ex,//写回阶段是否报出例外
    input         ws_eret,//例外返回
    input [31:0]  cp0_epc//断点PC
wire  fs_ex;//标识取值阶段是否发生例外
wire  fs_bd;//发生例外的指令是否为分支延迟槽指令
wire [31:0] fs_badvaddr;//虚地址
assign fs_to_ds_bus = {
                       fs_ex,
                       fs_bd,
                       fs_badvaddr,
                       fs_inst ,
                       fs_pc   };
assign nextpc       = ws_ex ? 32'h0xbfc00380://访存级报出例外,则进去例外入口
                      ws_eret ? cp0_epc     ://例外返回,则恢复断点值
                      br_taken ? br_target  : seq_pc;                                         
assign fs_to_ds_valid =  fs_valid && fs_ready_go && !ws_ex && !ws_eret;
assign fs_ex = (fs_pc[1:0]= 2'b00) && fs_valid;//判断取值是否出现地址错例外
assign fs_bd = ds_is_branch;//译码阶段跳转指令,则取值阶段为延迟槽指令
assign fs_badvaddr = fs_pc;
assign inst_sram_en    = to_fs_valid && fs_allowin;//修改
assign inst_sram_addr  = {nextpc[31:2],2'b0};

8.2 译码阶段

在译码阶段需要实现以下任务:
1)将取指级产生的有关例外的信息向下一级传递。
2)译码级可能出现系统调用例外、保留指令例外、断点例外和中断例外,对这四种例外产生的条件加以判断,并与取指阶段产生例外的原因进行整合,同时提供例外编码。
3)解决CP0写后读数据相关(MTC0指令写一个CP0寄存器,然后MFC0指令读这个寄存器),本书解决这个相关所引起的冲突的方法是,将MFC0与MTC0指令读、写CP0寄存器放在同一流水级处理,目前建议将MTC0放在写回级处理,那么MFC0也在写回级读CP0寄存器,因此MFC0指令和后续需要其结果的指令之间存在2个时钟周期的执行延迟,故需要其结果的指令必须阻塞在译码阶段,直至MFC0指令到达写回级才能继续执行。注意这个解决办法单纯使用阻塞来实现,不需要数据前递通路,所以我们需要增加新的通路去判断执行、访存、写回阶段是否与当前处于译码阶段的指令存在MFC0这种数据相关,不能复用rt_wait与rs_wait信号,因为这两个信号最终用于数据前递。
综上三点,对ID_stage.v修改及添加的代码如下:

    output  ds_is_branch,
    input   ws_ex,
    input   ws_eret,
    input  [31:0]  cp0_status,
    input  [31:0]  cp0_cause,
    input   es_is_mfc0,
    input   ms_is_mfc0,
    input   ws_is_mfc0 
    
wire        fs_to_ds_ex;
wire [31:0] ds_badvaddr;
wire        ds_bd  ;
assign {
        fs_to_ds_ex , //97:97
        ds_badvaddr , //96:65   
        ds_bd       , //64:64      
        ds_inst     , //63:32
        ds_pc         //31:0
        } = fs_to_ds_bus_r;
//第七章增加指令
//特权指令
wire        inst_eret;//从中断、例外返回
wire        inst_mfc0;//向协处理器0的寄存器取值
wire        inst_mtc0;//向协处理器0的寄存器中保存值
//自陷指令
wire        inst_break;//触发断电例外
wire        inst_syscall;//触发系统调用例外
assign inst_eret    = op_d[6'h10] & func_d[6'h18] & rs_d[5'h10] & rt_d[5'h00] & rd_d[5'h00] & sa_d[5'h00];
assign inst_mfc0    = op_d[6'h10] & rs_d[5'h00] & sa_d[5'h00] & (ds_inst[5:3]==3'b0);
assign inst_mtc0    = op_d[6'h10] & rs_d[5'h04] & sa_d[5'h00] & (ds_inst[5:3]==3'b0);  
assign inst_break   = op_d[6'h00] & func_d[6'h0d];
assign inst_syscall = op_d[6'h00] & func_d[6'h0c];

assign ds_is_branch = (inst_beq || inst_bne || inst_jal || inst_jr || inst_bgez ||
                       inst_bgtz || inst_blez || inst_bltz || inst_j || inst_bltzal ||
                       inst_bgezal || inst_jalr) && ds_valid ;
wire all_inst;
wire interrupt;
assign all_inst = inst_addu | inst_subu | inst_slt | inst_sltu | inst_and | inst_or | inst_xor | inst_nor
                            | inst_sll | inst_srl | inst_sra | inst_addiu | inst_lui | inst_lw | inst_sw 
                            | inst_beq | inst_bne | inst_jal | inst_jr | inst_add | inst_addi | inst_sub 
                            | inst_slti | inst_sltiu | inst_andi | inst_ori | inst_xori | inst_sllv| inst_srlv 
                            | inst_srav | inst_mult | inst_multu | inst_div | inst_divu | inst_mfhi | inst_mflo 
                            | inst_mthi | inst_mtlo | inst_bgez | inst_bgtz | inst_blez | inst_bltz | inst_j 
                            | inst_bltzal | inst_bgezal | inst_jalr | inst_lb | inst_lbu | inst_lh | inst_lhu 
                            | inst_lwl | inst_lwr | inst_sb | inst_sh | inst_swl | inst_swr | inst_syscall 
                            | inst_eret | inst_mfc0 | inst_mtc0 | inst_break;
assign interrupt =  (cp0_status[1:0] == 2'b01) && ((cp0_status[15:8] & cp0_cause[15:8]) != 8'b0);
wire  ds_ex;//译码阶段是否出现例外
wire [4:0] ds_excode;//例外编码
wire [7:0] cp0_addr;
wire  overflow_inst;//可能产生溢出例外的指令
wire mfc0_stall;//将需要mfc0结果的指令阻塞在译码阶段
assign ds_excode = (interrupt)    ? `EX_INT:
                   (inst_syscall) ? `EX_SYS:
                   (inst_break)   ? `EX_BP :
                   (!all_inst)    ? `EX_RI :
                   (fs_to_ds_ex)  ? `EX_ADEL: `EX_NO;
assign ds_ex = (fs_to_ds_ex | inst_syscall | inst_break | !all_inst | interrupt) & ds_valid;
assign cp0_addr = {ds_inst[15:11],ds_inst[2:0]};
assign overflow_inst = inst_sub | inst_add | inst_addi;
assign mfc0_stall = (es_is_mfc0 && (EXE_dest == rs || EXE_dest == rt)) ||
                    (ms_is_mfc0 && (MEM_dest == rs || MEM_dest == rt)) ||
                    (ws_is_mfc0 && (WB_dest == rs || WB_dest == rt));
assign ds_to_es_bus = {
                       inst_syscall,  //209:209
                       inst_mtc0   ,  //208:208
                       inst_mfc0   ,  //207:207
                       inst_eret   ,  //206:206
                       cp0_addr    ,  //205:198
                       overflow_inst, //197:197
                       ds_excode   , //196:192
                       ds_ex       , //191:191
                       fs_to_ds_ex , //190:190
                       ds_badvaddr , //189:158
                       ds_bd       , //157:157
                       mem_control ,  //156:145
                       mf_mt       ,  //144:141
                       mult_div    ,  //140:137
                       alu_op      ,  //136:125
                       load_op     ,  //124:124
                       src1_is_sa  ,  //123:123
                       src1_is_pc  ,  //122:122
                       src2_is_imm ,  //121:120
                       src2_is_8   ,  //119:119
                       gr_we       ,  //118:118
                       mem_we      ,  //117:117
                       dest        ,  //116:112
                       imm         ,  //111:96
                       rs_value    ,  //95 :64
                       rt_value    ,  //63 :32
                       ds_pc          //31 :0
                      };
assign ds_ready_go    = ds_valid & (~rs_wait & ~rt_wait & !mfc0_stall);
assign ds_to_es_valid = ds_valid && ds_ready_go && !ws_ex && !ws_eret;
assign dst_is_rt    = inst_addiu | inst_lui | inst_lw | inst_addi | inst_slti | 
                      inst_sltiu| inst_andi | inst_ori | inst_xori | inst_lb |
                      inst_lbu | inst_lh | inst_lhu | inst_lwl | inst_lwr | 
                      inst_mfc0;
assign gr_we        = ~inst_sw & ~inst_beq & ~inst_bne & ~inst_jr &
                      ~inst_mthi & ~inst_mtlo & ~inst_mult & ~inst_multu & 
                      ~inst_div & ~inst_divu & ~inst_bgez & ~inst_bgtz  &
                      ~inst_blez & ~inst_bltz & ~inst_swl & ~inst_swr &
                      ~inst_sb & ~inst_sh & ~inst_mtc0 & ~inst_syscall &
                      ~inst_eret & ~inst_break;
assign rs_wait = ~src1_no_rs & (rs != 5'd0)
                & ((rs == EXE_dest) | (rs == MEM_dest) | (rs == WB_dest))
                & ~ws_eret & ~ws_ex;
assign rt_wait = ~src2_no_rt & (rt != 5'd0)
                & ((rt == EXE_dest) | (rt == MEM_dest) | (rt == WB_dest))
                & ~ws_eret & ~ws_ex;                      

8.3 执行阶段

执行阶段需要做以下工作:
1)将取指、执行阶段传递过来的与例外有关的信号,继续向下传递。
2)需要对是否发生溢出例外作出判断,并给出例外编码。
3)由于数据存储器采用的是同步读RAM,由于同步读RAM一次读取操作需要跨越两个时钟周期,第一个时钟周期向RAM发出读使能和读地址,第二个时钟周期RAM才能返回读结果。故本书设计的处理器,在执行阶段就将读数据存储器的使能信号、写使能信号、读/写地址以及写数据提供给数据存储器了,这样在访存阶段正好返回读数据。所以我们还需要在执行阶段判断,加载指令和存储指令是否出现地址错例外,并给出例外编码。

    input ws_ex,
    input ws_eret,
    input ms_ex,
    input ms_eret,
    output es_is_mfc0
    
wire es_inst_syscall;
wire es_inst_mtc0;
wire es_inst_mfc0;
wire es_inst_eret;
wire [7:0] cp0_addr;
wire overflow_inst;
wire [4:0] ds_to_es_excode;
wire ds_to_es_ex;
wire fs_to_ds_ex;
wire [31:0] ds_to_es_badvaddr;
wire es_bd;
assign {
        es_inst_syscall,  //209:209
        es_inst_mtc0   ,  //208:208
        es_inst_mfc0   ,  //207:207
        es_inst_eret   ,  //206:206
        cp0_addr       ,  //205:198
        overflow_inst  ,  //197:197
        ds_to_es_excode,  //196:192
        ds_to_es_ex    ,  //191:191
        fs_to_ds_ex    ,  //190:190
        ds_to_es_badvaddr,//189:158
        es_bd          ,  //157:157
        es_mem_control ,  //156:145
        mf_mt          ,  //144:141
        mult_div       ,  //140:137
        es_alu_op      ,  //136:125
        es_load_op     ,  //124:124
        es_src1_is_sa  ,  //123:123
        es_src1_is_pc  ,  //122:122
        es_src2_is_imm ,  //121:120
        es_src2_is_8   ,  //119:119
        es_gr_we       ,  //118:118
        es_mem_we      ,  //117:117
        es_dest        ,  //116:112
        es_imm         ,  //111:96
        es_rs_value    ,  //95 :64
        es_rt_value    ,  //63 :32
        es_pc             //31 :0
       } = ds_to_es_bus_r;
wire [4:0] es_excode;
wire [31:0] es_badvaddr;
wire es_ex;  
assign es_to_ms_bus = {
                       es_inst_syscall,  //165:165
                       es_inst_mtc0   ,  //164:164
                       es_inst_mfc0   ,  //163:163
                       es_inst_eret   ,  //162:162
                       cp0_addr       ,  //161:154
                       es_excode      ,  //153:149
                       es_ex          ,  //148:148
                       es_badvaddr    ,  //147:116
                       es_bd          ,  //115:115
                       es_rt_value    ,  //114:83
                       es_mem_control ,  //82:71
                       es_res_from_mem,  //70:70
                       es_gr_we       ,  //69:69
                       es_dest        ,  //68:64
                       es_result  ,  //63:32
                       es_pc             //31:0
                      };    
wire    no_store;
wire overflow;
wire overflow_ex = overflow && overflow_inst;
assign es_is_mfc0 = es_inst_mfc0 && es_valid;
assign  no_store = ms_ex | ws_ex | es_ex | ms_eret | ws_eret;//标志是否取消除法运算
assign es_ready_go    = es_valid && (mult_div[3:2] == 2'b00| ((mult_div[3:2] != 2'b00)&& !no_store?div_ready_i:1'b1));
assign es_to_ms_valid =  es_valid && es_ready_go && !ws_eret && !ws_ex;
alu u_alu(
    .alu_op     (es_alu_op    ),
    .alu_src1   (es_alu_src1  ),
    .alu_src2   (es_alu_src2  ),
    .alu_result (es_alu_result),
    .overflow   (overflow     )//增加
    ); 
div div0 (
  .clk(clk),                    
  .rst(~reset),    
  .signed_div_i(signed_div_o), 
  .opdata1_i(es_alu_src1),  
  .opdata2_i(es_alu_src2),  
  .start_i(div_start_o),
  .annul_i(no_store),//修改
  .result_o(div_result_i),   
  .ready_o(div_ready_i) 
); 
always @ (posedge clk) begin
    if((mult_div != 4'b0000 | mf_mt[2] == 1'b1)&& !no_store) begin
        if(mult_div[1:0] != 2'b00) 
            lo_i <= mult_result_i[31: 0];
        else if(mult_div[3:2] != 2'b00) 
            lo_i<= div_result_i[31: 0];
        else if(mf_mt[2]) 
            lo_i <= es_alu_src1;
    end
    if((mult_div != 4'b0000 | mf_mt[3] == 1'b1)&& !no_store) begin
        if(mult_div[1:0] != 2'b00) 
            hi_i<= mult_result_i[63:32];
        else if(mult_div[3:2] != 2'b00) 
            hi_i <= div_result_i[63:32];
        else if(mf_mt[3]) 
            hi_i <= es_alu_src1;
    end
end
wire inst_is_lw;
wire inst_is_lh;
wire inst_is_lhu;  
assign      inst_is_lw = es_mem_control  == 12'b000000000001;
assign      inst_is_lh = es_mem_control  == 12'b000000010000;
assign      inst_is_lhu =es_mem_control  == 12'b000000100000; 
//地址错例外
wire mem_ex;
wire load_ex;
wire store_ex;
assign load_ex = (inst_is_lw && (es_alu_result[1:0] != 2'b00)) || ((inst_is_lh || inst_is_lhu) &&(es_alu_result[0] != 1'b0));//ADEL
assign store_ex = (inst_is_sw && (es_alu_result[1:0]!= 2'b00)) || (inst_is_sh && (es_alu_result[0] != 1'b0));//ADES
assign mem_ex = load_ex || store_ex;     
assign es_ex = (overflow_ex | mem_ex | ds_to_es_ex) & es_valid;
assign es_badvaddr = (fs_to_ds_ex) ? ds_to_es_badvaddr : es_alu_result;
assign es_excode = (ds_to_es_ex)? ds_to_es_excode :
                    (overflow_ex)? `EX_OV :
                    (load_ex)? `EX_ADEL :
                    (store_ex)? `EX_ADES :
                    ds_to_es_excode;
assign data_sram_wen   = es_mem_we&&es_valid&&~no_store ? sram_wen : 4'h0;          

alu.v

output overflow

wire overflow_add;
assign overflow_add = op_add && ((~alu_src1[31] && ~alu_src2[31] && adder_result[31]) || (alu_src1[31] && alu_src2[31] && ~adder_result[31]));
wire overflow_sub;
assign overflow_sub = op_sub && ((~alu_src1[31] && alu_src2[31] && adder_result[31]) || (alu_src1[31] && ~alu_src2[31] && ~adder_result[31]));
assign overflow = overflow_add || overflow_sub;

8.4 访存阶段

访存阶段要做的工作,主要是将执行阶段传递过来的与例外相关的信号,继续向下传递给执行阶段。

    input ws_ex,
    input ws_eret,
    output ms_ex,
    output ms_eret,
    output ms_is_mfc0

wire ms_inst_syscall;
wire ms_inst_mfc0;
wire ms_inst_mtc0;
wire ms_inst_eret;
wire [7:0] cp0_addr;
wire [4:0] ms_excode;
wire es_to_ms_ex;
wire [31:0] ms_badvaddr;
wire ms_bd;
assign {
        ms_inst_syscall,  //165:165
        ms_inst_mtc0   ,  //164:164
        ms_inst_mfc0   ,  //163:163
        ms_inst_eret   ,  //162:162
        cp0_addr       ,  //161:154
        ms_excode      ,  //153:149
        es_to_ms_ex    ,  //148:148
        ms_badvaddr    ,  //147:116
        ms_bd          ,  //115:115
        ms_rt_value    ,  //114:83
        ms_mem_control ,  //82:71
        ms_res_from_mem,  //70:70
        ms_gr_we       ,  //69:69
        ms_dest        ,  //68:64
        ms_alu_result  ,  //63:32
        ms_pc             //31:0
       } = es_to_ms_bus_r;
assign ms_to_ws_bus = {
                       ms_inst_syscall,  //120:120
                       ms_inst_mtc0   ,  //119:119
                       ms_inst_mfc0   ,  //118:118
                       ms_eret        ,  //117:117
                       cp0_addr       ,  //116:109
                       ms_excode      ,  //108:104
                       es_to_ms_ex    ,  //103:103
                       ms_badvaddr    ,  //102:71
                       ms_bd          ,  //70:70
                       ms_gr_we       ,  //69:69
                       ms_dest        ,  //68:64
                       ms_final_result,  //63:32
                       ms_pc             //31:0
                      };
assign ms_to_ws_valid = ms_valid && ms_ready_go && !ws_eret && !ws_ex;
assign ms_is_mfc0 = ms_valid && ms_inst_mfc0;
assign ms_ex = ms_valid && es_to_ms_ex; 
assign ms_eret = ms_inst_eret && ms_valid;      

8.5 写回阶段

在写回阶段对cp0模块例化

    output        ws_ex,
    output        ws_eret,
    output [31:0] cp0_status,
    output [31:0] cp0_cause,
    output [31:0] cp0_epc,
    output        ws_is_mfc0
wire        ws_inst_syscall;
wire        ws_inst_mtc0;
wire        ws_inst_mfc0;
wire        ws_inst_eret;
wire [7:0]  cp0_addr;
wire [4:0]  ws_excode;
wire        ms_to_ws_ex;
wire        ws_badvaddr;
wire        ws_bd;
assign {
        ws_inst_syscall,   //120:120
        ws_inst_mtc0   ,   //119:119
        ws_inst_mfc0   ,   //118:118
        ws_inst_eret   ,   //117:117
        cp0_addr       ,   //116:109
        ws_excode      ,  //108:104
        ms_to_ws_ex    ,  //103:103
        ws_badvaddr    ,  //102:71
        ws_bd          ,  //70:70
        ws_gr_we       ,  //69:69
        ws_dest        ,  //68:64
        ws_final_result,  //63:32
        ws_pc             //31:0
       } = ms_to_ws_bus_r;
wire [31:0]  cp0_rdata;
wire [5:0]   ext_int_in;
wire         cp0_we;
wire [31:0]  cp0_wdata;
wire [31:0] ws_cp0_status;
wire [31:0] ws_cp0_epc;
wire [31:0] ws_cp0_cause;
assign ws_is_mfc0 = ws_inst_mfc0 && ws_valid;
assign ws_ex = ms_to_ws_ex && ws_valid;
assign ws_eret = ws_inst_eret && ws_valid;
assign cp0_epc = {32{ws_valid}} & ws_cp0_epc;
assign cp0_cause = {32{ws_valid}} & ws_cp0_cause;
assign cp0_status = {32{ws_valid}} & ws_cp0_status;
assign ext_int_in = 6'b0;
assign cp0_we = ws_inst_mtc0 && ws_valid && !ws_ex;
assign cp0_wdata = ws_final_result;

assign rf_we    = ws_gr_we && ws_valid && ~ws_ex;
assign rf_wdata = ws_is_mfc0 ? cp0_rdata : ws_final_result;      
cp0_reg cp0_reg(
    .clk                (clk),
    .reset              (reset),
    .wb_ex              (ws_ex),
    .wb_bd              (ws_bd),
    .wb_excode          (ws_excode),
    .wb_pc              (ws_pc),
    .wb_badvaddr        (ws_badvaddr),
    .eret_flush         (ws_eret),
    .ext_int_in         (ext_int_in),
    .cp0_addr           (cp0_addr),
    .cp0_rdata          (cp0_rdata),
    .mtc0_we            (cp0_we),
    .cp0_wdata          (cp0_wdata),
    .cp0_epc            (ws_cp0_epc),  
    .cp0_status         (ws_cp0_status),
    .cp0_cause          (ws_cp0_cause)
); 

更改头文件mycpu.v

    `define FS_TO_DS_BUS_WD 98
    `define DS_TO_ES_BUS_WD 210
    `define ES_TO_MS_BUS_WD 166
    `define MS_TO_WS_BUS_WD 121
        `define CR_STATUS       8'b01100000
    `define CR_COMPARE      8'b01011000
    `define CR_CAUSE        8'b01101000
    `define CR_EPC          8'b01110000
    `define CR_COUNT        8'b01001000
    `define CR_BADVADDR     8'b01000000
    
    `define EX_INT              5'h00
    `define EX_ADEL             5'h04
    `define EX_ADES             5'h05
    `define EX_SYS              5'h08
    `define EX_BP               5'h09
    `define EX_RI               5'h0a
    `define EX_OV               5'h0c
    `define EX_ER               5'h0e
    `define EX_NO               5'h1f

最后注意,由于在流水级的各个模块中增加了新的端口,多以不要忘记在顶层模块mycpu_top.v中进行例化。

9. 实验结果

测试点通过在这里插入图片描述

  • 14
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值