BUAA-CO课程p7,将之前完成的CPU封装,完成一个MIPS的微系统,能够实现异常与中断。
p7_log
p7需要我们把p6设计的流水线CPU封装,并连接上一系列的外设,完成一个MIPS的微系统,能够实现异常和中断
所以我们要实现以下工作:
p6流水线的设计见p6文档,在这我们,把CPU留下接口后可以忽略他的内部的实现细节。
实现CP0协寄存器,异常和中断
实现系统桥,连接DM,Timer等外设
设计思路-纲领
摘要
- 实现计时器
- 实现系统桥
- 实现CP0
- 改进CPU的内部结构,使其拥有检测内部异常,响应外部中断,实现异常指令
- 通过Mars编写异常处理程序
- 测试
实现指令
- 对于指令的分类很重要
- 下面是所需要是实现的指令类和指令
- 寄存器寄存器计算:add, sub, and, or, slt, sltu, xor
- 寄存器立即数计算:addi, andi, ori, slti, xori
- 位移类: sll, srl, sra, sllv, srlv, srav
- B类:beq, bne
- store:sw, sh, sb
load:lw, lh, lhu, lb, lbu
跳转并链接:jal, jalr, j
- 跳转寄存器:jr, jalr
- 加载高位:lui
- 空指令:nop
- 读乘除法寄存器:mfhi, mflo
写乘除法寄存器:mult, multu, div, divu, mthi, mtlo
p5要求实现的基础指令有:add, sub, ori, lw, sw, beq, lui, jal, jr, nop
- p7在p6的基础上新增的指令:mfc0, mtc0, eret, syscall
命名规范
- 在控制信号连线名末尾加上_阶段字母,如:NPCOp_D,用以区分所属阶段
- 在寄存器输出信号前加上阶段字母_, 如:D_A1,用以区分所属阶段
- 功能部件输出信号前加上功能部件.,(实际打码时舍弃)
- MUX命名采取X_输入端口名_选择数
- 转发MUX命名采用F_输入端口名_选择数,级数越大优先级越高
- 内部异常信号,用EXC_异常类型_阶段
- 接口名清晰易懂
模块设计
Timer1/Timer2
信号 | IO | 描述 | 备注 |
---|---|---|---|
clk | I | 时钟信号 | |
reset | I | 同步复位信号 | |
addr[31:2] | I | Timer写入地址 | |
WE | I | Timer写使能 | |
Din[31:0] | I | Timer写入数据 | |
Dout[31:0] | O | Timer读取的数据 | |
IRQ | O | interupte require |
Bridge
信号 | IO | 描述 | 备注 |
---|---|---|---|
A_cpu[31:0] | I | 读取or写入外设的地址 | cpu输出 |
WD_cpu[31:0] | I | 写入外设的数据 | cpu输出 |
ByteEn[3:0] | I | 写外设的字节使能 | cpu输出 |
DM_RD_in[31:0] | I | DM读取值 | 外设读入 |
T0_RD_in[31:0] | I | T0读取值 | 外设读入 |
T1_RD_in[31:0] | I | T1读取值 | 外设读入 |
A_out[31:0] | O | cpu输出 | |
WD_out[31:0] | O | cpu输出 | |
RD_in[31:0] | O | 外设读入至CPU | 外设读入 |
DMWE[3:0] | O | DM写使能 | 由byteen与地址决定 |
T0WE | O | T0写使能 | 同上 |
T1WE | O | T1写使能 | 同上 |
CP0协寄存器
- 内置在CPU M级中,用于获取外部中断和内部异常的信息,并发送异常/中断请求。
信号 | IO | 描述 | 备注 |
---|---|---|---|
clk | I | 时钟信号 | |
reset | I | 同步复位信号 | |
addr_R[4:0] | I | mfc0读CP0的编号 | |
CPWE | I | mtc0写使能 | |
addr_W[4:0] | I | mtc0写CP0的编号 | |
WD[31:0] | I | mtc0写CP0的数据 | |
M_PC[31:0] | I | 中断异常产生时的M_PC | |
ExcCode[4:0] | I | 产生中断异常的类型 | |
BD_M | I | branch delay分支延迟槽 | |
HWInt[7:2] | I | 六个设备的中断标志 | |
EXL_clr | I | Eret_M用于将SR的EXL置零 | 表示退出中断异常 |
EIrequire | O | 异常/中断请求 | |
EPC[31:0] | O | 输出EPC | |
RD[31:0] | O | mfc0读CP0的数据 |
CPU
信号 | IO | 描述 | 备注 |
---|---|---|---|
clk | I | 时钟信号 | |
reset | I | 同步复位信号 | |
i_inst_rdata[31:0] | I | 从IM读取的指令 | |
m_data_rdata[31:0] | I | 从DM读取的数据 | |
Int_put | I | 外部中断的信号 | |
Int_T0 | I | Timer0输入的中断信号 | |
Int_T1 | I | Timer1输入的中断信号 | |
i_inst_addr[31:0] | O | F_PC | |
m_inst_addr[31:0] | O | M_PC | |
m_data_addr[31:0] | O | DM的读取or写入地址 | |
m_data_wdata[31:0] | O | DMWD | |
m_data_byteen[3:0] | O | DM的字节使能信号 | |
w_inst_addr[31:0] | O | W_PC | |
w_grf_we | O | RFWE_W | |
w_grf_addr[4:0] | O | W_A3 | |
w_grf_wdata[4:0] | O | RFWD/Wout |
实现方法
内部异常
- 每一级的异常进流水,已有异常的话按原异常流水(优先级)
- 在ExcCode_fixE的时候给overflow+上RFWE的判别
- 响应异常时需要做下面几件事:
- 清空:EMW寄存器响应Req,清空寄存器(注意处理PC,同stall情况)
外部中断
- 外部中断的优先级大于内部异常
Mars异常程序
.ktext 4180
将异常处理程序写在4180处- 注意处理中断时需要
sb $27,0x7f20($0)
响应异常
测试数据及自动化测试
- 测试要将各种异常覆盖到
- 同时注意尽可能多的中断情况
自动化
- 自动化测试上我实现了机器码的生成(不足1120行补零)
- 实现了格式的统一,去掉时间输出
1 | command = "java -jar Mars.jar db nc mc CompactDataAtZero a dump .text HexText testcode/testcode.txt testcode/test.asm" |
思考题
1、请查阅相关资料,说明鼠标和键盘的输入信号是如何被 CPU 知晓的?
- 鼠标和键盘产生中断信号,进入中断处理程序,在中断处理程序中,鼠标和键盘输入信号
2、请思考为什么我们的 CPU 处理中断异常必须是已经指定好的地址?如果你的 CPU 支持用户自定义入口地址,即处理中断异常的程序由用户提供,其还能提供我们所希望的功能吗?如果可以,请说明这样可能会出现什么问题?否则举例说明。(假设用户提供的中断处理程序合法)
- 若自定义入口地址,则很多软件将会不兼容,在程序员视角设计软件的时候,中断处理的入口地址是不重要的,也就是说这是软件和硬件之间的协议。
3、为何与外设通信需要 Bridge?
- 外设的种类是繁多,我们通过bridge并且约定某段内存地址对应于某个外设,这样我们就只需要通过访存去实现与外设的联系,指令集会比较的简洁。添加外设时,外设也只需要体现在入口地址的不同而不需要改变CPU的内部结构,让CPU访问外设只需通过地址,这样也是体现了”高内聚,低耦合”的原则。
4、请阅读官方提供的定时器源代码,阐述两种中断模式的异同,并分别针对每一种模式绘制状态移图。
- 两种中断,模式0为定时中断,模式1为周期性中断。区别在于倒计时达到0之后的状态。
5、倘若中断信号流入的时候,在检测宏观 PC 的一级如果是一条空泡(你的 CPU 该级所有信息均为空)指令,此时会发生什么问题?在此例基础上请思考:在 P7 中,清空流水线产生的空泡指令应该保留原指令的哪些信息?
- 写入EPC会出错,延迟槽标记信号也会出错。
- 如果是中断或者异常而清空流水线,应该保持原有的PC值,以保证宏观PC的正确。
- 如果是阻塞而清空流水线,应该要保持原有的PC并且保持原有的BD标志信号。
6、为什么 jalr 指令为什么不能写成 jalr $31, $31?
- 这种操作具有二义性,不知道先跳转还是先链接
- 指令集要求。寄存器说明符 rs 和 rd 不得相等,因为此类指令在重新执行时不具有相同的效果。执行此类指令的结果是不可预测的。此限制允许异常处理程序在分支延迟槽中发生异常时通过重新执行分支来恢复执行。