流水线CPU(支持异常处理)设计文档
设计草稿及模块安排
整体模块架构
mips
TC0
TC1
Bridge
CPU
D_Controller
E_Controller
M_Controller
W_Controller
Blocker
D_Controller
E_Controller
M_Controller
F_PC
FD_REG
D_GRF
D_ext
D_cmp
D_NPC
DE_REG
E_ALU
EM_REG
M_DM_IN
M_DM_OUT
CP0
MW_REG
模块安排
mips
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
clk | 1 | in | 系统时钟信号 |
reset | 1 | in | 复位信号 |
interrupt | 1 | in | 外部中断信号 |
macroscopic_pc | 32 | out | 宏观pc |
i_inst_rdata | 32 | in | i_inst_addr 对应的 32 位指令 |
i_inst_addr | 32 | out | F级的pc |
m_data_addr | 32 | out | DM或TC读写地址 |
m_data_wdata | 32 | out | DM或TC待写入数据 |
m_data_rdata | 32 | in | m_data_addr 对应的 32 位数据 |
m_data_byteen | 4 | out | 写DM的字节使能信号 |
m_inst_addr | 32 | out | M级的pc |
m_int_addr | 32 | out | 中断发生器待写入地址 |
m_int_byteen | 4 | out | 中断发生器字节使能信号 |
w_grf_we | 1 | out | GRF 写使能信号 |
w_grf_addr | 5 | out | GRF 中待写入寄存器编号 |
w_grf_wdata | 32 | out | GRF 中待写入数据 |
w_inst_addr | 32 | out | W级pc |
mips模块是整个系统的主模块,其主要承担整体架构中CPU,TC0,TC1,Bridge以及外置指令、数据存储器的线路连接任务。
TC
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
clk | 1 | in | 系统时钟信号 |
reset | 1 | in | 复位信号 |
Addr | 30 | in | 地址输入 |
WE | 1 | in | 写使能信号 |
Din | 32 | in | 数据输入 |
Dout | 32 | out | 数据输出 |
IRQ | 1 | out | 中断请求信号 |
TC是一种外部设备,其主要功能就是根据设定的时间来定时产生中断信号,是我们系统的中断来源之一;其内置3个32位寄存器,对于TC0,寄存器地址范围为0x0000_7F00∼0x0000_7F0B,对于TC1,寄存器地址范围为0x0000_7F10∼0x0000_7F1B,具体读写和控制特性可见:COCO定时器设计规范-1.0.0.4.pdf。
Bridge
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
m_data_addr | 32 | out | DM或TC的读写地址 |
m_data_rdata | 32 | in | 从外置DM直接读取的待选用的数据 |
m_data_wdata | 32 | out | 写入DM或TC的数据 |
m_data_byteen | 4 | out | 经处理的DM字节使能信号,若读写TC则置0 |
temp_m_data_addr | 32 | in | DM或TC的读写地址 |
temp_m_data_rdata | 32 | out | 经过选择从DM或TC中读出的数据 |
temp_m_data_wdata | 32 | in | 写入DM或TC的数据 |
temp_m_data_byteen | 4 | in | 未经处理的DM字节使能信号 |
TC0_WE | 1 | out | TC0写使能信号 |
TC0_Addr | 32 | out | TC0读写地址 |
TC0_Din | 32 | out | 可能写入TC0的数据 |
TC0_Dout | 32 | in | 根据地址从TC0中直接读出的数据 |
TC1_WE | 1 | out | TC1写使能信号 |
TC1_Addr | 32 | out | TC1读写地址 |
TC1_Din | 32 | out | 可能写入TC1的数据 |
TC1_Dout | 32 | in | 根据地址从TC1中直接读出的数据 |
Bridge可以理解为一个大型的多路选择器,根据地址信息来判断读写目标是外置DM、TC0 或 TC1 ;CPU通过 lw , sw 指令对特定地址的读写来与TC交互。
CPU
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
clk | 1 | in | 系统时钟信号 |
reset | 1 | in | 复位信号 |
HWInt | 5 | in | 外部中断信号 |
macroscopic_pc | 32 | out | 宏观pc |
i_inst_rdata | 32 | in | i_inst_addr 对应的 32 位指令 |
i_inst_addr | 32 | out | F级的pc |
m_data_addr | 32 | out | DM或TC读写地址 |
m_data_wdata | 32 | out | DM或TC待写入数据 |
m_data_rdata | 32 | in | m_data_addr 对应的 32 位数据 |
m_data_byteen | 4 | out | 未经Bridge处理的写数据存储器的字节使能信号 |
m_inst_addr | 32 | out | M级的pc |
w_grf_we | 1 | out | GRF 写使能信号 |
w_grf_addr | 5 | out | GRF 中待写入寄存器编号 |
w_grf_wdata | 32 | out | GRF 中待写入数据 |
w_inst_addr | 32 | out | W级pc |
CPU即该系统的主处理器,承担各个子模块的线路连接,数据阻塞、转发,异常和中断识别和控制、程序在异常处理和正常工作模式下转换等功能的核心模块。
F_PC
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
clk | 1 | in | 系统时钟信号 |
reset | 1 | in | 复位信号 |
en | 1 | in | 使能信号 |
Req | 1 | in | 异常或中断识别信号 |
next_pc | 32 | in | 下一条执行指令地址 |
pc | 32 | out | 当前执行指令地址 |
F_PC即F级的指令取址器,定位执行指令的地址;由于流水线CPU可能存在的阻塞处理,这里使用en端实现,需阻塞则置为0,暂停更新pc值,对于可能存在的异常情况,即Req为1时,pc也进行更新,但跳转到异常处理程序的地址,注意阻塞时异常亦跳转。
FD_REG
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
clk | 1 | in | 系统时钟信号 |
reset | 1 | in | 复位信号 |
flush | 1 | in | 刷新信号,置1则输出全0 |
en | 1 | in | 使能信号 |
Req | 1 | in | 异常或中断识别信号 |
F_pc | 32 | in | F级当前执行指令的地址 |
F_instr | 32 | in | F级当前的执行指令 |
F_ExcCode | 5 | in | F级异常码 |
F_BD | 1 | in | F级指令是否是延迟槽指令 |
D_pc | 32 | out | D级当前需执行指令的地址 |
D_instr | 32 | out | D级当前需执行的指令 |
D_ExcCode | 5 | out | D级从F级接收的异常码 |
D_BD | 1 | out | D级指令是否是延迟槽指令 |
FD_REG即保存前一周期F级得到的指令及状态并在本周期将其传送到D级的寄存器,其中引入flush即刷新信号也是为了服务于阻塞机制,但在目前的指令集下,其在FD_REG中无作用,将在DE_REG中介绍;需要注意的是,阻塞发生时,该寄存器的en也应置为0;引入Req信号,在异常发生时,刷新输出,并将D_pc置为异常处理指令地址。
D_GRF
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
clk | 1 | in | 系统时钟信号 |
reset | 1 | in | 复位信号 |
pc | 32 | in | W级指令地址 |
A1 | 5 | in | 读取的寄存器1编号 |
A2 | 5 | in | 读取的寄存器2编号 |
A3 | 5 | in | 需写入的寄存器编号 |
WD | 32 | in | 需写入寄存器的数据 |
RD1 | 32 | out | 从寄存器1读出的数据 |
RD2 | 32 | out | 从寄存器2读出的数据 |
D_GRF即D级的寄存器文件,值得注意的是其中pc、A3和WD来自W级,且可能产生冒险行为,这里采用寄存器内部转发来解决W级的回写与D级读寄存器地址冲突的情况,采用外部转发解决E,M与D级产生的数据冲突;具体操作见冲突处理一节。
D_ext
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
imm16 | 16 | in | 16位立即数输入 |
extop | 1 | in | 扩展方式控制:0:0扩展;1:符号扩展 |
imm32 | 32 | out | 扩展后的32位立即数 |
D_ext即安排在D级的立即数扩展模块
D_cmp
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
rs_data | 32 | in | 从寄存器1读出的数据(可能是转发过来的) |
rt_data | 32 | in | 从寄存器2读出的数据(可能是转发过来的) |
cmpop | 3 | in | 选择比较方式,对应beq、bne等指令 |
jump | 1 | out | 根据指令比较方式和比较结果决定是否跳转,跳转则置1 |
D_cmp即D级的比较器,为了减少判断跳转指令可能带来的流水线上的无效指令,将分支判断提前到D级,那么即使发生跳转,需要作废的指令只有F级,此时若跳转也约定F级指令不作废,即得到延时槽。
D_NPC
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
NPCop | 3 | in | 根据指令选择的下一条指令地址的操作选择信号 |
F_pc | 32 | in | 当前F级的指令地址 |
D_pc | 32 | in | 当前D级的指令地址 |
b_jump | 1 | in | 来自D_cmp的跳转判断信号 |
imm16 | 16 | in | 16位地址偏移量 |
imm26 | 26 | in | 26位伪直接寻址的指令地址 |
rs_data | 32 | in | 从寄存器1读出的数据(可能是转发过来的) |
next_pc | 32 | out | 经判断计算得到的下一条执行指令的地址 |
Req | 1 | in | 异常或中断识别信号 |
eret | 1 | in | 从异常处理程序返回信号 |
EPC | 32 | in | 从异常处理程序返回地址的上一条指令地址 |
F_BD | 1 | out | F级指令是否是延迟槽指令 |
D_NPC即D级的指令更新器,注意若处理b这一类指令满足条件应在跳转至D_pc + 4 + {{14{imm16[15]}},imm16,2'b00}
,而若不需要跳转则下一条指令地址为F_pc+4
;若Req有效则跳转至异常处理程序的地址;若eret有效则返回EPC+4
。值得注意的一点是若imm16 = 0则由于延时槽的存在且D_pc+4 = F_pc该跳转指令的下一条指令会被执行两次。
另设延时槽识别信号F_BD,若D级指令的下一条指令(即在F级的指令)地址关系不满足F_pc == D_pc + 4
,则F级指令为延时槽指令。
DE_REG
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
clk | 1 | in | 系统时钟信号 |
reset | 1 | in | 复位信号 |
flush | 1 | in | 刷新信号,置1则输出全0 |
en | 1 | in | 使能信号 |
ifBlock | 1 | in | 阻塞信号 |
Req | 1 | in | 异常信号 |
D_pc | 32 | in | D级正在执行的指令地址 |
D_instr | 32 | in | D级正在执行的指令 |
D_rs_data | 32 | in | 从寄存器1读出的数据(可能是转发过来的) |
D_rt_data | 32 | in | 从寄存器2读出的数据(可能是转发过来的) |
D_imm32 | 32 | in | 在D级被扩展得到的32位立即数 |
D_b_jump | 1 | in | 在D级经判断得到的b指令跳转信号 |
D_ExcCode | 5 | in | D级指令的异常码 |
D_BD | 1 | in | D级指令是否是延迟槽指令 |
E_pc | 32 | out | E级需执行的指令地址 |
E_instr | 32 | out | E级需执行的指令 |
E_rs_data | 32 | out | 传递到E级的寄存器1数据 |
E_rt_data | 32 | out | 传递到E级的寄存器2数据 |
E_imm32 | 32 | out | 传递到E级的32位立即数 |
E_b_jump | 1 | out | 传递到E级的b指令跳转信号 |
E_ExcCode | 5 | out | E级接收的D级指令的异常码 |
E_BD | 1 | out | E级指令是否是延迟槽指令 |
DE_REG即保存前一周期D级得到的指令及状态并在本周期将其传送到E级的寄存器,需要注意的是 异常的优先级高于阻塞,只要处于阻塞状态,该寄存器的flush置1,清空除了E_pc,E_BD,E_ExcCode的其他E级信号,这主要是为了保证异常信号不丢失,保证流水线的“单周期化”,即在流水线中产生“气泡”,“气泡”随流水线传递,达到等待直至阻塞状态解除的目的。
E_ALU
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
A | 32 | in | 操作数1 |
B | 32 | in | 操作数2 |
ALUCtrl | 4 | in | ALU运算控制信号 |
possible_CalOv | 1 | in | 计算类指令可能溢出信号 |
possible_AddrOv | 1 | in | 访存类指令地址可能溢出信号 |
ALUResult | 32 | out | 运算结果 |
E_Exc_CalOv | 1 | out | E级计算类指令溢出信号 |
E_Exc_AddrOv | 1 | out | E级访存类指令地址溢出信号 |
E_ALU即安排在E级的运算单元,在P7中新增了判断特定指令数据溢出的功能。
E_MDU
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
A | 32 | in | 操作数1 |
B | 32 | in | 操作数2 |
Start | 1 | in | 乘除运算开始信号 |
Busy | 1 | out | 乘除运算正在进行信号 |
HI | 32 | out | 高位寄存器 |
LO | 32 | out | 低位寄存器 |
E_MDU即安排在E级的乘除相关指令(mult, multu, div, divu, mfhi, mflo, mthi, mtlo
)处理单元。乘除法部件的执行乘法的时间为 5 个时钟周期,执行除法的时间为 10 个时钟周期(包含写入内部的 HI 和 LO 寄存器);通过只能有效 1 个时钟周期的 Start 信号来启动乘除法运算,通过 Busy 输出标志来反映这个延迟。
EM_REG
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
clk | 1 | in | 系统时钟信号 |
reset | 1 | in | 复位信号 |
flush | 1 | in | 刷新信号,置1则输出全0 |
en | 1 | in | 使能信号 |
E_pc | 32 | in | E级正在执行的指令地址 |
E_instr | 32 | in | E级正在执行的指令 |
E_ALUResult | 32 | in | E级ALU的运算结果 |
E_MDUResult | 32 | in | E级MDU的运算结果(HI/LO) |
E_rt_data | 32 | in | E级保存的寄存器2数据 |
E_imm32 | 32 | in | E级保存的32位立即数 |
E_b_jump | 1 | in | E级保存的b指令跳转信号 |
E_BD | 1 | in | E级指令是否为延迟槽指令 |
E_Exc_AddrOv | 1 | in | E级访存类指令地址溢出信号 |
E_ExcCode | 5 | in | E级异常码 |
M_pc | 32 | out | M级需执行的指令地址 |
M_instr | 32 | out | M级需执行的指令 |
M_ALUResult | 32 | out | 传递到M级的ALU的运算结果 |
M_MDUResult | 32 | out | 传递到M级的MDU的运算结果 |
M_rt_data | 32 | out | 传递到M级的寄存器2数据 |
M_imm32 | 32 | out | 传递到M级的32位立即数 |
M_b_jump | 1 | out | 传递到M级的b指令跳转信号 |
M_BD | 1 | out | M级指令是否为延迟槽指令 |
M_Exc_AddrOv | 1 | out | M级访存类指令地址溢出信号 |
M_ExcCode | 5 | out | M级从E级接收的异常码 |
EM_REG即保存前一周期E级得到的指令及状态并在本周期将其传送到M级的寄存器,不同于DE_REG,这里EM_REG不会刷新,若Req有效则清空除M_pc以外的M级所有信息,M_pc置为异常处理程序地址,因为CP0在M级,宏观pc就是M_pc。
M_DM_IN
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
instr | 32 | in | M级指令 |
DMop | 3 | in | 存取数据存储器方式选择信号 |
MemAddr | 32 | in | 存取数据存储器地址 |
dataIn | 32 | in | 待处理的写入外置存储器的数据 |
WriteEnable | 1 | in | 写存储器使能信号 |
store | 1 | in | 存储指令识别 |
M_Exc_AddrOv | 1 | in | M级访存指令地址溢出信号 |
m_data_byteen | 4 | out | 写数据存储器的字节使能信号 |
m_data_wdata | 32 | out | 写入外置存储器的数据 |
M_Exc_AdEs | 1 | out | 存储指令异常信号 |
M_DM_IN即安排再M级的外置DM和TC写入数据的预处理器。
M_DM_OUT
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
instr | 32 | in | M级指令 |
DMop | 3 | in | 存取数据存储器方式选择信号 |
MemAddr | 32 | in | 存取数据存储器地址 |
m_data_rdata | 32 | in | 从外置存储器读出的数据 |
M_Exc_AddrOv | 1 | in | M级访存指令地址溢出信号 |
load | 1 | in | 读取指令识别 |
dataOut | 32 | out | 处理后的读出数据 |
M_Exc_AdEl | 1 | out | 读取指令异常信号 |
M_DM_OUT即安排再M级的外置DM和TC读出的数据的处理器。
CP0
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
clk | 1 | in | 系统时钟信号 |
reset | 1 | in | 复位信号 |
WriteEnable | 1 | in | 写使能信号 |
CP0Addr | 5 | in | 读写CP0内置寄存器编号 |
CP0In | 32 | in | 写入数据 |
CP0Out | 32 | out | 读出数据 |
VPC | 32 | in | 受害pc |
BDIn | 1 | in | 是否为延迟槽指令 |
ExcCodeIn | 5 | in | 异常码,记录异常类型 |
HWInt | 5 | in | 输入的异常信号 |
EXLClr | 1 | in | exl置0信号,此时允许中断 |
EPCOut | 32 | out | 异常处理结束后返回的pc |
Req | 1 | out | 异常信号 |
CP0即处理异常和中断的协处理器,承担保存、识别异常信号,在异常处理程序和主程序间控制跳转的任务。
MW_REG
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
clk | 1 | in | 系统时钟信号 |
reset | 1 | in | 复位信号 |
flush | 1 | in | 刷新信号,置1则输出全0 |
en | 1 | in | 使能信号 |
Req | 1 | in | 异常信号 |
M_pc | 32 | in | M级正在执行的指令地址 |
M_instr | 32 | in | M级正在执行的指令 |
M_ALUResult | 32 | in | M级保存的ALU的运算结果 |
M_MDUResult | 32 | in | M级保存的MDU的运算结果 |
M_dataOut | 32 | in | M级读出的存储器数据 |
M_CP0Out | 32 | in | M级CP0读出的数据 |
M_b_jump | 1 | in | M级保存的b指令跳转信号 |
W_pc | 32 | out | W级需执行的指令地址 |
W_instr | 32 | out | W级需执行的指令 |
W_ALUResult | 32 | out | 传递到W级的ALU的运算结果 |
W_MDUResult | 32 | out | 传递到W级的MDU的运算结果 |
W_dataOut | 32 | out | 传递到W级的存储器读出数据 |
W_CP0Out | 32 | out | 传递到W级的CP0读出的数据 |
W_b_jump | 1 | out | 传递到W级的b指令跳转信号 |
MW_REG即保存前一周期M级得到的指令及状态并在本周期将其传送到W级的寄存器。
Controller
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
instr | 32 | in | 32位指令 |
b_jump | 1 | in | b指令跳转信号 |
rs | 5 | out | 指令的21-25位,寄存器1编号 |
rt | 5 | out | 指令的16-20位,寄存器2编号 |
rd | 5 | out | 指令的11-15位,寄存器3编号 |
shamt | 5 | out | 指令的6-10位,多用于移位指令的位移量 |
imm16 | 16 | out | 指令的0-15位,16位立即数 |
imm26 | 26 | out | 指令的0-25位,26位立即数 |
ALUCtrl | 4 | out | ALU运算操作选择信号 |
ALU_Asel | 2 | out | ALU操作数1选择信号 |
ALU_Bsel | 2 | out | ALU操作数2选择信号 |
MDUCtrl | 3 | out | MDU运算选择信号 |
MDU_Start | 1 | out | 乘除运算开始信号 |
cmpop | 3 | out | 比较操作选择信号 |
extop | 1 | out | 位拓展操作选择信号 |
NPCop | 3 | out | 指令地址更新操作选择信号 |
DMop | 3 | out | 存储器读/写数据操作选择信号:字/半字/字节 |
DM_WriteEnable | 1 | out | 外置数据存储器写使能信号 |
GRF_WriteEnable | 1 | out | 寄存器文件写使能信号 |
GRF_A3 | 5 | out | 寄存器文件写数据地址 |
WDSel | 2 | out | 寄存器写入数据选择信号 |
CP0_WriteEnable | 1 | out | CP0写使能信号 |
load | 1 | out | 读取数据存储器指令识别信号 |
store | 1 | out | 写入数据存储器型指令识别信号 |
cal_r | 1 | out | 寄存器操作计算指令识别信号 |
cal_i | 1 | out | 立即数操作计算指令识别信号 |
shift_s | 1 | out | 固定位移指令识别信号 |
shift_v | 1 | out | 可变位移指令识别信号 |
branch | 1 | out | b型跳转指令识别信号 |
j_reg | 1 | out | 从寄存器获取跳转地址的跳转指令识别信号 |
j_imm | 1 | out | 以立即数为跳转地址的跳转指令识别信号 |
j_link | 1 | out | 跳转并链接指令识别信号 |
mul_div | 1 | out | 乘除指令识别信号 |
mt | 1 | out | 向 HI/LO 寄存器写入指令信号 |
mf | 1 | out | 从 HI/LO 寄存器读出数据写入寄存器文件指令识别信号 |
mfc0 | 1 | out | mfc0识别信号 |
mtc0 | 1 | out | mtc0识别信号 |
eret | 1 | out | eret识别信号 |
D_Exc_Syscall | 1 | out | syscall引起的异常识别信号 |
D_Exc_RI | 1 | out | 未识别指令引起的异常识别信号 |
possible_CalOv | 1 | out | 可能发生计算类指令溢出 |
possible_AddrOv | 1 | out | 可能发生访存类指令地址溢出 |
Controller即通用控制器,在mips模块中,在D,E,M,W每一级被调用,接收在相应级所需的状态信息,达到分布式译码的目的,以但前级的指令和状态为依据,发出控制信号,操作数据通路,控制转发操作;在Blocker模块中,在D,E,M级调用,发出指令识别信号以计算 Tuse 和 Tnew ,为是否阻塞提供依据;并对在译码过程中就可以发生的异常给出识别信号。
Blocker
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
D_instr | 32 | in | D级正在执行的指令 |
E_instr | 32 | in | E级正在执行的指令 |
M_instr | 32 | in | M级正在执行的指令 |
E_MDU_Start | 1 | in | MDU开始执行乘除指令信号 |
E_MDU_Busy | 1 | in | MDU正在执行乘除指令信号 |
ifBlock | 1 | out | 阻塞操作使能信号,需阻塞置1 |
Blocker即阻塞控制器,根据Controller的译码结果,计算D_Tuse_rs,D_Tuse_rt,E_Tnew,M_Tnew(由于当前指令集W_Tnew恒为0故先不作计算),再结合寄存器读写信息使用AT模型控制输出的阻塞信号。注意新增的冲突是,eret读取CP0的EPC时之前指令对CP0的EPC的写。
冲突处理
在流水线CPU中,由于多条指令同时存在于流水线上,且在同一周期内执行不同的指令操作,这可能引发由于硬件资源重叠和指令间依赖性而导致的冲突问题;当前指令集下冲突有以下2种可能:
- 寄存器文件中的寄存器被同时读写
- 后面指令在需要使用数据时,前面供给的数据还没有存入寄存器堆
本节主要讨论阻塞和转发处理冲突的情况判断和实现方式。
阻塞操作
阻塞,顾名思义使流水线停在某一指令,需等待某种条件解除阻塞状态。
何时阻塞
后面指令(记为B)在需要使用数据时,前面指令(记为A)供给的数据还没有产生并写入流水级寄存器,这时转发无源头 (转发的源头都是流水级寄存器存储的数据,故不能认为数据产生后可被立即转发) ,唯一的方法是让B等待,直到A在流水线某一级产生所需数据时解除,再考虑转发或直接使用。
这里采用Tuse–Tnew模型判断。
- Tuse:某指令位于 D 级的时候,再经过多少个时钟周期就必须要使用相应的数据。
- Tnew:位于某个流水级的某个指令,它经过多少个时钟周期可以算出结果并且存储到流水级寄存器里。
具体各指令的Tuse,Tnew参见文件夹内表格Tuse&&Tnew。
由此,可以得到结论 Tuse < Tnew 且读写地址重叠(均不为$0) 时必须进行相应阻塞操作。
加入乘除指令后,由于乘除槽的存在,MDU在进行运算时,后续的乘除相关指令应被阻塞在D级,故需要额外为此添加一个阻塞信号。
加入异常处理机制后,由于eret
指令读取CP0存储EPC的寄存器,而mtc0
可能写此寄存器,故额外为此添加一个阻塞信号。
当前指令集需阻塞的情况代码表示如下:
wire E_ifBlock_rs = (E_GRF_A3 == D_rs && D_rs != 0) && (D_Tuse_rs < E_Tnew);
wire E_ifBlock_rt = (E_GRF_A3 == D_rt && D_rt != 0) && (D_Tuse_rt < E_Tnew);
wire M_ifBlock_rs = (M_GRF_A3 == D_rs && D_rs != 0) && (D_Tuse_rs < M_Tnew);
wire M_ifBlock_rt = (M_GRF_A3 == D_rt && D_rt != 0) && (D_Tuse_rt < M_Tnew);
wire E_ifBlock_MDU = (D_mul_div | D_mf | D_mt) && (E_MDU_Busy | E_MDU_Start);
wire M_ifBlock_eret = (D_eret) && (E_mtc0 && E_rd == 14 || M_mtc0 && M_rd == 14);
assign ifBlock = E_ifBlock_rs | E_ifBlock_rt | M_ifBlock_rs | M_ifBlock_rt | E_ifBlock_MDU | M_ifBlock_eret;
如何阻塞
自此,我们得到了阻塞信号ifBlock
以此为依据操作阻塞情况的数据通路(这里先不考虑异常):
- 将F_IFU和FD_REG的使能信号(en)置为0,不再更新并发送新的指令信号,达到使进入流水线的指令滞留在D级的目的。
- 将DE_REG的刷新信号(flush)置为1,使向E级发送的指令为nop,而发送的指令地址为D_pc,这主要是为了使保证宏观pc的单周期特性,即产生“合理气泡”填充流水线;注意,仅需在此寄存器刷新,因为只要处于阻塞状态,该寄存器不断产生“气泡”,而这些“气泡”随时钟周期向后移动填充流水线各级。
- 对于不同指令,当 Tuse >= Tnew 或 E_MDU_Busy = 0, E_MDU_Start = 0时解除阻塞状态,使能信号置为1,刷新信号置为0,开始考虑转发,继续流水。
具体代码实现如下:
assign PC_en = !ifBlock;
assign FD_REG_en = !ifBlock;
assign DE_REG_en = 1;
assign EM_REG_en = 1;
assign MW_REG_en = 1;
assign FD_REG_flush = 0;
assign DE_REG_flush = ifBlock;
assign EM_REG_flush = 0;
assign MW_REG_flush = 0;
转发操作
转发,即将先进入流水线的指令产生的数据根据条件发送给后进入的指令。
何时转发
前面指令供给的数据,而后面指令在需要使用数据时,前面供给的数据已经产生且写入流水级寄存器但还没有存入寄存器堆,导致后面的指令在GRF中取不到正确的值,故当两个流水级出现读写寄存器的重叠时,(在无需阻塞或阻塞完成时)应考虑转发。
如何转发
在当前指令级下,仅存在:
- W向D级转发(寄存器内自转发)
- E,M向D级转发
- M,W向E级转发
- W向M级转发
具体转发关系见文件夹下表格 hazard_and_relocate
具体代码实现如下:
//寄存器内自转发
assign RD1 = (A1==0) ? 0 :
(A1==A3 && A1!=0) ? WD :
regFile[A1];
assign RD2 = (A2==0) ? 0 :
(A2==A3 && A2!=0) ? WD :
regFile[A2];
//向D级转发
assign D_Forward_rs_data = (D_rs == 0) ? 0 :
(D_rs == E_GRF_A3) ? E_WD :
(D_rs == M_GRF_A3) ? M_WD :
D_rs_data;
assign D_Forward_rt_data = (D_rt == 0) ? 0 :
(D_rt == E_GRF_A3) ? E_WD :
(D_rt == M_GRF_A3) ? M_WD :
D_rt_data;
//向E级转发
assign E_Forward_rs_data = (E_rs == 0) ? 0 :
(E_rs == M_GRF_A3) ? M_WD :
(E_rs == W_GRF_A3) ? W_WD :
E_rs_data;
assign E_Forward_rt_data = (E_rt == 0) ? 0 :
(E_rt == M_GRF_A3) ? M_WD :
(E_rt == W_GRF_A3) ? W_WD :
E_rt_data;
//向M级转发
assign M_Forward_rt_data = (M_rt == 0) ? 0 :
(M_rt == W_GRF_A3) ? W_WD :
M_rt_data;
值得注意的是,这种转发方式的正确性是由阻塞机制和转发优先级决定的。
所谓优先级即向每一级转发时,依次沿流水线检索此级的下级,满足条件即转发,若都无需转发,则采用本级读出的寄存器值,这在代码中有所体现。
但可能存在这样两个问题
1.如需要向D级转发某数据,此数据在E级产生但未写入流水级寄存器,直至下个时钟上升沿才写入EM_REG(M级),则按优先级优先转发了DE_REG(E级)保存的数据,这是否会导致错误?
答:实际上不会,由于阻塞机制的存在,当数据在E级产生但未写入流水级寄存器时,流水线被阻塞,指令停滞,此时转发的数据也起不到作用,待到下个时钟上升沿才写入EM_REG(M级),转发来自M级的数据会直接将错误值覆盖,阻塞状态解除,流水线正常执行。
2.如果转发到的寄存器在此指令期间不被读(可以不转发)将一个值存入其中会不会有影响?
答:不会,若该寄存器不被读,则其要么被写,要么不参与本指令的执行,那么对于第一种情况,该指令写入时会将原本转发的值覆盖;对于第二种情况,该寄存器在流水级中的表现实际是相当于提前被写入了(实际上写入还是要到转发的源头指令的W级)。
异常和中断处理
异常概览
graph TD
A[异常]-->B[内部异常]
A-->C[外部中断]
B-->G[F级]
G-->D[pc取址异常:F_Exc_AdEl]
D-->H[D级]
H-->E[无法识别指令:D_Exc_RI]
H-->F[syscall调用:D_Exc_Syscall]
E-->I[E级]
F-->I
I-->J[计算类指令数据溢出:E_Exc_CalOv]
I-->K[访存类指令地址溢出:E_Exc_AddrOv]
J-->L[M级]
K-->L[M级]
L-->M[存储指令异常:M_Exc_AdEs]
L-->N[读取指令异常:M_Exc_AdEl]
C-->O[来自TC的中断信号]
C-->P[来自tb的中断信号]
优先级
- 同时发生时:外部中断 > 内部异常 > 阻塞转发等行为
- 对于流水线上的多条指令都发生异常的情况,优先处理先进入流水线的。
- 对于同一条指令在不同流水级可能发生的异常,优先处理最先出现的。
异常的收集
外部中断
外部中断有两种来源:一是测试模块通过手动设置的发出interrupt,二是TC0和TC1计时结束所发出的IRQ。
P要求用一个 HWInt 来记录中断,具体如下:
wire [5:0] HWInt = {3'b000, interrupt, TC1_IRQ, TC0_IRQ};
内部异常
如异常概览中所述,指令在不同流水级可能产生不同的异常,而又根据优先级的要求,我们将异常进行流水,每个流水级进行更新,更新方式如下:
assign F_ExcCode = F_Exc_AdEl ? `Exc_AdEl : `Exc_NULL;
assign D_ExcCode = temp_D_ExcCode != 0 ? temp_D_ExcCode :
D_Exc_RI ? `Exc_RI :
D_Exc_Syscall ? `Exc_Syscall :
`Exc_NULL;
assign E_ExcCode = (temp_E_ExcCode != 0) ? temp_E_ExcCode :
(E_Exc_CalOv) ? `Exc_Ov :
`Exc_NULL;
assign M_ExcCode = (temp_M_ExcCode != 0) ? temp_M_ExcCode :
(M_Exc_AdEl) ? `Exc_AdEl :
(M_Exc_AdEs) ? `Exc_AdEs :
`Exc_NULL;
注意这里有 temp 前缀的异常码是通过流水级寄存器从上一级直接获取的。
综合异常
对于某一条指令,当其到达M级时,所有可能的异常都已经收集完毕,下一步则是交由CP0综合处理并将Req信号发送到各流水级进行相应的处理操作。
wire interuption = !`exl && `ie && (`im & HWInt != 0);
wire exception = !`exl && (ExcCodeIn != 0);
assign Req = interuption | exception;
异常的处理
对异常的处理可以分为三步:
- 由于封装要求的单周期性,在M级CP0产生异常信号时,需对各流水级寄存器做一些数据更新操作,并记录受害pc(即 M_pc);具体如下:
// FD_REG if(reset | flush | Req)begin D_instr <= 0; D_pc <= Req ? 32'h00004180 : 32'h00003000; D_ExcCode <= 0; //32'h00004180 为异常处理程序首地址 D_BD <= 0; end // DE_REG if(reset || flush || Req || ifBlock)begin E_pc <= Req ? 32'h00004180 : ifBlock ? D_pc : 0; //服务于“单周期化” E_instr <= 0; E_rs_data <= 0; E_rt_data <= 0; E_imm32 <= 0; E_b_jump <= 0; E_BD <= ifBlock ? D_BD : 0; E_ExcCode <= ifBlock ? D_ExcCode : 0; end //EM_REG if(reset || flush || Req)begin M_pc <= Req ? 32'h00004180 : 0; M_instr <= 0; M_ALUResult <= 0; M_MDUResult <= 0; M_rt_data <= 0; M_imm32 <= 0; M_b_jump <= 0; M_BD <= 0; M_ExcCode <= 0; M_Exc_AddrOv <= 0; end
- 跳转进入异常处理程序;我们的任务在上一步更新pc时已经完成了,异常处理程序并不需要我们维护。
- 检测到
eret
指令,根据先前记录的EPC从异常处理程序返回,具体实现如下://CP0 中更新EPC wire [31:0] temp_EPC = Req ? (BDIn ? VPC-32'h00000004 : VPC) : EPC; always @(posedge clk) begin if(Req) begin EPC <= {temp_EPC[31:2],2'b00}; end end //F级中更新pc assign F_pc = D_eret ? EPC : temp_F_pc; //D_NPC 中更新next_pc assign next_pc = (Req) ? 32'h00004180 : (eret) ? EPC + 4 : (b_jump && NPCop == `NPCop_b) ? D_pc + 4 + {{14{imm16[15]}},imm16,2'b00} : (NPCop == `NPCop_jr_jalr) ? rs_data : (NPCop == `NPCop_j_jal) ? {D_pc[31:28],imm26,2'b00} : F_pc+4 ;
最后值得注意的一点是,对于来自tb的中断信号,即 mips 模块的 interrupt 信号,另需要响应方法,即向中断发生器写入:
//DM/TC 写入地址
assign m_data_addr = bridge_m_data_addr;
assign m_data_byteen = bridge_m_byteen;
//中断响应写入地址
assign m_int_addr = bridge_m_data_addr;
assign m_int_byteen = bridge_m_byteen;
因为 CP0 模块安排在 M 级,故以上两类地址在系统桥的保障下可以直接与系统桥处理后的地址相连。
测试方案
异常处理程序
.ktext 0x4180
_entry:
mfc0 $k0, $14
mfc0 $k1, $13
ori $k0, $0, 0x1000
sw $sp, -4($k0)
addiu $k0, $k0, -256
move $sp, $k0
j _save_context
nop
_main_handler:
mfc0 $k0, $13
ori $k1, $0, 0x007c
and $k0, $k1, $k0
beq $0, $k0, _restore_context
nop
mfc0 $k0, $14
addu $k0, $k0, 4
mtc0 $k0, $14
j _restore_context
nop
_restore:
eret
_save_context:
sw $1, 4($sp)
sw $2, 8($sp)
sw $3, 12($sp)
sw $4, 16($sp)
sw $5, 20($sp)
sw $6, 24($sp)
sw $7, 28($sp)
sw $8, 32($sp)
sw $9, 36($sp)
sw $10, 40($sp)
sw $11, 44($sp)
sw $12, 48($sp)
sw $13, 52($sp)
sw $14, 56($sp)
sw $15, 60($sp)
sw $16, 64($sp)
sw $17, 68($sp)
sw $18, 72($sp)
sw $19, 76($sp)
sw $20, 80($sp)
sw $21, 84($sp)
sw $22, 88($sp)
sw $23, 92($sp)
sw $24, 96($sp)
sw $25, 100($sp)
sw $26, 104($sp)
sw $27, 108($sp)
sw $28, 112($sp)
sw $29, 116($sp)
sw $30, 120($sp)
sw $31, 124($sp)
mfhi $k0
sw $k0, 128($sp)
mflo $k0
sw $k0, 132($sp)
j _main_handler
nop
_restore_context:
lw $1, 4($sp)
lw $2, 8($sp)
lw $3, 12($sp)
lw $4, 16($sp)
lw $5, 20($sp)
lw $6, 24($sp)
lw $7, 28($sp)
lw $8, 32($sp)
lw $9, 36($sp)
lw $10, 40($sp)
lw $11, 44($sp)
lw $12, 48($sp)
lw $13, 52($sp)
lw $14, 56($sp)
lw $15, 60($sp)
lw $16, 64($sp)
lw $17, 68($sp)
lw $18, 72($sp)
lw $19, 76($sp)
lw $20, 80($sp)
lw $21, 84($sp)
lw $22, 88($sp)
lw $23, 92($sp)
lw $24, 96($sp)
lw $25, 100($sp)
lw $26, 104($sp)
lw $27, 108($sp)
lw $28, 112($sp)
lw $29, 116($sp)
lw $30, 120($sp)
lw $31, 124($sp)
lw $k0, 128($sp)
mthi $k0
lw $k0, 132($sp)
mtlo $k0
j _restore
nop
.text
ori $2, $0, 0x1001
mtc0 $2, $12
ori $28, $0, 0x0000
ori $29, $0, 0x0000
lui $8, 0x7fff
lui $9, 0x7fff
add $10, $8, $9
or $10, $8, $9
end:
beq $0, $0, end
nop
testbench
`timescale 1ns/1ps
module mips_txt;
reg clk;
reg reset;
reg interrupt;
wire [31:0] macroscopic_pc;
wire [31:0] i_inst_addr;
wire [31:0] i_inst_rdata;
wire [31:0] m_data_addr;
wire [31:0] m_data_rdata;
wire [31:0] m_data_wdata;
wire [3 :0] m_data_byteen;
wire [31:0] m_inst_addr;
wire w_grf_we;
wire [4 :0] w_grf_addr;
wire [31:0] w_grf_wdata;
wire [31:0] w_inst_addr;
mips uut(
.clk(clk),
.reset(reset),
.interrupt(interrupt),
.macroscopic_pc(macroscopic_pc),
.i_inst_addr(i_inst_addr),
.i_inst_rdata(i_inst_rdata),
.m_data_addr(m_data_addr),
.m_data_rdata(m_data_rdata),
.m_data_wdata(m_data_wdata),
.m_data_byteen(m_data_byteen),
.m_inst_addr(m_inst_addr),
.w_grf_we(w_grf_we),
.w_grf_addr(w_grf_addr),
.w_grf_wdata(w_grf_wdata),
.w_inst_addr(w_inst_addr)
);
initial begin
clk <= 0;
reset <= 1;
interrupt <= 0;
#20 reset <= 0;
end
integer i;
reg [31:0] fixed_addr;
reg [31:0] fixed_wdata;
reg [31:0] data[0:4095];
reg [31:0] inst[0:5119];
// ----------- For Instructions -----------
assign m_data_rdata = data[(m_data_addr >> 2) % 5120];
assign i_inst_rdata = inst[((i_inst_addr - 32'h3000) >> 2) % 5120];
initial begin
$readmemh("code.txt", inst);
for (i = 0; i < 5120; i = i + 1) data[i] <= 0;
end
// ----------- For Data Memory -----------
always @(*) begin
fixed_wdata = data[(m_data_addr >> 2) & 4095];
fixed_addr = m_data_addr & 32'hfffffffc;
if (m_data_byteen[3]) fixed_wdata[31:24] = m_data_wdata[31:24];
if (m_data_byteen[2]) fixed_wdata[23:16] = m_data_wdata[23:16];
if (m_data_byteen[1]) fixed_wdata[15: 8] = m_data_wdata[15: 8];
if (m_data_byteen[0]) fixed_wdata[7 : 0] = m_data_wdata[7 : 0];
end
always @(posedge clk) begin
if (reset) for (i = 0; i < 4096; i = i + 1) data[i] <= 0;
else if (|m_data_byteen && fixed_addr >> 2 < 4096) begin
data[fixed_addr >> 2] <= fixed_wdata;
$display("%d@%h: *%h <= %h", $time, m_inst_addr, fixed_addr, fixed_wdata);
end
end
// ----------- For Registers -----------
always @(posedge clk) begin
if (~reset) begin
if (w_grf_we && (w_grf_addr != 0)) begin
$display("%d@%h: $%d <= %h", $time, w_inst_addr, w_grf_addr, w_grf_wdata);
end
end
end
// ----------- For Interrupt -----------
wire [31:0] fixed_macroscopic_pc;
assign fixed_macroscopic_pc = macroscopic_pc & 32'hfffffffc;
parameter exception_pc = 32'h00003018;
integer exception_count;
integer needInterrupt;
initial begin
needInterrupt = 0;
exception_count = 0;
$display("#exception@%h",exception_pc);
$display("#interrupt@1");
$display("#end@");
#20 reset = 0;
end
always @(negedge clk) begin
if (reset) begin
needInterrupt = 0;
interrupt = 0;
end
else begin
if (interrupt) begin
if (|m_data_byteen && fixed_addr == 32'h7F20) begin
interrupt = 0;
end
end
else if (needInterrupt) begin
needInterrupt = 0;
interrupt = 1;
end
else begin
case (fixed_macroscopic_pc)
exception_pc:
begin
if (exception_count == 0) begin
exception_count = 1;
interrupt = 1;
end
end
endcase
end
end
end
always #2 clk <= ~clk;
endmodule
测试数据
.data
.globl TC0_BASE TC1_BASE cnt0 cnt1 cnt0_double cnt1_double
TC0_BASE: .word 0x7f00
TC1_BASE: .word 0x7f10
cnt0: .word 1
cnt1: .word 1
cnt0_double: .word 0
cnt1_double: .word 0
.text
ori $28, $0, 0x0000
ori $29, $0, 0x0f00
mtc0 $0, $12
#save start address
ori $t0, $0, 0x7f00
sw $t0, 0($0)
ori $t1, $0, 0x7f10
sw $t1, 4($0)
#set SR included IM, IE, EXL
ori $t0,$0, 0x0c01
mtc0 $t0,$12
#set Timer0
la $t1, TC0_BASE
lw $t1, 0($t1)
sw $0, 0($t1) # disable Timer0.CTRL
addiu $t0, $0, 0x80 # set Timer0.PRESET
sw $t0, 4($t1)
addiu $t0, $0, 9 # set Timer0.CTRL
sw $t0, 0($t1)
#set Timer1
la $t1, TC1_BASE
lw $t1, 0($t1)
sw $0, 0($t1) # disable Timer1.CTRL
addiu $t0, $0, 0x40 # set Timer1.PRESET
sw $t0, 4($t1)
addiu $t0, $0, 9 # set Timer1.CTRL
sw $t0, 0($t1)
lui $8, 0x7fff
lui $9, 0x7fff
ori $8, $8, 0xffff
j slot_ov1
add $10, $8, $9
slot_ov1:
lui $t0,0x8000
jal slot_ov2
addi $10, $8, -1
slot_ov2:
ori $t1, $0, 0x0ba0
lui $8, 0x8000
lui $9, 0x1000
j slot_ov3
sub $10, $8, $9
slot_ov3:
ori $t2, $0, 0x93ac
lui $8, 0x7fff
lui $9, 0x7fff
j slot_ov4
add $10, $8, $9
slot_ov4:
ori $t2, $0, 0x5daa
lui $8, 0x7fff
lui $9, 0x8000
sub $10, $8, $9
lui $8, 0x8111
lui $9, 0x8111
add $10, $8, $9
addi $10, $8,-2
beq $0,$0,slot_adel1
addi $10, $8,-3
add $10, $8, $9
sub $10, $8, $9
slot_adel1:
slot_adel2:
li $8, 0x801
li $9,0x800
sw $9,0($9)
lh $9,0($8)
j slot_adel3
nop
slot_adel3:
li $8, 0x802
lw $9,0($8)
j slot_adel4
nop
slot_adel4:
li $8, 0x803
lhu $9,0($8)
j slot_adel5
nop
slot_adel5:
li $8, 0x803
lw $9,0($8)
j slot_ades1
nop
slot_ades1:
li $8, 0x801
sh $9,0($8)
j slot_ades2
nop
slot_ades2:
li $8, 0x802
sw $9,0($8)
j slot_ades3
nop
slot_ades3:
li $8, 0x801
sw $9,0($8)
j slot_combination
nop
slot_combination:
lui $s0,0x8000
lui $s1,0x7fff
ori $s1,$s1,0xffff
add $10,$s0,$s0
sub $10,$s0,$s1
addi $10,$s1,10
sw $10,0x1002($0)
sh $10,0x1001($0)
mult $10,$10
lw $10,0x1002($0)
lh $10,0x1001($0)
mult $10,$10
lhu $10,0x1001($0)
sub $10,$s0,$s1
addi $10,$s1,10
sw $10,0x1002($0)
sh $10,0x1001($0)
mult $10,$10
sw $10,0x1002($0)
sh $10,0x1001($0)
lw $10,0x1002($0)
lh $10,0x1001($0)
mult $10,$10
sh $10,0x1001($0)
add $10,$s0,$s0
sub $10,$s0,$s1
mult $10,$10
add $10,$s0,$s0
sub $10,$s0,$s1
j label_1
add $10,$s0,$s0
sub $10,$s0,$s1
label_1:
mult $10,$10
add $10,$s0,$s0
sub $10,$s0,$s1
mult $10,$10
sh $10,0x1001($0)
lw $10,0x1002($0)
add $10,$s0,$s0
bne $0,$10,label_2
lw $10,0x1002($0)
sh $10,0x1001($0)
label_2:
sub $10,$s0,$s1
mult $10,$10
sh $10,0x1001($0)
lw $10,0x1002($0)
nop
ori $t0, $0, 0x0001
wait:
lw $k0, 8($0)
lw $k1,12($0)
bne $k0, $t0, wait
nop
bne $k1, $t0, wait
nop
ori $t0, $0, 0xffff
ori $t1, $0, 0xffff
dead_loop:
j dead_loop
nop
思考题解答
1、请查阅相关资料,说明鼠标和键盘的输入信号是如何被 CPU 知晓的?
答:
键盘是常用的输入设备,它是由一组开关矩阵组成,包括数字键、字母键、符号键、功能键及控制键等。每一个按键在计算机中都有它的惟一代码。当按下某个键时,键盘接口将该键的二进制代码送入计算机处理器中,并将按键字符显示在显示器上。当快速大量输入字符,主机来不及处理时,先将这些字符的代码送往内存的键盘缓冲区,然后再从该缓冲区中取出进行分析处理。键盘接口电路多采用单片微处理器,由它控制整个键盘的工作,如上电时对键盘的自检、键盘扫描、按键代码的产生、发送及与主机的通讯等。
鼠标是输入设备,鼠标通过南桥将位置位移及点击信息传送给cpu,cpu计算后再将结果等一堆信息传送给显卡,显卡生成图像通过dp、dvi、hdmi等接口输出到显示器。
总的来说:鼠标和键盘产生中断信号,进入中断处理区的对应位置,将输入信号从鼠标和键盘中读入寄存器。
2、请思考为什么我们的 CPU 处理中断异常必须是已经指定好的地址?如果你的 CPU 支持用户自定义入口地址,即处理中断异常的程序由用户提供,其还能提供我们所希望的功能吗?如果可以,请说明这样可能会出现什么问题?否则举例说明。(假设用户提供的中断处理程序合法)
答:
可以,但若用户操作不当可能会对存储有 CPU 初始数据和命令的地址区域进行读写,或将连续的地址空间分割,增加了数据丢失和指令处理混乱的风险,并大大增加了硬件设计的难度。
3、为何与外设通信需要 Bridge?
答:
Bridge 实际上是一个大型的多路选择器,其可以使 CPU 以相对固定的方式读取或写入不同的外设,并且在系统需要增添外设时,只需要添加相应的读写地址的映射,可拓展性良好。
4、请阅读官方提供的定时器源代码,阐述两种中断模式的异同,并分别针对每一种模式绘制状态移图。
答:
- 模式0:
当计数器倒计数为 0 后,计数器停止计数,此时控制寄存器中的使能 Enable 自动变为 0。当使能 Enable 被设置为 1 后,初值寄存器值再次被加载至计数器,计数器重新启动倒计数。通常用于产生定时中断。 - 模式1:
当计数器倒计数为 0 后,初值寄存器值被自动加载至计数器,计数器继续倒计数。常用于产生周期性脉冲。
5、倘若中断信号流入的时候,在检测宏观 PC 的一级如果是一条空泡(你的 CPU 该级所有信息均为空)指令,此时会发生什么问题?在此例基础上请思考:在 P7 中,清空流水线产生的空泡指令应该保留原指令的哪些信息?
答:
会丢失上一级指令的延迟槽信息、可能记录的错误信息;这样处理也与“单周期的封装”的目的相违背,因为可能会凭空生成 nop 指令。详细处理见异常的处理一节。
6、为什么 jalr 指令为什么不能写成 jalr $31, $31?
答:
若读写同一寄存器,则当前pc的值加4会被再次写入该寄存器,若产生异常,则当前CPU结构无法消除其已经改变的值,造成错误的指令行为。