设计草稿

任务清单

  • 流水线 CPU 为计时器模块(已实现)提供接口
  • 创建系统桥模块,为 CPU 提供统一的访问外设的接口
  • 创建协处理器 CP0模块,用于引导 CPU 执行异常处理程序
  • 使 CPU 能够检测内部指令执行的错误
  • 使 CPU 能够初步响应外部的中断信号
  • 在 CPU 中实现一些异常处理需要执行的指令

注:

CPU(包含 CP0)、Bridge 和 2 个计时器 Timer1、Timer2都是MIPS 微系统的组成部分,也即我们需要设计搭建的系统模块;而 DM 和中断产生器是外部的设备,由外部(testbench)传入,与我们无关。

对于 Bridge 来说,Timer1、Timer2 和 InterruptGenerator 是设备(外设),各自对应于一个 CPU逻辑地址空间(即0x0000_7F00∼0x0000_7F0B0x0000_7F10∼0x0000_7F1B0x0000_7F20∼0x0000_7F23);DM 是存储器,对应于 CPU逻辑地址空间0x0000_0000∼0x0000_2FFF。(这里之所以称作逻辑地址空间,是因为这些地址其实是给 CPU 看的,用来告诉 CPU 哪一块地址对应哪一个部件,实际上并不存在这样一个连续的内存空间,而只是反映一个映射关系)。

当 CPU 写内存的信号(即逻辑地址 PrAddr 和写内容 PrWD)传入 Bridge 时,Bridge 根据 PrAddr 翻译出需要写入的部件(设备或 DM)该部件需要写入的寄存器/内存,并通过写使能控制设备的写入;当 CPU 读内存信号(即逻辑地址 PrAddr)传入 Bridge 时,Bridge 根据 PrAddr 翻译出需要读的部件(设备或 DM)该部件需要读的寄存器/内存,并通过一个 MUX 选择正确的读出数据传入 CPU。

由此可见Bridge只是起到译码的功能,生成相应的控制信号,只有组合逻辑。

新增模块

TC

已实现。

Bridge

信号 方向 描述
A_in[31:0] I 逻辑地址
WD_in[31:0] I 写入的数据
Byteen[3:0] I 字节使能
DM_RD[31:0] I DM 读出的数据
DEV0_RD[31:0] I 设备 0 读出的数据
DEV1_RD[31:0] I 设备 1 读出的数据
A_out[31:0] O 逻辑地址
WD_out[31:0] O 写入的数据
DM_WE[3:0] O DM 字节使能信号
WeDEV0 O 设备 0 写使能信号
WeDEV1 O 设备 1 写使能信号
RD[31:0] O 读出的数据

CPU 地址空间设计如下:

image-20241126200149916

CP0(CPU 内)

信号 方向 描述
clk I 时钟信号
reset I 同步复位信号
en I 写使能信号
CP0Addr[4:0] I 寄存器地址
CP0In[31:0] I CP0 写入数据
CP0Out[31:0] O CP0 读出数据
VPC[31:0] I 受害 PC
BDIn I 是否是延迟槽指令
ExcCodeIn[4:0] I 记录异常类型
HWInt[5:0] I 输入中断信号
EXLClr I EXL 复位信号
EPCOut[31:0] O EPC 的值
Req O 进入处理程序请求

注:Timer0 输出的中断信号接入 HWInt[0] (最低中断位),Timer1 输出的中断信号接入 HWInt[1],来自中断发生器的中断信号接入 HWInt[2]。

CP0 中需要实现的寄存器

寄存器 编号 功能
SR(State Register) 12 表示系统的状态(如能否发生异常)
Cause 13 记录异常发生的原因和情况
EPC 14 记录异常处理结束后需要返回的 PC
  • SR

    功能域 位域 解释
    IE(Interrupt Enable) SR[0] 全局中断使能,该位置 1 表示允许中断,置 0 表示禁止中断。
    EXL(Exception Level) SR[1] 任何异常发生时置 1,这会强制进入核心态(也就是进入异常处理程序)并禁止中断。
    IM(Interrupt Mask) SR[15:10] 分别对应六个外部中断,相应位置 1 表示允许中断,置 0 表示禁止中断。这是一个被动的功能,只能通过 mtc0 这个指令修改,通过修改这个功能域,我们可以屏蔽一些中断。
  • Cause

    功能域 位域 解释
    BD(Branch Delay) Cause[31] 当该位置 1 的时候,EPC 指向当前指令的前一条指令(一定为跳转),否则指向当前指令。
    IP(Interrupt Pending) Cause[15:10] 为 6 位待决的中断位,分别对应 6 个外部中断,相应位置 1 表示有中断,置 0 表示无中断,将会每个周期被修改一次,修改的内容来自计时器和外部中断。
    ExcCode Cause[6:2] 异常编码,记录当前发生的是什么异常。

    ExcCode 的编码必须遵守规范,不然在评测的时候可能会出现问题

    1732627794497

寄存器规范

  • CP0 寄存器的初始值均为 0,未实现位始终保持 0。
  • 当进入中断或异常状态时,均需要将 EXL 置为 1,用以屏蔽中断信号(注意《SMRL》中并没有指定进入中断时 EXL 的值);当退出中断或异常状态时,也均需要将 EXL 置为 0,取消屏蔽中断信号。
  • Cause 寄存器的 IP 域每周期写入 HWint 对应位的值。
  • 当进入中断或异常状态时,需要将受害指令的 PC 写入 EPC。

有关 CP0 硬件实现

  • CP0 位置:为了满足精确异常的需求,将 CP0 设在 M 级。

  • 传递异常:流水各级异常码,使得先处理指令序列中靠前的异常指令(即使可能靠后的指令先出现异常)。

    发生取指异常或 RI 异常后视为 nop 直至提交到 CP0。

    所有异常在进入 M 级之前都要识别出来,并且将异常指令的内存写使能置 0。

    • 流水异常码 不同指令异常的优先级

    • 异常选择 同一指令异常的优先级

    注:P7 中的 nop 操作(不论是阻塞还是异常)有所改动,应保持原来指令的 PC 值与 BD 值。(否则,PC 与 BD 都为 0,EPC 会存入错误的值)

  • 清空流水线:避免宏观 PC 之后的指令被执行。

  • 写入 EPC:为了异常/中断处理结束后,重新执行受害指令(异常/中断处指令),应将受害 PC写入 EPC:

    • 对于跳转指令,目的指令的 PC为受害 PC;

    • 否则,对于非延迟槽指令,受害 PC 为当前指令的 PC

    • 对于延迟槽指令,应将PC - 4作为受害 PC(否则,执行完延迟槽指令后将顺序执行其后面的指令,此时如果跳转指令应该跳转则执行错误)。

  • 中断响应

    • 每条指令的 M 阶段检测最终异常和中断

      中断检测时判断是否中断允许:

      1
      assign IntReq = |(HWInt[5:0] & IM[5:0]) & IE & !EXL;

      注:中断优先级高于异常(清除各级指令时,先判断中断再判断异常流水标志位)

    • 中断响应时,在同一时期完成 4 个操作

      1. PC EPC,ExcCode Cause
      2. 中断处理程序入口地址 PC
      3. 清空流水线
      4. EXL 置位,防止再次进入
    • 中断处理结束(eret)后,在同一时期完成 2 个操作

      1. EPC PC(同时清空延迟槽)
      2. 清除 EXL,允许再次中断

异常分析器

  • F

    信号 方向 描述
    F_PC[31:0] I F 级 PC 值
    ExcCode[4:0] O 异常码
  • D

    信号 方向 描述
    D_Instr[31:0] I D 级指令
    Op[5:0] O 指令 Op 位
    Funct[5:0] O 指令 Funct 位
    ExcCode[4:0] O 异常码
  • E

    信号 方向 描述
    Addr[31:0] I 访存地址,来自 E 级 ALU 运算结果
    Overflow I ALU 算术溢出信号
    Op[5:0] I 指令 Op 位
    Funct[5:0] I 指令 Funct 位
    ExcCode[4:0] O 异常码

新增指令

  • 在 P6 基础上新增了 mfc0, mtc0, eret, syscall 四条新指令。
  • eret 具有跳转的功能但是没有延迟槽,你的设计应该保证 eret 的后续指令不被执行。
  • syscall 指令行为与 MARS 不同,无需实现特定的输入输出功能,只需直接产生异常并进入内核态。

框架重构

P6 中”mips.v”文件中为流水线 CPU。

P7 中流水线 CPU(新增 CP0 协处理器)单独为一个模块,“mips.v”中为 CPU、Bridge、Timer0、Timer1 四个模块构成的微系统(即任务清单中图片的架构)。

注意事项

  • P6 以前加减法指令均当作无符号数处理,P7 应改为有符号数

  • PC 寄存器中,异常中断优先级高于阻塞

  • CP0 的异常请求要与上 EXL 信号

  • eret 需要考虑阻塞(弱测未测出来)

  • 发生阻塞时,CTRL 块内的 BD 值也要保持(弱测未测出来)

  • mtc0 的 GPR[rt]在 M 级转发(弱测未测出来)

测试方案

对于异常,使用 COKiller 对拍;

对于中断,在 COKiller 自动生成的代码中对 SR 寄存器赋值,使得 CPU 允许中断,然后观察 ISE 仿真波形图判断对错。

思考题

  1. 请查阅相关资料,说明鼠标和键盘的输入信号是如何被 CPU 知晓的?

    鼠标和键盘的输入信号被 CPU 知晓的过程涉及多个硬件和软件组件,这个过程可以分为几个步骤来理解:

    1. 设备连接:首先,鼠标和键盘通过有线(如 USB 接口)或无线方式(如蓝牙、2.4GHz 无线接收器等)与计算机相连。这些设备需要特定的接口或者适配器来实现物理上的连接。

    2. 信号生成:当用户操作键盘或移动/点击鼠标时,相应的硬件会生成电信号。例如,按下键盘上的一个键会导致该键下方的电路闭合,从而产生一个代表该按键的电子信号;鼠标中的光电传感器或机械开关也会根据用户的动作生成对应的信号。

    3. 信号传输:产生的信号通过连接线(对于有线设备)或者无线发射器发送到计算机主板上的相应接口控制器。对于 USB 设备来说,信号是通过 USB 总线传输的;而对于无线设备,则是通过无线收发模块进行数据交换。

    4. 中断处理:接收到信号后,接口控制器会向 CPU 发起一个中断请求。中断是一种机制,允许外设通知 CPU 发生了某种事件,需要立即关注。CPU 接收到中断请求后,会暂停当前执行的任务,转而执行中断服务程序(ISR)。

    5. 中断服务程序:中断服务程序负责处理来自外设的数据。对于键盘或鼠标输入来说,ISR 会读取并解析从设备传来的信号,将其转换为操作系统能够理解和处理的信息格式。这通常涉及到将原始的二进制信号转换成字符代码或者其他形式的输入数据。

    6. 操作系统处理:一旦输入信息被正确解析,操作系统会进一步处理这些数据。它可能会将按键记录下来作为文本输入,或者根据鼠标的移动更新屏幕上的光标位置。此外,操作系统还负责维护一个输入缓冲区,以确保所有输入都能被应用程序按顺序访问。

    7. 应用程序响应:最后,应用程序通过调用操作系统提供的 API 来获取用户输入,并据此做出反应。比如,在文字编辑器中显示新输入的文字,或是游戏中角色按照玩家的指令移动。

  2. 请思考为什么我们的 CPU 处理中断异常必须是已经指定好的地址?如果你的 CPU 支持用户自定义入口地址,即处理中断异常的程序由用户提供,其还能提供我们所希望的功能吗?如果可以,请说明这样可能会出现什么问题?否则举例说明。(假设用户提供的中断处理程序合法)

    这样可以确保在任何情况下,CPU 都能够准确地跳转到正确的处理程序。

    可以。但是用户自定义的中断处理程序可能会导致安全性和兼容性的问题,增加了系统的维护成本。

  3. 为何与外设通信需要 Bridge?

    CPU 无法直接通过地址访问外设,而需要通过系统桥转译信息,识别出需要访问的设备。

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

    模式 0 在倒计时变为 0 后停止计数,直到重新启动计数;

    模式 1 在倒计时变为 0 后自动重新倒计数。

    image-20241201010413982

  5. 倘若中断信号流入的时候,在检测宏观 PC 的一级如果是一条空泡(你的 CPU 该级所有信息均为空)指令,此时会发生什么问题?在此例基础上请思考:在 P7 中,清空流水线产生的空泡指令应该保留原指令的哪些信息?

    会导致存入 EPC 的值为 0,从而 eret 会跳转到错误的地址。

    BD 值与 PC 值。

  6. 为什么 jalr 指令的两个寄存器不能相同,例如 jalr $31, $31

    在流水线 CPU 中,jalr rd,rs 指令跳转到值为GPR[rs]的地址,同时将当前的 PC + 8 写入GPR[rd]

    rs == rt,如果延迟槽指令运行到 M 级时发生中断,则下一个周期$31将会写入 PC + 8。当异常处理程序运行结束后,重新执行jalr指令时,$31的值发生了变化,不再跳转到原来的地址,而是跳转到延迟槽的下一条指令,导致错误。