MIPS五段流水处理器I 顺序实现
尝试使用 Verilog 实现一个 mips 五段流水处理器。
MIPS 指令集
MIPS 指令集分为三类,总共 31 条,分别是
- I 型
- 指令内容中带有立即数
- 最多使用两个寄存器
- Op 字段用于区别不同指令
- J 型
- 长跳转类型
- 有且仅有一个立即数
- Op 字段用于区别不同指令
- R 型
- 仅使用寄存器的指令
- Op 字段为 0,使用 funct 字段区别
这三种指令的具体划分以及内容参照这篇博客, 本文中不再赘述。
流水线划分
经典的五段流水线划分为
- 取指
Fetch
- 从 PC 程序计数器指向的地址中取出指令
- 放入 IR 指令寄存器中
- PC += 指令长度
- 译码
Decode
- 根据 IR 中的指令内容,得到其所需要的
- 源操作数(寄存器)
- 目标操作数寄存器
- 根据 IR 中的指令内容,得到其所需要的
- 执行
Execute
- 将源操作数(对应的寄存器)加载进 ALU,然后将对应的运算结果保存进目标操作数寄存器
- 访存
Memory
(仅针对 load/store 指令)- load: 从存储器中读出 ALU 计算出的有效地址存储的数据
- store: 将寄存器中的数据输入 ALU 计算出的有效地址中
- 写回
Write Back
(仅针对除 store 以外的指令)- load: 将读出的数据存储到目标寄存器中
- 其他指令: 将 ALU 计算出的结果输出到目标寄存器中
稍微多提一句的是,由于我们想要首先制作的是顺序处理器,因此这 5 个步骤中很多地方是没有必要的,比如更新 PC 可以放在最后一步,但是因为我们希望能给流水线寄存器预留拓展,因此还是采用这样符合流水线的设计来制造我们的 cpu。
基本设计图
我们使用自顶向下的设计方法来设计我们的系统,得到的草图如下: 图片是我自己画的,可能比较草率,具体的设计可以参照《CSAPP》一书 P302 的这张图
因此我们可以得到需要的几个基本模块的划分:
- 控制器
- 用于控制传入、传出信号
- 作为顶层原件存在
- RAM(严格来说不能算为 cpu 的一部分)
- 使用冯诺依曼体系结构,同时存储数据和指令
- 寄存器组
- 流水线寄存器组 用于存储流水线过程中产生的各种信号和数据,对于用户不可见
- 内部寄存器组 用于存储对于用户可见的寄存器
- 五大单元,用于处理各个阶段
- 取指单元
- 译码单元
- 执行单元(主体构成为 ALU)
- 访存单元
- 写回单元
使用 verilog 的模块化设计,我们正好得到了一共 9 个模块,我们接下来的任务即设计这几个模块的具体输入以及输出,这里我们使用大写字母来标识某个阶段的输出,小写字母来标识上个阶段的输入。
模块设计
模块名称 | 输入 | 输出 |
---|---|---|
F 取指单元 | ram_in: 从 ram 接入的指令数据 | F_IR: 指令 F_PC: 新的程序计数器值 |
D 译码单元 | f_ir: 指令 | D_src: 源寄存器组 D_dst: 目标寄存器组 D_alufun: 运算种类 D_icode: 指令类型 |
E 执行单元 | d_src: 译码得到的输入 d_alufun: 译码得到的运算种类 d_dst d_icode |
E_val: 执行单元得到的运算结果 D_dst: 目标寄存器组 D_icode |
M 访存单元 | e_val: 执行单元得到的运算结果 d_src (store 指令): 放入存储器的数据 d_dst: 传给下一步 d_icode |
M_val: 从内存中读出的数据 E_val: 传给下一步 D_dst: 目标寄存器组 D_icode |
W 写回单元 | e_val: ALU 运算结果 d_dst: 目标寄存器组 m_val: 内存中读出的数据 d_icode |
根据上文的分析,发现由于有流水线寄存器这一机制的存在,不需要设置一个专门的控制器来进行相关的控制,每一阶段的运算只取决于上一阶段流水线寄存器的结果,这样一方面简化了设计,另一方面也让我们以后从顺序向流水线转化更加方便,下一步则是各个模块的 verilog 具体实现。
To be continue...
MIPS五段流水处理器I 顺序实现