
目标简述
在P6的基础上,实现一个简单的计算机系统(MIPS微系统)。
三种外设
- Timer(计时器):计时部件,可以按照配置定时产生
时钟中断 - Memory(存储器):外置IM、DM(类似P6)
- InterruptGenerator(中断发生器):随机产生外部中断信号,产生的中断信号在CPU响应前会持续置高
支持异常处理的CPU
-
异常
- 内部异常:
- 由于指令执行错误导致的“事件”
- 例如:加法溢出,除法除零
- 外部中断
- 由于外部设备信号导致的“事件”
- 例如:计时器信号
- 内部异常:
-
内部异常检测
-
外部中断检测
-
CP0:接收异常,储存异常类型,实现精确异常
-
异常处理
软硬件接口
- 外设的接口设计——系统桥
-
连接CPU和外设的功能设备
- CPU — SystemBridge — { DM , Timer , InterruptGen , … }
-
给CPU提供一种接口,使得CPU可以按地址读写复杂多变的外设
- CPU不再关心外设长什么样,它只会按地址读写(用统一的方法访问外设)
- 所有外设都是“DM”
-
- CPU的接口设计
- 封装成单周期CPU
宏观PC
- 把复杂的多级流水线CPU封装成单周期CPU
- 宏观PC:整个 CPU “宏观”运行指令所对应的PC地址。我把
CP0放在了M级,所以把M_PC作为宏观PC。- 该指令之前的所有指令序列对 CPU 的更新已完成
- 该指令及其之后的指令序列对 CPU 的更新未完成
顶层设计

任务清单
设计思路:从CP0开始,一步步推进。
协处理器CP0
-
功能 接收内部异常和外部中断,产生异常控制
-
设计 本质上是一组特殊寄存器。
寄存器 编号 功能 SR(State Register) 12 配置异常的功能,即:
当前CPU:
1.能不能处理外部中断(IE)
2.能处理哪些外部中断(IM)
3.是否正在处理异常(EXL)Cause 13 记录异常发生的原因和情况
- 外部中断源:IP
- 内部异常类型:ExcCode)EPC 14 记录异常处理结束后需要返回的PC 寄存器 功能域 位域 解释 SR(State Register) IM(Interrupt Mask) 15:10 分别对应六个外部中断源,相应位置 1 表示允许中断,置 0 表示禁止中断。
控制能处理哪些外部中断SR(State Register) EXL(Exception Level) 1 只要进入异常(I&E)就置1,处理完异常、执行 eret时置0SR(State Register) IE(Interrupt Enable) 0 全局中断使能,该位置 1 表示允许外部中断 Cause BD(Branch Delay) 31 异常是否发生在分支延迟槽中
tip:只有内部异常才可能导致BD为1Cause IP(Interrupt Pending) 15:10 为 6 位待决的中断位,分别对应 6 个外部中断,相应位置 1 表示有中断,置 0 表示无中断
反映外部中断源Cause ExcCode 6:2 内部异常编码,记录当前发生的是什么内部异常。 -
模块规格
信号名 方向 位宽 描述 clk I 1 时钟信号 reset I 1 复位信号 CP0WE I 1 CP0 写使能信号 CP0Address I 5 要访问哪个CP0寄存器(SR/Cause/EPC) CP0WD I 32 写入 CP0 的数据 VPC I 32 受害 PC(发生异常时的 PC) BDIn I 1 是否处于延迟槽 ExcCodeIn I 5 内部异常类型编码 HWInt I 6 外部硬件中断信号,对应Cause_IP/SR_IM EXLclr I 1 清除 EXL,用于执行 eretCP0RD O 32 从 CP0 读出的数据 EPC O 32 EPC 寄存器输出值 Req O 1 是否进入异常处理程序
1: PC ← 异常入口地址
tips:
-
CP0Address、CP0WE是针对 CPU 执行mfc0 / mtc0指令时访问 CP0 寄存器用的,而异常更新并不会经过CP0Address。这两条路径几乎完全分离; -
Req生成逻辑
- Req = 外部中断|内部异常
- 外部中断(Interrupt):
能处理中断(
SR.IE) & 未在处理异常(!SR.EXL) & 存在外部中断源且该中断源可以被处理(SR.IM&HWInt) 需要处理外部中断(Interrupt置高)- 外部中断看情况处理!
- 内部异常(Execption):
未在处理异常(
!SR.EXL) & 存在内部异常(ExcCodeIn) 需要处理内部异常(Execption置高)- 内部异常必须处理!
-
CP0何时被写?
顺序如下:- HWInt无论如何会被更新到Cause.IP
- Req == 1
更新SR.EXL/Cause.BD/EPC
- Exception? 更新Cause.ExcCode,否则置零
- EXLclr == 1
即:执行
eret,将SR.EXL置零 - CP0WE == 1
写对应寄存器
- CP0 寄存器的初始值均为 0,未实现位始终保持 0。
-
CP0接线
- M_CP0WE\M_EXLclr来自控制信号M_ControlBus(增加Control)
- M_PC来自M
- M_ExcCodein来自内部异常检测,M_HWInt来自外部异常检测
- M_BDin:BranchControl生成BDin信号,向后流水,从Reg_M取出
- M_CP0RD:向后流水,传入WDsel
- Req:接入IFU/Reg_D/Reg_E/Reg_M,实现精确异常
- EPC:核心在于理解eret没有延迟槽
- 绝对不执行已经被取到F中的错误指令(eret后面紧跟的指令清空)
eret指令在D时,插入一个 nop(类似likely清空延迟槽——D_Flush)
-
Req如何实现精确异常?
- 精确异常:
- 异常受害指令之前的所有指令都生效
- 异常指令以及之后的指令都不产生任何副作用
- 返回后从异常指令(or上一条
(BDin))重新执行
- 流水线表现
- W级:正常执行
- M级:指令停止,进入异常处理
- E/D/F:flush
- 优先级:reset > Req > stall > (To_nop) > likely造成的D_flush
- 具体实现
- IFU:PC跳转至异常处理地址
- Reg_D:清空instr,PC正常流水
- Reg_E:清空所有,PC、BD正常流水
- Reg_M:清空所有,PC、BD正常流水
- Reg_W:清空所有
- 特别规定,在进入中断或异常状态时,如果受害指令及其后续指令已经改变了
MDU的状态,则无需恢复。考虑以下情况:mult在 E 级启动了乘法运算(其它乘除法指令同理),流水到 M 级时产生了中断- MDU不用恢复
mthi在 E 级修改了 HI 寄存器(mtlo同理),流水到 M 级时产生了中断。- MDU不用恢复
mult在 E 级,受害指令在 M 级,此时还未改变 MDU 状态,不应开始乘法计算,其它乘除法指令同理。mthi在 E 级,受害指令在 M 级,此时还未改变 MDU 状态,不应修改 HI 寄存器的值,mtlo同理。
总之:
- MDU接入req。如果
req&!Busy,那么MDU freeze(MDU不应开始运算/改变HI、LO状态)
- 精确异常:
-
关于PC的处理
- 核心问题:flush造成的nop的PC到底应该怎么取,才保证外部中断后EPC更新不出错?
- nop的分类
- 天然指令nop:不管
- 异常指令视作nop:PC、BDin、ExcCode正常流水,其它清零
- D_flush替换的nop:PC正常流水
- stall插入的bubble(nop):PC、BDin正常流水,其它清零
-
中断和异常优先级:中断优先级高于异常优先级,即当有异常提交至 CP0 寄存器时,若有中断发生,则硬件应先响应中断(
强制将Cause.IP记为5'b0)
内部异常检测与流水
-
异常与中断码
异常码 助记符与名称 指令与类型 描述 0 Int(外部中断) 所有指令 中断请求,来源于计时器与外部中断。 4 AdEL(取指异常) 所有指令 PC 地址未字对齐。
PC 地址超过 0x3000 ~ 0x6ffc。4 AdEL(取数异常) lw 取数地址未与 4 字节对齐。 lh 取数地址未与 2 字节对齐。 lh, lb 取 Timer 寄存器的值。 load 型指令 计算地址时加法溢出。 load 型指令 取数地址超出 DM、Timer0、Timer1、中断发生器范围。 5 AdES(存数异常) sw 存数地址未 4 字节对齐。 sh 存数地址未 2 字节对齐。 sh, sb 存 Timer 寄存器的值。 store 型指令 计算地址加法溢出。 store 型指令 向计时器 Count 寄存器存值。 store 型指令 存数地址超出 DM、Timer0、Timer1、中断发生器范围。 8 Syscall(系统调用) syscall 系统调用。 10 RI(未知指令) - 未知指令码。 12 Ov(溢出异常) add, addi, sub 算术溢出。 -
指令异常检测位置
异常与中断码 异常 检测位置(到该阶段才能检测) 检测指标 0 外部中断Int E \ 4 AdEL D PC 4 AdEL E ALUresult 5 AdES E ALUresult/operand1、operand2 8 Syscall D Instrction 10 RI(未知指令) D Instruction 12 Ov(算术溢出) E operand1、operand2、ALUop -
ExceptionDetect(指令异常检测模块)
- 功能 检测内部异常类型,生成ExcCode
- 模块规格
信号名 方向 位宽 描述 D_ControlBus I 32 D ControlBus E_ControlBus I 32 E ControlBus D_PC I 32 D PC E_ExcCode I 5 从E取出的ExcCode E_operand1 I 32 ALU operand1 E_operand2 I 32 ALU operand2 D_ExcCode O 5 存入E的ExcCode E_NewExcCode O 5 真正存入M的ExcCode 2nop O 1 是否将D中指令视为nop
-
tips:
- 发生取指异常或 RI 异常后视为 nop 直至提交到 CP0(保留BDin,PC和ExcCode)
- 内部异常优先级:AdEL最大(先有取,再有译)
- Timer中count映射的地址?
- 看Timer中count的定义 & 整个Timer映射的地址范围
- e.g.
- Timer0:
0x0000_7F00 ~ 0x0000_7F0B(长度 12 字节 = 3 个 4 字节寄存器) - Timer:``define count mem[2]`
-
count:0x0000_7F08 ~ 0x0000_7F0B
- Timer0:
BDinGenerator
-
BDin
异常发生的这条指令是否位于延迟槽(B/J - 延迟槽(该指令)) 上一条指令是不是B/J -
功能 生成BDin,向后流水
-
原理 分支跳转指令无论跳转与否,延迟槽指令为受害指令时 BD 均需要置位。
-
模块规格
信号名 方向 位宽 描述 D_ifB I 1 D级指令是否是B指令 D_ifJ I 1 D级指令是否是J指令 BDin O 1 D级指令紧跟的指令是否位于延迟槽
tip:eret没有延迟槽但要跳转
Bridge系统桥
-
功能
- 地址解码:当前地址属于哪个设备
- 把CPU的读写信号、写数据转发给对应外设
- 从对应外设返回数据给CPU
-
架构
CPU 给地址、数据、byteen ↓ Bridge 内部解码 ↓ Bridge 产生 DM_Byteen / Timer_Byteen / Int_Byteen ↓ Bridge 选择外设返回数据 RD
计时器
- 功能:根据设定的时间来定时产生中断信号
单周期CPU的封装
宏观PC:M_PC
HCU新增控制
1 | wire stall_eret = D_eret & ( |
DM
- DM写入要屏蔽异常
wire M_TrueMemWrite = M_MemWrite & (!Req)
思考题
-
请查阅相关资料,说明鼠标和键盘的输入信号是如何被 CPU 知晓的?
- 通过外设控制器发出的中断信号 + OS 注册的中断服务程序,CPU 被动接收并处理输入数据。
-
请思考为什么我们的 CPU 处理中断异常必须是已经指定好的地址?如果你的 CPU 支持用户自定义入口地址,即处理中断异常的程序由用户提供,其还能提供我们所希望的功能吗?如果可以,请说明这样可能会出现什么问题?否则举例说明。(假设用户提供的中断处理程序合法)
- CPU 在异常发生时必须立即进入可信、统一、受保护的处理入口,才能保证系统继续正确运行。
- 如果允许用户自定义入口,虽然仍能处理中断,但会出现无法保存现场、破坏内核权限、系统不可恢复等问题,使 CPU 无法提供可靠的操作系统支持。
-
为何与外设通信需要 Bridge?
- 我们可能随时需要增加外设,但不可能每增加一种外设就给CPU增加响应接口。为此我们设计Bridge来连接外设和CPU,将所有外设都映射为一段地址(“DM”),使得CPU能以统一的方式访问不同外设。
-
请阅读官方提供的定时器源代码,阐述两种中断模式的异同,并分别针对每一种模式绘制状态移图。
- 异:计时结束动作不同
- 模式0:单次中断,计时结束持续输出中断信号
- 模式1:循环计时,每次计时归零输出一周期中断信号
- 同:计时过程相同

- 异:计时结束动作不同
-
倘若中断信号流入的时候,在检测宏观 PC 的一级如果是一条空泡(你的 CPU 该级所有信息均为空)指令,此时会发生什么问题?在此例基础上请思考:在 P7 中,清空流水线产生的空泡指令应该保留原指令的哪些信息?
- 问题:EPC出错
- 保留PC、BDin
-
为什么 jalr 指令为什么不能写成 jalr $31, $31?
- 从jalr的语义来看,PC转向被调用函数,函数入口地址保存在 GPR[rs]中。但如果rs和rd相同,那么将PC+8写入rd的操作就会覆盖保存的函数入口地址,使得程序无法返回调用点。