「BUAA CO」P7总结
主要任务
- 新增指令:
nop
,mfc0
,mtc0
,eret
,syscall
- 新增模块:
总线Bridge
,CP0协处理器
,Timer0
,Timer1
- 新增功能:完成对五类异常和中断的响应
顶层设计
其中红色为转发有关设计,绿色为阻塞有关设计,蓝色为乘除有关设计,黄色为CP0有关设计或外设,浅绿色虚线内为CPU,粉色为Bridge。
注意事项
P7忘写设计文档Gap了一周…
一定要写设计文档!!!
1. 关于CP0及其实现
P7添加了协处理器CP0,实现了其中的sr(state register)
, cause
和EPC
三个寄存器的一些位。未实现位需要保持0。
功能定义如下:
Reg | Byte | Name | Function |
---|---|---|---|
cause | 31 | BD |
如果CP0 所在级指令为延迟槽指令就接入1,否则接0 |
15:10 | IP/HWInt[5:0] |
HWInt[5:3] 永远保持0;Interrupt 信号接入HWInt[2] Timer1 的IRQ 信号接入HWInt[1] Timer0 的IRQ 信号接入HWInt[0] |
|
6:2 | Exc_code |
如果发生异常或者中断, 则将对应的异常码写入 |
|
sr | 15:10 | IM |
sr[i] 置1表示不对HWInt[i] 的中断信号进行响应 |
1 | EXL |
进入核心态时置1, 退出核心态时置0 置1时不响应异常中断 |
|
0 | IE |
置1响应中断信号 | |
EPC | 31:0 | epc |
写入VPC 。需要注意延迟槽 |
此外需要说明的是cause
寄存器是不可写的。这意味着用户没发通过执行mtc0
指令来修改cause
指令。cause
寄存器的写入是由硬件完成的。每个周期都需要对cause
寄存器的[15:10]进行写入。
2. 关于新增指令
新增指令会引入新的冒险。这主要发生在:
eret
无延迟槽,D级跳转会产生控制冒险,需要清空延迟槽;或者F级跳转。mtc0
和eret
产生供需模型形成数据冒险,需要转发或者阻塞解决冒险。mtc0
和mfc0
与其他指令的冲突…
3. 关于CPU和操作系统的界限
P7要实现的事情只有:
- 翻译指令。
- 在需要响应中断或者发生异常的时候要能够正确跳转到
0x4180
的入口。 - 周期性写入
cause[15:10]
。
比如,在异常或者中断的时候,我们只需要向CP0
中写入对应的VPC
和性质(比如是否是延迟槽)。至于要保存哪些寄存器值,根据指令性质不同执行差异化的异常中断响应代码,这些都不是我们需要考虑的,而是编写异常处理程序的程序员需要操心的事。
4. 关于中断及其对拍
关于中断响应
程序对中断的响应是通过许许多多的开关实现的。在上机中可能也会遇到添加许多开关,根据这些开关的状态决定是否执行对应程序的情况。
(待补充~)
关于中断的对拍
很不幸中断无法和Mars
对拍。流水线CPU中时钟周期与指令周期是不相等的,而Timer
的中断是根据时钟周期来定的;Interrupt
信号是根据宏观PC
和定,因此也无法得知CPU
内部的阻塞情况。也就是说中断信号可以在任意一个时钟周期而非指令周期产生。Mars
模拟的是单周期CPU
的运行,与流水线CPU
不同。此外哪怕是在不会产生冒险的汇编代码下用和Mars
进行对拍,个人也观测到过官方Mars
的鬼畜行为。
如果对自己的中断响应不放心的话,建议找一个设计相当的小伙伴进行对拍,以避免时钟周期不同。
关于Timer
官方教程中对Timer
有专门的文件进行规范,在2023秋的计算机组成课程中又提供了官方的Timer源代码,读懂以后实例化两个Timer即可。需要注意的是只有每个Timer中的三个寄存器只有俩可写,另一个只可读。另外就是Timer
访问的字对齐问题。
5.关于异常和异常优先级
异常
P7需要实现的异常有5种:Adel
Ades
Overflow
RI
Syscall
。每一种异常在教程中已经写的十分详细了。
跳转指令异常
- 跳转指令不会因为PC不对齐或越界而成为受害指令,异常只会发生在跳转到的那条指令上。
beq``bne``jal
不会发生未字对齐的情况,只可能越界。jr
可能未字对齐,也可能越界。
异常优先级
简单来说,就是先发生的异常产生的会顶替掉后发生的异常。比方说当指令取址异常和未知指令异常同时发生时,我们肯定会先考虑取址异常而非未知指令异常。除了从直观感觉来判断以外,可以通过流水线级来判断异常优先级,即在F级发生的异常 > D级发生的异常 > E级发生的异常 > M级发生的异常。F级取址,D级译码,因此指令取址异常优先于未知指令异常。
6.关于延迟槽
指令是否是延迟槽需要在F级进行判断,并进行流水。特例如下:
1 | div $t0, $t1 |
mfhi
会被阻塞在D级,因此等于或晚于D级进行判断都会使得延迟槽判断不准确。
指令是否是延迟槽是由上一条执行的指令决定的,也就是说这条指令的存储空间上的前一条指令是不是跳转指令,以及这条指令的下一级流水级是不是跳转指令都不能决定这条指令是不是延迟槽。
7.关于空泡和宏观PC
流水线CPU
应当保持宏观PC的连续性,因此空泡的PC
不能随便选。一般来说,空泡的PC
应当永远保持与下一条指令的PC
一致:阻塞产生空泡的话就保持与被阻塞指令一致,eret
清空延迟槽的空泡应当与EPC一致(如果是用清空延迟槽实现eret
的话)。
将空泡的PC永远置成32'h0000_3000
也是不行的。尽管程序在第一次运行32'h0000_3000
处的指令时会因为没有将EXL
置1而不对中断做出响应,但是之后可就不好说了。这样就会导致程序在某一个位置因为阻塞了一下使得EPC
置3000
。这可就坏事了。此外,空泡的BD
也应当与下一条指令保持一致。
至于为什么空泡的PC
不能和上一条指令一致呢?这是因为当上一条指令进入CP0
所在级的下一级时,我们应当认为这条指令已经执行完了,这个时候异常中断的VPC
就不应当发生在这条已经执行完的指令上(除非延迟槽指令VPC-4
)——都执行完了怎么受害?
- 标题: 「BUAA CO」P7总结
- 作者: Squirrel7ang
- 创建于 : 2024-01-02 22:11:27
- 更新于 : 2024-01-09 23:09:43
- 链接: https://redefine.ohevan.com/2024/01/02/CO/P7/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。