计组P7设计文档
P7建议:先理解异常的处理过程,明确需要硬件完成是哪一部分,然后逐步搭建。(异常的处理和系统桥感觉相对独立?)
设计要求、架构与需求
设计要求
处理器应为五级流水线设计,支持如下指令集:
1 |
|
其他详见“P7提交要求”
任务需求
说实话,P7的教程给出了大致框架,但很多地方的细节并未给出,或者分散在不同地方(不同章节、SMRL、设计文档等),难以明确需求与任务要求,从而导致实现困难,下面是个人的总结。
术语的明确
- 内部异常:指由于执行指令引发的异常,如算术溢出、地址未对齐等等
- 外部中断:指外部引发的中断,如计时器、tb给出的中断信号等等
- 异常/中断:以上两类事件的统称
任务
核心任务:实现异常的处理
处理步骤
- 根据当前CP0的SR状态、内部异常exc信号、外部中断HWint信号判断是否中断
- 把宏观PC写入EPC(注意延迟槽指令),异常信息写入CP0的Cause寄存器,SR相应位置1进入内核态(处理中断过程中禁止嵌套中断)
- 将PC调整到0x4180,清空宏观PC及之前的流水线寄存器
- 执行异常处理程序(处理异常、响应中断、回到原来指令等操作都交给软件实现)
具体实现
- 每一级新增异常判断,流水异常信号——对应步骤1
- 每一级新增中断信号,进行清空操作——对应步骤2
- 每一级针对新增的指令进行修改(要结合CP0的设计一起进行),完成阻塞和转发的修改——对应步骤4
- CP0的实现,包括接受内部异常与外部中断信号、给出中断信号、实现对三个寄存器的写入与读出。
一些细节
- CP0位置:我选择在E级,此时已经可以判断出所有类型,并且不会对乘除模块的寄存器、DM与外设、寄存器堆进行写入,同时可以参考乘除模块进行转发与阻塞的处理
- 延迟槽的判断:直接引入M级指令,若M为跳转,则判断为延迟槽
- 优先级:复位信号>外部中断>内部异常,这体现在Cause寄存器写入等方面
HWint={3'b0,interrupt,timer0,timer1}
(看了学长的博客才知道,根本没有6个中断)
附属任务:将CPU封装为单周期,实现与外设沟通的系统桥
封装为单周期
考察CPU的功能,主要有以下几点:
- 执行指令进行计算——需要IM接口(提供pc,获取指令)
- 与外设进行读、写操作——需要DM等接口(提供addr、写入数据,获取读出数据)
- 获取外部中断信号——2个timer和tb的中断
- 老2样——clk与reset
- 课程组检查寄存器的入口——寄存器堆相关信号
系统桥
考察系统桥的功能,就是根据M级指令、地址、数据,向外设提供相应的写使能信号、写入数据,同时对来自外设的数据进行选择。因此,接口应该能实现以上功能。
架构
模块规格
命名规则
流水线信号共5级,IF/ID ID/EX EX/MEM MEM/WB,两个寄存器间的信号用
级别_数据/功能_去向
命名。
CPU
封装
变量 | 方向 | 位宽 | 解释 | 来源/去向 |
---|---|---|---|---|
clk | in | 1 | 时钟信号 | tb |
reset | in | 1 | 复位信号 | tb |
int_export | in | 1 | 外部中断信号 | tb |
int_timer0 | in | 1 | 时钟中断信号0 | Timer0 |
int_timer1 | in | 1 | 时钟中断信号1 | Timer1 |
macroscopic_pc | out | [31:0] | 宏观pc | EX/tb |
i_inst_rdata | in | [31:0] | IM的指令 | IF_instr |
i_inst_addr | out | [31:0] | IF的pc | IF_pc |
w_grf_we | out | 1 | WB | tb |
w_grf_addr | out | [4:0] | WB | tb |
w_grf_wdata | out | [31:0] | WB | tb |
w_inst_addr | out | [31:0] | WB | tb |
m_inst_addr | out | [31:0] | M级pc | EX_pc_MEM/bridge |
out_instr | out | [31:0] | 写入外设的指令 | EX_instr_MEM/bridge |
out_addr | out | [31:0] | 写入外设的地址 | EX_out/bridge |
out_WD | out | [31:0] | 写入外设的数据 | EX_rt_MEM或W级转发 |
out_RD | in | [31:0] | 从外设读取的数据 | MEM_data |
模块共有信号:
变量 | 方向 | 位宽 | 解释 | 来源/去向 |
---|---|---|---|---|
clk | in | 1 | 时钟信号 | 顶层输入 |
rst | in | 1 | 复位信号 | 顶层输入 |
xx_pc | in | [31:0] | 当前级的pc | 上一级(IF为out) |
xx_pc_yy | out | [31:0] | 要传给下一级的pc | 下一级yy(WB无) |
xx_instr | in | [31:0] | 当前级的指令 | 上一级 |
xx_instr_yy | out | [31:0] | 要传给下一级的指令 | 下一级yy(WB无) |
IF
变量 | 方向 | 位宽 | 解释 | 来源/去向 |
---|---|---|---|---|
req | in | 1 | 中断信号,跳至0x4180 | CP0 |
stall | in | 1 | 阻塞信号,使pc保持不变 | staller |
IF_j | in | 1 | 是否跳转 | ID_j_IF |
IF_pc4 | in | [31:0] | 跳转指令地址 | ID_pc_IF |
IF_instr | in | [31:0] | 从tb获取的指令 | tb |
IF_pc | out | [31:0] | IF级当前指令 | tb |
IF_exc_ID | out | [31:0] | 内部异常信号 | ID |
ID
接口
变量 | 方向 | 位宽 | 解释 | 来源/去向 |
---|---|---|---|---|
req | in | 1 | 中断信号,清空D级 | CP0 |
stall | in | 1 | 阻塞信号,清空D级 | staller |
EPC | in | [31:0] | CP0的EPC | EX_rt(转发)、CP0 |
WB_pc_ID | in | [31:0] | 写入指令的pc | WB_pc_ID |
ID_we | in | 1 | 寄存器写入信号 | WB_we_ID |
ID_addr | in | [4:0] | 要写入的寄存器 | WB_addr_ID |
ID_data | in | [31:0] | 要写入的值 | WB_data_ID |
ID_rs_sign | in | [2:0] | 转发选择,0为原值 | |
ID_rt_sign | in | [2:0] | 转发选择,0为原值 | |
ID_rs_data | in | [31:0] | 转发值 | EX_out,EX_data_WB,MEM_data_WB |
ID_rt_data | in | [31:0] | 转发值 | |
ID_exc | in | [31:0] | IF的异常信号 | IF |
ID_exc_EX | out | [31:0] | 给EX的异常信号 | EX |
ID_rs_base | out | [31:0] | rs寄存器的值 | EX_rs_base |
ID_rt | out | [31:0] | rt寄存器的值 | EX_rt |
ID_j_IF | out | 1 | 跳转指示信号 | IF_j |
ID_pc_IF | out | [31:0] | 跳转指令地址 | IF_pc4 |
内部变量与操作
1 |
|
EX
接口
变量 | 方向 | 位宽 | 解释 | 来源/去向 |
---|---|---|---|---|
req | in | 1 | 中断信号,清空本级 | CP0 |
EX_rs_base | in | [31:0] | rs寄存器的值 | ID_rs_base |
EX_rt | in | [31:0] | 参与运算的rt寄存器的值,可能从转发处来 | |
EX_CP0 | in | [31:0] | 从CP0读取的值 | CP0 |
EX_exc | in | [31:0] | 从ID流水下来的内部异常信号 | ID |
EX_exc_CP0 | out | [31:0] | 传给CP0的内部异常信号 | CP0 |
EX_new | out | 1 | 表示E级产生了新的要写入寄存器的值 | instr |
EX_addr | out | [4:0] | E级产生新值的寄存器地址 | instr |
EX_out | out | [31:0] | 运算结果 | MEM_addr |
EX_rt_MEM | out | [31:0] | rt寄存器的值 | MEM_data |
busy | out | 1 | mul是否正忙 | mul |
内部变量
1 |
|
内部模块——mul
变量 | 方向 | 位宽 | 解释 | 来源/去向 |
---|---|---|---|---|
clk | in | 1 | 时钟信号 | EX |
rst | in | 1 | 复位信号 | EX |
start | in | [2:0] | 开始运算信号 | EX |
A | in | [31:0] | 运算数rs | EX_rs_base |
B | in | [31:0] | 运算数rt | EX_rt_use |
busy | out | 1 | 正忙信号 | busy |
hi | out | [31:0] | 乘法高位/除法余数 | out |
lo | out | [31:0] | 乘法低位/除法的商 | out |
MEM
接口
变量 | 方向 | 位宽 | 解释 | 来源/去向 |
---|---|---|---|---|
MEM_addr | in | [31:0] | 要读出的地址 | EX_out |
MEM_data | in | [31:0] | 读出的数据 | bridge |
MEM_new | out | 1 | 表示M级有了新的要写入寄存器的值 | instr |
MEM_new_addr | out | [4:0] | M级新值的寄存器地址 | instr |
MEM_data_WB | out | [31:0] | 传向下一级的数据 | WB_lw_data |
EX_data_WB | out | [31:0] | 运算结果 | WB_alu_data |
内部变量
1 |
|
内部模块——ext
变量 | 方向 | 位宽 | 解释 | 来源/去向 |
---|---|---|---|---|
A | in | A | 读出地址的低位 | MEM_addr |
Din | in | Din | 原始数据 | MEM_data |
Op | in | Op | 扩展选择信号 | op |
Dout | out | Dout | 扩展后的数据 | MEM_data_WB |
WB
输出均不是reg型
变量 | 方向 | 位宽 | 解释 | 来源/去向 |
---|---|---|---|---|
WB_lw_data | in | [31:0] | 从内存取出的数 | MEM_data_WB |
WB_alu_data | in | [31:0] | 运算结果 | EX_data_WB |
WB_we_ID | out | 1 | 寄存器写入信号 | ID_we |
WB_addr_ID | out | [4:0] | 要写入的寄存器地址 | ID_addr |
WB_data_ID | out | [31:0] | 要写入寄存器的值 | ID_data |
CP0
位于E级,此时已经可以判断出所有异常类型,而且不会写入乘除模块、DM以及寄存器
通过E级指令来写入/读出相应寄存器,读出/写入的值与E级其他指令一样流水
变量 | 方向 | 位宽 | 解释 | 来源/去向 |
---|---|---|---|---|
clk | in | 1 | 时钟信号 | tb |
rst | in | 1 | 复位信号 | tb |
HWint | in | [5:0] | 输入中断信号 | tb |
exc | in | [31:0] | 异常类型 | EX_exc_CP0 |
EX_pc | in | [31:0] | E级pc | EX |
EX_instr | in | [31:0] | E级指令 | EX |
MEM_instr | in | [31:0] | M级指令,判断延迟槽 | EX_instr_MEM |
EX_rt | in | [31:0] | 写入CP0的数据 | EX_rt |
CP0_rd | out | [31:0] | 从CP0读出的数据 | EX_CP0 |
EPC | out | [31:0] | EPC | IF |
Req | out | 1 | 中断信号 | 好多 |
寄存器对应位的功能
其中,SR编号12,Cause编号13,EPC编号14
寄存器 | 功能域 | 位域 | 解释 |
---|---|---|---|
SR(State Register) | IM(Interrupt Mask) | 15:10 | 分别对应六个外部中断,相应位置 1
表示允许中断,置 0 表示禁止中断。这是一个被动的功能,只能通过
mtc0
这个指令修改,通过修改这个功能域,我们可以屏蔽一些中断。 |
SR(State Register) | EXL(Exception Level) | 1 | 任何异常发生时置位,这会强制进入核心态(也就是进入异常处理程序)并禁止中断。 |
SR(State Register) | IE(Interrupt Enable) | 0 | 全局中断使能,该位置 1 表示允许中断,置 0 表示禁止中断。 |
Cause | BD(Branch Delay) | 31 | 当该位置 1 的时候,EPC 指向当前指令的前一条指令(一定为跳转),否则指向当前指令。 |
Cause | IP(Interrupt Pending) | 15:10 | 为 6 位待决的中断位,分别对应 6 个外部中断,相应位置 1 表示有中断,置 0 表示无中断,将会每个周期被修改一次,修改的内容来自计时器和外部中断。 |
Cause | ExcCode | 6:2 | 异常编码,记录当前发生的是什么异常。 |
EPC | - | - | 记录异常处理结束后需要返回的 PC。 |
异常码
异常与中断码 | 助记符与名称 | 指令与指令类型 | 描述 |
---|---|---|---|
0 | Int (外部中断) |
所有指令 | 中断请求,来源于计时器与外部中断。 |
4 | AdEL (取指异常) |
所有指令 | PC 地址未字对齐。 |
PC 地址超过
0x3000 ~ 0x6ffc 。 |
|||
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 |
算术溢出。 |
Bridge
变量 | 方向 | 位宽 | 解释 | 来源/去向 |
---|---|---|---|---|
MEM_instr | in | [31:0] | M级指令 | CPU |
MEM_addr | in | [31:0] | M级要读/写的地址 | CPU |
MEM_WD | in | [31:0] | M级要写入的数据 | CPU |
MEM_RD | out | [31:0] | 传给M的从外设读取的数据 | CPU |
WD | out | [31:0] | 传给外设的写入的数据 | CPU |
DM_RD | in | [31:0] | 从DM读出的数据 | tb |
m_data_byteen | out | [3:0] | DM写使能 | tb |
m_int_byteen | out | [3:0] | 外部中断写使能 | tb |
Timer0_RD | in | [31:0] | Timer0读出的数据 | Timer0 |
Timer0_we | out | 1 | Timer0写使能 | Timer0 |
Timer1_RD | in | [31:0] | Timer1读出的数据 | Timer1 |
Timer1_we | out | 1 | Timer1写使能 | Timer1 |
主要功能:根据从M级的指令、地址、数据,给出相应的读写信号、写入数据的预处理、读出数据的选择
Timer
冒险处理
新增了E级到D级的转发(eret)
mfc0采用之前的转发就好
mtc0采用之前的转发就好
转发
转发共5个接受:
1 |
|
共2处提供:
1 |
|
其中,2处提供接口还给出new信号与addr,代表新值产生且可用、要写入的寄存器编号
接受者只需判断new与addr即可决定转发与否(有新值且可用就转发)
阻塞
\(T_{use}\) 与 \(T_{new}\) 的计算见表格
采用Time模块传递计算结果,[31:0] use_new
具体每位对应结果如下表
[2][1][0]分别对应rs、rt、rd
rs | rt | rd | [4] | [2] | [1] | [0] |
---|---|---|---|---|---|---|
-> | use或new的数值 | <- | 1代表正在算乘除法 | -> | 0为use,1为new | <- |
若 \(T_{use}<T_{new}\) ,或乘除类指令遇上busy,则执行阻塞操作:
- 冻结IF/ID
- 清除ID/EX
- 禁止PC
注意事项
- 从W级转发到D级,采用D级内部转发(如beq)
- 转发时如果地址是0寄存器应剔除
添加指令
添加到const、IF已有指令里
填写TIME表格,根据use和new更改M级中的MEM_new与MEM_new_addr,添加Time中的use与new值
根据操作在每个模块添加行为
- IF:指令获取与延迟槽、未知指令与取指异常
- ID:给出跳转指令
- EX:各种计算行为、存储地址计算
- MEM:给出写入内存的地址、转发信号、对读出的数据进行加工
- WB:给出写寄存器信号、寄存器编号、数据
- Bridge:sw类指令给出使能信号判断
每类指令对应行为(P5)
- cal:修改EX计算过程、添加WB回写信号,加入相关阻塞指令中(带cal的),加入相关转发指令中(同上,注意供需接口都有)。
- lw:修改EX计算过程、添加WB回写信号,……
- sw:修改EX计算过程、Bridge使能判断、MEM写入,………
- j:修改ID跳转判断与跳转地址计算,加入阻塞、转发指令
- jal:修改ID跳转判断与跳转地址计算、EX算pc等、WB回写信号,阻塞转发
是否有新的转发通路、阻塞可能
思考、bug与测试
思考题
请查阅相关资料,说明鼠标和键盘的输入信号是如何被 CPU 知晓的?
当键盘、鼠标有输入时(状态更新或其他),向CPU发出中断信号,CPU接到中断信号后判断中断种类,并执行相应区域代码,完成输入信号的读取。其中,输入信号需要驱动程序的解读。
请思考为什么我们的 CPU 处理中断异常必须是已经指定好的地址?如果你的 CPU 支持用户自定义入口地址,即处理中断异常的程序由用户提供,其还能提供我们所希望的功能吗?如果可以,请说明这样可能会出现什么问题?否则举例说明。(假设用户提供的中断处理程序合法)
必须是已经指定好的地址。处理中断异常程序的目的是维护系统、程序的正常运行,并返回错误信息。如果地址由用户自定义,可能地址无效产生新的异常、或处理程序也产生新的异常等,达不到目的。
为何与外设通信需要 Bridge?
外设种类很多而CPU指令集有限,因此要把外设的接口和CPU的接口通过系统桥连接起来,通过统一的方式,由系统桥选择相关信息的输入输出。
请阅读官方提供的定时器源代码,阐述两种中断模式的异同,并针对每一种模式绘制状态移图。
state的状态转换其实 一样,区别在于从INT到IDLE这一步。
模式0将ctrl[0]置0,IRQ仍然为1,让state在IDLE卡死
模式1将IRQ置0,ctrl[0]仍然为1,让state能继续由IDLE到LOAD,顺便在这一过程中把IRQ置0,实现循环
倘若中断信号流入的时候,在检测宏观 PC 的一级如果是一条空泡(你的 CPU 该级所有信息均为空)指令,此时会发生什么问题?在此例基础上请思考:在 P7 中,清空流水线产生的空泡指令应该保留原指令的哪些信息?
EPC会存入0,跳转回正常指令的时候出错。pc、错误信息
为什么
jalr
指令为什么不能写成jalr $31, $31
?根据MARS的行为,jalr两个寄存器相同会导致先写入当前pc+4,再从寄存器取数跳转,实际上达不到跳转到$31的目的
[P7 选做] 请详细描述你的测试方案及测试数据构造策略。
详见“测试部分”
写P7遇到的bug
eret的跳转值要接受来自mtc0(E级)的转发,还要判断地址是不是EPC
ID在中断时写寄存器操作不能停(相当于W级)
未知指令少打了个sb、mtc0的指令编码敲错了
计数器之间的地址不连续
外部异常比内部异常优先写入Cause
stall和req时应当保持pc
判断溢出的实现有误,应严格按MARS文档来
不管跳转指令是否跳转,其后的延迟槽指令都必须携带BD标记
写入外设的数据和DM提前准备好的冲突
应流水延迟槽标记,而非通过M级判断
阻塞、中断的时候没管BD
测测延迟槽阻塞、中断(已测)
外部中断的时候,BD需要置1吗——需要
SR的IP要每周期修改一次
mtc0和mfc0如果超出12-14怎么办——不用管
何时复位EXL——eret到CP0的下一上升沿
取值异常或RI后视为nop提交至CP0
未知指令判断时,mtc0?、
在延迟槽中断后,修改pc使得在延迟槽重新执行,BD需要置1吗?——不需要
eret处中断怎么办——不会出现tb中断
从中断发生器(不是计时器)读出的数据应该保持0
CP0部分位是只读,未使用的位应当保持为0!!!
通过sw相应一些中断时,阻塞阻塞再eret?、
mtc0、mfc0的rt和rd不要混!!!
D/E流水线寄存器在阻塞的时候应当流水上一级pc和BD!!!
好奇:课程组到底是怎么测试时钟中断的?
优化:lui提前至D级实现、取消对0寄存器的阻塞
测试部分
1 |
|
part1——P4的数据测试
1 |
|
part2——p5冒险测试
\(A_9^4\)?
1 |
|
part3——p6新增指令测试
重点测试边缘数据
1 |
|
part4——p7中断异常测试
异常全覆盖
1 |
|
中断、异常、阻塞、延迟槽交错,限制部分中断
1 |
|