北航计组P7设计文档

2024-11-21

Verilog实现五级全速转发流水线CPU,支持中断处理

一、模块设计

模块一:寄存器堆

  • 功能同 P0 第三题 GRF

模块二:算术逻辑单元

  • ALUOp 决定 ALU 进行的运算

模块三:取指令模块

  • 每个时钟周期上升沿将pcNext赋值给pc
  • 将pc的值减去初值0x00003000,作为ROM读取的地址addr
  • pc若为0则将pc置为初始值0x00003000,地址置为0

模块四:乘除模块

  • 进行乘除运算,结果存入内部相应的寄存器
  • 通过busy信号对后续指令进行阻塞
  • 写使能时,将值写入内部寄存器中
  • 根据控制信号,输出内部寄存器地计算结果

模块五:控制信号生成器

(1)D段

  • pcOp:设置程序计数器的操作。
  • cmpOp:表示条件跳转比较。
  • extOp:是否需要扩展符号。
  • regWE:表示寄存器写使能。
  • MD :是否需要使用乘除模块。
  • rtTusersTuse:再过几个周期,该指令要使用 rs 或 rt 寄存器的值。
  • eret:是否是eret指令。
  • delay:是否是延迟槽指令。
  • syscall:是否是syscall指令。
  • RI:不存在的指令。

(2)E段

  • ALUOp:决定 ALU 执行的操作。
  • MDOp:决定 MD 执行的操作。
  • MDWE:表示HI、LO寄存器写使能。
  • MDAddrOp:选择读取 MD 中的寄存器。
  • resOp:选择E段结果来自 ALU 还是 MD。
  • ALUIn2Op:确定 ALU 第二个输入。
  • fwAddrOp:选择转发的地址。
  • fwDataOp:选择转发的数据。
  • Tnew:表示以D级为基准,再过几个周期,该指令产生所需的结果。
  • mtc0:是否是mtc0指令。
  • load:是否是访存型指令。
  • store:是否是存储型指令。
  • cal:是否是计算型指令。

(3)M段

  • memStoreOp:决定写入主存的数据是字、字节和半字。
  • memLoadOp:决定主存读出的数据是字、字节和半字。
  • fwAddrOp:选择转发的地址。
  • fwDataOp:选择转发的数据。
  • Tnew:从结果产生到存入流水线寄存器需要几个周期。
  • eret:是否是eret指令。
  • mtc0:是否是mtc0指令.
  • mfc0:是否是mfc0指令。

(4)W段

  • fwAddrOp:选择转发的地址。
  • fwDataOp:选择转发的数据。
  • Tnew:从结果产生到存入流水线寄存器需要几个周期。

模块六:流水线寄存器

  • D段:存储 pc、指令、异常码、是否是延迟槽指令
  • E段:存储 pc、指令、rs 和 rt 读出的数据、立即数、是否写入寄存器、异常码、是否是延迟槽指令
  • M段:存储 pc、指令、rs 和 rt 读出的数据、立即数、ALU 计算结果、是否写入寄存器、异常码、是否是延迟槽指令
  • W段:存储 pc、指令、rs 和 rt 读出的数据、立即数、ALU 计算结果、主存读出结果、是否写入寄存器

模块七:字节/半字/字选择模块

  • 根据ALU计算地址的后两位,以及存取指令的类型,决定主存存取的数据

模块八:CP0模块

  • 放置在M级流水段
  • 一方面尽可能多的收集前段的异常,另一方面防止异常数据写回寄存器
  • 通过异常码修改三个内部寄存器的值
  • 支持内部寄存器的读写操作
寄存器 功能域 位域 解释
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。

模块九:桥

  • 获取CPU申请访问的地址,读取相应的数据
  • 通过地址的区间确定数据位于主存、Timer0、Timer1中

模块十:Timer

  • 课程组已给出代码
  • 两种模式给出模拟中断信号
  • 通过地址读写Timer内存

二、阻塞与转发

1.阻塞

条件:

  • 读写寄存器冲突
    • Tuse<Tnew
    • 需要将后续数据写入寄存器
    • 写入寄存器地址不为0
    • 写入寄存器地址与后续转发地址相同
  • 乘除单元使用冲突
    • MD单元处于busy状态
    • 后续指令需要使用MD单元
  • eret 指令冲突
    • D段是eret指令
    • E段或M段需要写CP0寄存器

行为:

  • 停止取下一条指令
  • D级寄存器保持原值不变
  • E级寄存器复位(延迟槽指令除外)

2.转发

条件:

  • Tnew=0
  • 需要将后续数据写入寄存器
  • 写入寄存器地址不为0
  • 写入寄存器地址与后续转发地址相同

通路:

  • E段->D段
  • M段->D段、E段
  • W段->D段、E段、M段

内容:

  • 转发地址:写入的寄存器
  • 转发数据:写入寄存器的内容

三、异常

中断码 助记符与名称 指令与指令类型 描述 收集
0 Int(外部中断) 所有指令 中断请求,来源于计时器与外部中断。 -
4 AdEL(取指异常) 所有指令 PC 地址未字对齐。
PC 地址超过 0x3000 ~ 0x6ffc
F段
4 AdEL(取数异常) lw 取数地址未与 4 字节对齐。 M段
4 AdEL(取数异常) lh 取数地址未与 2 字节对齐。 M段
4 AdEL(取数异常) lhlb 取 Timer 寄存器的值。 M段
4 AdEL(取数异常) load 型指令 计算地址时加法溢出。 E段
4 AdEL(取数异常) load 型指令 取数地址超出 DM、Timer0、Timer1、中断发生器的范围。 M段
5 AdES(存数异常) sw 存数地址未 4 字节对齐。 M段
5 AdES(存数异常) sh 存数地址未 2 字节对齐。 M段
5 AdES(存数异常) shsb 存 Timer 寄存器的值。 M段
5 AdES(存数异常) store 型指令 计算地址加法溢出。 E段
5 AdES(存数异常) store 型指令 向计时器的 Count 寄存器存值。 M段
5 AdES(存数异常) store 型指令 存数地址超出 DM、Timer0、Timer1、中断发生器的范围。 M段
8 Syscall(系统调用) syscall 系统调用。 D段
10 RI(未知指令) - 未知的指令码。 D段
12 Ov(溢出异常) addaddisub 算术溢出。 E段

四、思考题

问题一、

  • Q:请查阅相关资料,说明鼠标和键盘的输入信号是如何被 CPU 知晓的?
  • A:鼠标和键盘的输入信号都会转化为不同的系统中断信号,CPU根据中断信号的值可以执行对应的汇编指令,这样就实现了相应鼠标和键盘的功能

问题二、

  • Q:请思考为什么我们的 CPU 处理中断异常必须是已经指定好的地址?如果你的 CPU 支持用户自定义入口地址,即处理中断异常的程序由用户提供,其还能提供我们所希望的功能吗?如果可以,请说明这样可能会出现什么问题?否则举例说明。(假设用户提供的中断处理程序合法)
  • A:必须是已经指定好的地址。处理中断异常程序的目的是维护系统、程序的正常运行,并返回错误信息。如果地址由用户自定义,可能地址无效产生新的异常,达不到目的

问题三、

  • Q:为何与外设通信需要 Bridge?
  • A:一方面,CPU不需要关心具体的数据从何而来,只需通过地址就能获得对应的数据,达到了高内聚低耦合的目的。另一方面,外设种类很多而CPU指令集有限,把外设的接口和CPU的接口通过系统桥连接起来,通过统一的方式,由系统桥选择相关信息的输入输出,使得CPU能够支持各种外设,有更强的拓展性和灵活性

问题四、

  • Q:请阅读官方提供的定时器源代码,阐述两种中断模式的异同,并分别针对每一种模式绘制状态移图。

  • 计数器模式 0
    • 当计数器倒计数为 0 后,计数器停止计数,寄存器的计数使能自动变为 0,中断信号始终保持有效,状态机在中断状态下循环,直到屏蔽中断或重新开始计数
  • 计数器模式 1
    • 当计数器倒计时为 0 后, 初值寄存器值被自动加载至计数器,然后重新开始计数。在这种模式下,中断信号只会产生一个周期

问题五、

  • Q:倘若中断信号流入的时候,在检测宏观 PC 的一级如果是一条空泡(你的 CPU 该级所有信息均为空)指令,此时会发生什么问题?在此例基础上请思考:在 P7 中,清空流水线产生的空泡指令应该保留原指令的哪些信息?
  • CPU此时不知道应该要进行的操作是什么,在异常处理程序返回时EPC中返回的值为0,导致程序错乱
  • 在清空流水线的时候,应该保留被阻塞的当前指令的PC值、判断其是否为延迟槽指令的BD位控制信号

问题六、

  • Q:为什么 jalr 指令为什么不能写成 jalr $31, $31?
  • A:因为 $31 本身用来存储返回地址。如果你将返回地址存储到 $31 中,并且又用它来确定跳转的目标地址,这样就会产生自引用,导致程序跳转到它自身的地址。进而程序会陷入死循环,或者跳转到错误的地址,破坏了正常的控制流