BUAA-CO-P7 MIPS微系统

通关纪念!

目标简述

在P6的基础上,实现一个简单的计算机系统(MIPS微系统)。

三种外设

  • Timer(计时器):计时部件,可以按照配置定时产生时钟中断
  • Memory(存储器):外置IM、DM(类似P6)
  • InterruptGenerator(中断发生器):随机产生外部中断信号,产生的中断信号在CPU响应前会持续置高

支持异常处理的CPU

  1. 异常

    1. 内部异常:
      • 由于指令执行错误导致的“事件”
      • 例如:加法溢出,除法除零
    2. 外部中断
      • 由于外部设备信号导致的“事件”
      • 例如:计时器信号
  2. 内部异常检测

  3. 外部中断检测

  4. CP0:接收异常,储存异常类型,实现精确异常

  5. 异常处理

软硬件接口

  1. 外设的接口设计——系统桥
    • 连接CPU和外设的功能设备

      • CPU — SystemBridge — { DM , Timer , InterruptGen , … }
    • 给CPU提供一种接口,使得CPU可以按地址读写复杂多变的外设

      • CPU不再关心外设长什么样,它只会按地址读写(用统一的方法访问外设)
      • 所有外设都是“DM”
  2. CPU的接口设计
    • 封装成单周期CPU

宏观PC

  • 把复杂的多级流水线CPU封装成单周期CPU
  • 宏观PC:整个 CPU “宏观”运行指令所对应的PC地址。我把CP0放在了M级,所以把M_PC作为宏观PC。
    • 该指令之前的所有指令序列对 CPU 的更新已完成
    • 该指令及其之后的指令序列对 CPU 的更新未完成

顶层设计

整体层次 顶层数据通路示意图

任务清单

最终架构图 设计思路:从CP0开始,一步步推进。

协处理器CP0

  • 功能 接收内部异常和外部中断,产生异常控制

  • 设计 本质上是一组特殊寄存器。

    寄存器 编号 功能
    SR(State Register) 12 配置异常的功能,即:
    当前CPU:
    1.能不能处理外部中断(IE)
    2.能处理哪些外部中断(IM)
    3.是否正在处理异常(EXL)
    Cause 13 记录异常发生的原因和情况
    - 外部中断源:IP
    - 内部异常类型:ExcCode)
    EPC 14 记录异常处理结束后需要返回的PC
    寄存器 功能域 位域 解释
    SR(State Register) IM(Interrupt Mask) 15:10 分别对应六个外部中断源,相应位置 1 表示允许中断,置 0 表示禁止中断。
    控制能处理哪些外部中断
    SR(State Register) EXL(Exception Level) 1 只要进入异常(I&E)就置1,处理完异常、执行eret时置0
    SR(State Register) IE(Interrupt Enable) 0 全局中断使能,该位置 1 表示允许外部中断
    Cause BD(Branch Delay) 31 异常是否发生在分支延迟槽中
    tip:只有内部异常才可能导致BD为1
    Cause IP(Interrupt Pending) 15:10 为 6 位待决的中断位,分别对应 6 个外部中断,相应位置 1 表示有中断,置 0 表示无中断
    反映外部中断源
    Cause ExcCode 6:2 内部异常编码,记录当前发生的是什么内部异常。
  • 模块规格

    信号名 方向 位宽 描述
    clk I 1 时钟信号
    reset I 1 复位信号
    CP0WE I 1 CP0 写使能信号
    CP0Address I 5 要访问哪个CP0寄存器(SR/Cause/EPC)
    CP0WD I 32 写入 CP0 的数据
    VPC I 32 受害 PC(发生异常时的 PC)
    BDIn I 1 是否处于延迟槽
    ExcCodeIn I 5 内部异常类型编码
    HWInt I 6 外部硬件中断信号,对应Cause_IP/SR_IM
    EXLclr I 1 清除 EXL,用于执行 eret
    CP0RD O 32 从 CP0 读出的数据
    EPC O 32 EPC 寄存器输出值
    Req O 1 是否进入异常处理程序
    1: PC ← 异常入口地址

tips

  1. CP0AddressCP0WE 是针对 CPU 执行 mfc0 / mtc0 指令时访问 CP0 寄存器用的,而异常更新并不会经过CP0Address。这两条路径几乎完全分离;

  2. Req生成逻辑

    • Req = 外部中断|内部异常
    • 外部中断(Interrupt): 能处理中断(SR.IE) & 未在处理异常(!SR.EXL) & 存在外部中断源且该中断源可以被处理(SR.IM&HWInt) 需要处理外部中断(Interrupt置高)
      • 外部中断看情况处理!
    • 内部异常(Execption): 未在处理异常(!SR.EXL) & 存在内部异常(ExcCodeIn) 需要处理内部异常(Execption置高)
      • 内部异常必须处理!
  3. CP0何时被写? 顺序如下:

    1. HWInt无论如何会被更新到Cause.IP
    2. Req == 1 更新SR.EXL/Cause.BD/EPC
      1. Exception? 更新Cause.ExcCode,否则置零
    3. EXLclr == 1 即:执行eret,将SR.EXL置零
    4. CP0WE == 1 写对应寄存器
      • CP0 寄存器的初始值均为 0,未实现位始终保持 0
  4. CP0接线

    • M_CP0WE\M_EXLclr来自控制信号M_ControlBus(增加Control)
    • M_PC来自M
    • M_ExcCodein来自内部异常检测,M_HWInt来自外部异常检测
    • M_BDin:BranchControl生成BDin信号,向后流水,从Reg_M取出
    • M_CP0RD:向后流水,传入WDsel
    • Req:接入IFU/Reg_D/Reg_E/Reg_M,实现精确异常
    • EPC:核心在于理解eret没有延迟槽
      • 绝对不执行已经被取到F中的错误指令(eret后面紧跟的指令清空)
      • eret指令在D时,插入一个 nop(类似likely清空延迟槽——D_Flush)
  5. Req如何实现精确异常?

    • 精确异常:
      • 异常受害指令之前的所有指令都生效
      • 异常指令以及之后的指令都不产生任何副作用
      • 返回后从异常指令(or上一条(BDin))重新执行
    • 流水线表现
      • W级:正常执行
      • M级:指令停止,进入异常处理
      • E/D/F:flush
    • 优先级:reset > Req > stall > (To_nop) > likely造成的D_flush
    • 具体实现
      • IFU:PC跳转至异常处理地址
      • Reg_D:清空instr,PC正常流水
      • Reg_E:清空所有,PC、BD正常流水
      • Reg_M:清空所有,PC、BD正常流水
      • Reg_W:清空所有
    • 特别规定,在进入中断或异常状态时,如果受害指令及其后续指令已经改变了MDU的状态,则无需恢复。考虑以下情况:
      • mult 在 E 级启动了乘法运算(其它乘除法指令同理),流水到 M 级时产生了中断
        • MDU不用恢复
      • mthi 在 E 级修改了 HI 寄存器(mtlo 同理),流水到 M 级时产生了中断。
        • MDU不用恢复
      • mult 在 E 级,受害指令在 M 级,此时还未改变 MDU 状态,不应开始乘法计算,其它乘除法指令同理。
      • mthi 在 E 级,受害指令在 M 级,此时还未改变 MDU 状态,不应修改 HI 寄存器的值,mtlo 同理。

    总之:

    • MDU接入req。如果req&!Busy,那么MDU freeze(MDU不应开始运算/改变HI、LO状态)
  6. 关于PC的处理

    • 核心问题:flush造成的nop的PC到底应该怎么取,才保证外部中断后EPC更新不出错?
    • nop的分类
      • 天然指令nop:不管
      • 异常指令视作nop:PC、BDin、ExcCode正常流水,其它清零
      • D_flush替换的nop:PC正常流水
      • stall插入的bubble(nop):PC、BDin正常流水,其它清零
  7. 中断和异常优先级:中断优先级高于异常优先级,即当有异常提交至 CP0 寄存器时,若有中断发生,则硬件应先响应中断强制将Cause.IP记为5'b0

内部异常检测与流水

  • 异常与中断码

    异常码 助记符与名称 指令与类型 描述
    0 Int(外部中断) 所有指令 中断请求,来源于计时器与外部中断。
    4 AdEL(取指异常) 所有指令 PC 地址未字对齐。
    PC 地址超过 0x3000 ~ 0x6ffc。
    4 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 算术溢出。
  • 指令异常检测位置

    异常与中断码 异常 检测位置(到该阶段才能检测) 检测指标
    0 外部中断Int E \
    4 AdEL D PC
    4 AdEL E ALUresult
    5 AdES E ALUresult/operand1、operand2
    8 Syscall D Instrction
    10 RI(未知指令) D Instruction
    12 Ov(算术溢出) E operand1、operand2、ALUop
  • ExceptionDetect(指令异常检测模块)

    • 功能 检测内部异常类型,生成ExcCode
    • 模块规格
      信号名 方向 位宽 描述
      D_ControlBus I 32 D ControlBus
      E_ControlBus I 32 E ControlBus
      D_PC I 32 D PC
      E_ExcCode I 5 从E取出的ExcCode
      E_operand1 I 32 ALU operand1
      E_operand2 I 32 ALU operand2
      D_ExcCode O 5 存入E的ExcCode
      E_NewExcCode O 5 真正存入M的ExcCode
      2nop O 1 是否将D中指令视为nop
  • tips:

    • 发生取指异常或 RI 异常后视为 nop 直至提交到 CP0(保留BDin,PC和ExcCode)
    • 内部异常优先级:AdEL最大(先有取,再有译)
    • Timer中count映射的地址?
      • 看Timer中count的定义 & 整个Timer映射的地址范围
      • e.g.
        • Timer0: 0x0000_7F00 ~ 0x0000_7F0B (长度 12 字节 = 3 个 4 字节寄存器)
        • Timer:``define count mem[2]`
        • count:0x0000_7F08 ~ 0x0000_7F0B

BDinGenerator

  • BDin 异常发生的这条指令是否位于延迟槽(B/J - 延迟槽(该指令)) 上一条指令是不是B/J

  • 功能 生成BDin,向后流水

  • 原理 分支跳转指令无论跳转与否,延迟槽指令为受害指令时 BD 均需要置位。

  • 模块规格

    信号名 方向 位宽 描述
    D_ifB I 1 D级指令是否是B指令
    D_ifJ I 1 D级指令是否是J指令
    BDin O 1 D级指令紧跟的指令是否位于延迟槽

tip:eret没有延迟槽但要跳转

Bridge系统桥

  • 功能

    1. 地址解码:当前地址属于哪个设备
    2. 把CPU的读写信号、写数据转发给对应外设
    3. 从对应外设返回数据给CPU
  • 架构

      CPU 给地址、数据、byteen
      ↓
      Bridge 内部解码
      ↓
      Bridge 产生 DM_Byteen / Timer_Byteen / Int_Byteen
      ↓
      Bridge 选择外设返回数据 RD
    

计时器

  • 功能:根据设定的时间来定时产生中断信号

单周期CPU的封装

宏观PC:M_PC

HCU新增控制

1
2
3
4
wire stall_eret = D_eret & (
(E_mtc0 & (E_rd_addr == 5'd14)) ||
(M_mtc0 & (M_rd_addr == 5'd14))
);

DM

  • DM写入要屏蔽异常 wire M_TrueMemWrite = M_MemWrite & (!Req)

思考题

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

    • 通过外设控制器发出的中断信号 + OS 注册的中断服务程序,CPU 被动接收并处理输入数据。
  2. 请思考为什么我们的 CPU 处理中断异常必须是已经指定好的地址?如果你的 CPU 支持用户自定义入口地址,即处理中断异常的程序由用户提供,其还能提供我们所希望的功能吗?如果可以,请说明这样可能会出现什么问题?否则举例说明。(假设用户提供的中断处理程序合法)

    • CPU 在异常发生时必须立即进入可信、统一、受保护的处理入口,才能保证系统继续正确运行。
    • 如果允许用户自定义入口,虽然仍能处理中断,但会出现无法保存现场、破坏内核权限、系统不可恢复等问题,使 CPU 无法提供可靠的操作系统支持。
  3. 为何与外设通信需要 Bridge?

    • 我们可能随时需要增加外设,但不可能每增加一种外设就给CPU增加响应接口。为此我们设计Bridge来连接外设和CPU,将所有外设都映射为一段地址(“DM”),使得CPU能以统一的方式访问不同外设。
  4. 请阅读官方提供的定时器源代码,阐述两种中断模式的异同,并分别针对每一种模式绘制状态移图。

    • 异:计时结束动作不同
      • 模式0:单次中断,计时结束持续输出中断信号
      • 模式1:循环计时,每次计时归零输出一周期中断信号
    • 同:计时过程相同 timer
  5. 倘若中断信号流入的时候,在检测宏观 PC 的一级如果是一条空泡(你的 CPU 该级所有信息均为空)指令,此时会发生什么问题?在此例基础上请思考:在 P7 中,清空流水线产生的空泡指令应该保留原指令的哪些信息?

    • 问题:EPC出错
    • 保留PC、BDin
  6. 为什么 jalr 指令为什么不能写成 jalr $31, $31?

    • 从jalr的语义来看,PC转向被调用函数,函数入口地址保存在 GPR[rs]中。但如果rs和rd相同,那么将PC+8写入rd的操作就会覆盖保存的函数入口地址,使得程序无法返回调用点。

Copyright © 2026 CRZ's Blog

Powered by Hexo • Theme Candelas