MIPS简易流水线CPU

设计思路

基本结构

流水线寄存器,GRF等模块与P6相同,新增timer模块,bridge模块,cp0模块,形成如下图所示的结构。
图片
其中timer模块采用课程组设计,bridge模块负责将cpu传出的数据与外界,timer模块相连,cp0模块负责响应异常和终端,并记录相关信息。
## 模块设计 ### bridge + 主要用于传输数据,将数据发送至相应接口,部分代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
assign cpuReadData = (cpuDataAddr>=0&&cpuDataAddr<=32'h2fff)?memReadData:
(cpuDataAddr>=32'h7f00&&cpuDataAddr<=32'h7f0b)?timer1ReadData:
(cpuDataAddr>=32'h7f10&&cpuDataAddr<=32'h7f1b)?timer2ReadData:
0;
assign timer1DataAddr = cpuDataAddr;
assign timer1We = (cpuDataAddr>=32'h7f00&&cpuDataAddr<=32'h7f0b&&cpuDataByteen==4'b1111);
assign timer1WriteData = cpuWriteData;
assign timer2DataAddr = cpuDataAddr;
assign timer2We = (cpuDataAddr>=32'h7f10&&cpuDataAddr<=32'h7f1b&&cpuDataByteen==4'b1111);
assign timer2WriteData = cpuWriteData;
assign memDataAddr = cpuDataAddr;
assign memWriteData = cpuWriteData;
assign memDataByteen = (cpuDataAddr>=0&&cpuDataAddr<=32'h2fff)?cpuDataByteen:0;
assign intAddr = cpuDataAddr;
assign intByteen = (cpuDataAddr>=32'h7f20&&cpuDataAddr<=32'h7f23)?cpuDataByteen:0;
### cp0 cp0模块主要用于处理异常和终端,位于流水线cpu中的M极,设有与GRF类似的多个寄存器,此处仅设计其中的三个寄存器:SR,Cause,EPC。其中SR用于记录异常状态,Cause用于记录异常类型,EPC用于记录异常发生时的指令地址。 + 输入输出端口如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module CP0 (
input clk, // 时钟信号
input reset, // 复位信号
input en, // mtc0指令写使能信号
input [4:0] CP0Add, // cp0寄存器地址(仅用于mtc0指令)
input [31:0] CP0In, // cp0寄存器输入数据(仅y用于mtc0指令)
output [31:0] CP0Out, // cp0寄存器输出数据 (仅用于mfc0指令)
input [31:0] VPC, // 发生异常或传入中断时的PC值
input BDIn, // 是否为延迟槽指令
input [4:0] ExcCodeIn, // 异常类型
input [5:0] HWInt, // 外部中断类型
input EXLClr, // 异常中断处理结束信号
output [31:0] EPCOut, // EPC输出值
output reg Req // 异常中断处理开始信号
);
+ 三个寄存器部分位含义,见下图。
图片
+ 异常和中断处理逻辑(CP0部分): 1. 发生中断或异常时如果此时不在处理中断和异常,则将Req信号置为1,即中断和异常处理的开始信号,如果此时正在处理中断和异常则置为0。 2. 在下一个时钟上升沿到来时,如果Req为1,将EXL置为1,表示正在进行中断和异常处理,并将相应的输入信号存入对应寄存器。 3. EXLClr为1时,表示异常中断处理结束,EXL置为0。 + ps:由于EPC可能产生数据冒险,为其输出端口EPCOut设计内部转发。 ### mips 顶层模块,连接cpu,bridge,timer1,timer2。
### 冒险处理模块 大体上与P6相同,由于新增了mfc0,mtc0,eret,指令,故需考虑以下情况:
1. mtc0写入EPC寄存器,下一条指令为eret指令,此时需要阻塞一个周期。 2. mfc0,mtc0冒险处理与乘除模块的mfhi,mthi等类似(除tUse,tNew以及写寄存器地址可能不同)。
## 异常判断以及相应信号的产生 涉及到的异常情况及其编码ExcCode如下表:

异常中断码 助记符与名称 指令或指令类型 描述
0 Int(外部中断) 所有指令 中断请求,来源于计时器与外部中断。
4 AdEl(取指异常) 所有指令 PC地址未字对齐
PC地址超出0x3000 ~ 0x6ffc
AdEl(取数异常) lw 地址未4字节对齐
lh 地址未2字节对齐
lh,lb 取timer寄存器的值
load型指令 地址计算加法溢出
load型指令 取数地址超出DM,timer1,timer2,中断发生器地址
5 AdEs(存数异常) sw 地址未4字节对齐
sh 地址未2字节对齐
sh,sb 存timer寄存器的值
store型指令 地址计算加法溢出
store型指令 向计时器的Count寄存器存值
store型指令 取数地址超出DM,timer1,timer2,中断发生器地址
8 syscall(系统调用) syscall 系统调用
10 RI(未知指令) - 未知指令码
12 Ov(溢出异常) add,addi,sub 算数溢出
  1. I极判断pc值是否产生异常同时判断是否为延迟槽指令,注意如果此时正在进行阻塞,异常判断码和延迟槽判断信号应当保持不变
    1
    2
    3
    assign ExcCodeI = (freezD==1)?ExcCodeD:
    (pcI[1:0]!=0||!(pcI>=32'h3000&&pcI<=32'h6ffc))?4:0;
    assign BDInI = (freezD==1)?BDInD:((codeD[31:26]==0&&codeD[5:0]==`jrfun)||codeD[31:26]==`beq||codeD[31:26]==`jal||codeD[31:26]==`bne);
  2. D极判断是否为未知指令或系统调用,注意如果此时正在进行阻塞,异常判断码应当置为0,延迟槽判断信号保持不变同时注意异常优先级
    1
    2
    3
    4
    5
    assign ExcCodeDIn = (freezD==1)?0:
    (ExcCodeD!=0)?ExcCodeD:
    (syscall==1)?8:
    (RI==1)?10:
    ExcCodeD;
  3. E极判断是否有溢出现象,注意异常优先级
    1
    2
    3
    4
    5
    assign ExcCodeEIn = (ExcCodeE!=0)?ExcCodeE:
    (Ov==1&&(ALUop==0||ALUop==1))?((MemWriteE==1)?5:
    (MemtoRegE==1)?4:
    12):
    ExcCodeE;
  4. M极判断存取指令地址是否有异常,注意异常优先级
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    assign ExcCodeMIn = (HWInt!=0)?0:
    (ExcCodeM!=0)?ExcCodeM:((MemtoRegM==1)?((extMemWordOp==0&&m_data_addr[1:0]!=0)?4:
    (extMemWordOp==4&&m_data_addr[0]!=0)?4:
    ((extMemWordOp==2||extMemWordOp==4)&&((m_data_addr>=32'h7f00&&m_data_addr<=32'h7f0b)||(m_data_addr>=32'h7f10&&m_data_addr<=32'h7f1b)))?4:
    (!((m_data_addr>=0&&m_data_addr<=32'h2fff)||(m_data_addr>=32'h7f00&&m_data_addr<=32'h7f0b)||(m_data_addr>=32'h7f10&&m_data_addr<=32'h7f1b)||(m_data_addr>=32'h7f20&&m_data_addr<=32'h7f23)))?4:
    ExcCodeM):
    ((MemWrite==1)?((mDataByteen==3&&m_data_addr[1:0]!=0)?5:
    (mDataByteen==2&&m_data_addr[0]!=0)?5:
    ((mDataByteen==2||mDataByteen==1)&&((m_data_addr>=32'h7f00&&m_data_addr<=32'h7f0b)||(m_data_addr>=32'h7f10&&m_data_addr<=32'h7f1b)))?5:
    (m_data_addr[3:0]==8&&((m_data_addr>=32'h7f00&&m_data_addr<=32'h7f0b)||(m_data_addr>=32'h7f10&&m_data_addr<=32'h7f1b)))?5:
    (!((m_data_addr>=0&&m_data_addr<=32'h2fff)||(m_data_addr>=32'h7f00&&m_data_addr<=32'h7f0b)||(m_data_addr>=32'h7f10&&m_data_addr<=32'h7f1b)||(m_data_addr>=32'h7f20&&m_data_addr<=32'h7f23)))?5:
    ExcCodeM):
    ExcCodeM));
  5. 异常和中断处理开始时,清空除W极以外的所有极指令同时将这些值pc置为32'h4180。
  6. 异常和中断开始时,如果乘除模块正在进行乘除运算,此时不需要处理,如果异常和中断刚刚开始时(即M极为异常指令或产生中断,E极为乘除模块相关指令),则需要取消其运算。
  7. 异常和中断开始时,跳转至地址为32'h4180的指令,从而进行异常和中断的处理。
  8. 异常优先级:最早发生的异常优先级最高
    ## 备注
  • 由于中断可能发生于任意时刻,此时流水线cpu并不能明确地说其在进行哪一条指令,因此引入宏观pc的概念,即宏观地将流水线cpu看作单周期cpu,此时的pc值应当为CP0寄存器所在流水极的pc值(此处为M极pc)。
  • EPC负责保存发生异常或中断时的pc值,即为宏观pc,然而当此时正在进行延迟槽指令时,应当存入pc+4(避免重复执行延迟槽指令)。