2022北航计算机组成原理 Project 7


流水线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 >= TnewE_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 = &#123;3'b000, interrupt, TC1_IRQ, TC0_IRQ&#125;;

内部异常
如异常概览中所述,指令在不同流水级可能产生不同的异常,而又根据优先级的要求,我们将异常进行流水,每个流水级进行更新,更新方式如下:

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 <= &#123;temp_EPC[31:2],2'b00&#125;;
              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 + &#123;&#123;14&#123;imm16[15]&#125;&#125;,imm16,2'b00&#125; :
                           (NPCop == `NPCop_jr_jalr) ? rs_data :
                           (NPCop == `NPCop_j_jal) ? &#123;D_pc[31:28],imm26,2'b00&#125; :
                           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结构无法消除其已经改变的值,造成错误的指令行为。


文章作者: Argithun
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Argithun !