计算机组成CO:P4-Verilog搭建单周期CPU
处理器为 32 位单周期处理器,不考虑延迟槽。支持指令集:add, sub, ori, lw, sw, beq, lui, jal, jr, nop。其中 nop 为空指令,机器码 0x00000000;add, sub 按无符号处理,不考虑溢出;顶层有效驱动信号包括且仅包括同步复位信号 reset 和时钟信号 clk 。
IM(指令存储器)
在 ISE 中,我们直接将指令的十六进制代码从 code.txt 中读到 IM ,使用 $readmemh 指令完成相应功能即可。
该模块接收由 NPC 传入的指令,输出 PC 指令作为 NPC 的输入,在 NPC 模块中进一步进行跳转或累加。
| 信号名 | 方向 | 描述 |
|---|---|---|
| clk | I | 时钟信号 |
| reset | I | 同步复位信号 |
| NPC | I | 32 位 PC 指令输入信号 |
| Instr | O | 32 位指令信号 |
| PC | O | 32 位 PC 指令输出信号 |
1 | module IM( |
PC(程序计数器)
不同于上一个项目的 PC ,考虑到实际使用,这里的 PC 将用于实现指令地址的累加、跳转和复位。复位后,指令地址将指向 0x00003000。
| 信号名 | 方向 | 描述 |
|---|---|---|
| PC | I | 32 位 PC 输入 |
| imm16 | I | 16 位立即数 |
| imm26 | I | 26 位立即数 |
| BranchOp | I | 3 位跳转方式 |
| jrreg | I | 32 位寄存器值,用于 jr 跳转 |
| NPC | O | 32 位新指令地址 |
1 | module PC( |
GRF(通用寄存器组)
使用 reg [31:0] regs[0:31]; 构造 32 个寄存器,并全部初始化为 0 。时钟上升沿且写使能信号高电平时,使用 $display 进行输出。
注意 0 号寄存器的值永远为 0 。
| 信号名 | 方向 | 描述 |
|---|---|---|
| clk | I | 时钟信号 |
| reset | I | 同步复位信号 |
| WE | I | 写使能信号 |
| A1 | I | 5 位地址输入信号,读取输出到 RD1 |
| A2 | I | 5 位地址输入信号,读取输出到 RD2 |
| A3 | I | 5 位地址输入信号,作为目标写入寄存器 |
| WD | I | 32 位数据输入 |
| PC | I | 32 位指令地址信号,用作 display 输出 |
| RD1 | O | 32 位 A1 数据输出 |
| RD2 | O | 32 位 A2 数据输出 |
1 | module GRF( |
DM(数据存储器)
使用 reg [31:0] dm[0:3071]; 构造内存,初始化为 0 。
| 信号名 | 方向 | 描述 |
|---|---|---|
| clk | I | 时钟信号 |
| reset | I | 同步复位信号 |
| WE | I | 写使能信号 |
| addr | I | 16 位写入地址 |
| WD | I | 32 位写入数据 |
| PC | I | 32 位指令地址,用于 display 输出 |
| RD | O | 32 位数据输出 |
1 | module DM( |
EXT(扩展单元)
| 信号名 | 方向 | 描述 |
|---|---|---|
| Imm | I | 16 位立即数 |
| ExtOp | I | 扩展方式,高电平为符号扩展 |
| Imm32 | O | 32 位输出 |
ALU(算术逻辑单元)
| 信号名 | 方向 | 描述 |
|---|---|---|
| ALUctr | I | 3 位选择信号 000:加法 001:减法 010:或 011:逻辑左移 16 位 |
| Op1 | I | 32 位操作数 |
| Op2 | I | 32 位操作数 |
| Zero | O | 判断运算结果是否为 0 |
| ALUResult | O | 32 位运算结果 |
1 | module ALU( |
Controller(控制器)
| 信号名 | 方向 | 描述 |
|---|---|---|
| Instr | I | 32 位指令 |
| RegDst | O | 控制信号,确定目标寄存器的指定方式 0:将结果写入 rt 寄存器(I 型指令)1:将结果写入 rd 寄存器(R 型指令) |
| ALUSrc | O | 控制信号,选择 ALU 的第二个源操作数 0:来自寄存器文件的一个寄存器 1:来自指令中的立即数字段 |
| MemtoReg | O | 控制信号,控制数据从内存到寄存器的传输 0:执行指令后,不将内存中的数据写入寄存器文件 1:执行指令后,将内存中的数据写入寄存器文件 |
| RegWrite | O | 控制信号,控制数据是否从内部数据路径写入寄存器文件 0:不应该将执行结果写入寄存器文件 1:应该将执行结果写入寄存器文件中的一个指定寄存器 |
| MemWrite | O | 控制信号,指示是否应该将数据从数据缓存写入DM |
| BranchOp | O | 3 位控制信号,表示跳转的种类 001:beq 跳转 010:jal 跳转,需要将 PC + 4 写入 31 号寄存器 ($ra) 011:jr 跳转 |
| ExtOp | O | 控制信号,操作数扩展信号 0:0扩展 1:符号扩展 |
| ALUctr | O | 3 位控制信号,选择 ALU 运算符 |
| imm16 | O | 16 位立即数 |
| imm26 | O | 26 位立即数 |
| rs | O | 5 位 rs |
| rt | O | 5 位 rt |
| rd | O | 5 位 rd |
| 指令 | add | sub | ori | lw | sw | beq | lui | jal | jr |
|---|---|---|---|---|---|---|---|---|---|
| RegDst | 1 | 1 | |||||||
| ALUSrc | 1 | 1 | 1 | 1 | |||||
| MemtoReg | 1 | ||||||||
| RegWrite | 1 | 1 | 1 | 1 | 1 | 1 | |||
| MemWrite | 1 | ||||||||
| BranchOp | 001 | 010 | 011 | ||||||
| ExtOp | 1 | 1 | 1 | ||||||
| ALUctr | 000 | 001 | 010 | 000 | 000 | 001 | 011 | 000 | 000 |
| 指令 | op5 | op4 | op3 | op2 | op1 | op0 |
|---|---|---|---|---|---|---|
| add | 0 | 0 | 0 | 0 | 0 | 0 |
| sub | 0 | 0 | 0 | 0 | 0 | 0 |
| ori | 0 | 0 | 1 | 1 | 0 | 1 |
| lw | 1 | 0 | 0 | 0 | 1 | 1 |
| sw | 1 | 0 | 1 | 0 | 1 | 1 |
| beq | 0 | 0 | 0 | 1 | 0 | 0 |
| lui | 0 | 0 | 1 | 1 | 1 | 1 |
| nop | 0 | 0 | 0 | 0 | 0 | 0 |
| jal | 0 | 0 | 0 | 0 | 1 | 1 |
| jr | 0 | 0 | 0 | 0 | 0 | 0 |
| 指令 | func5 | func4 | func3 | func2 | func1 | func0 |
|---|---|---|---|---|---|---|
| add | 1 | 0 | 0 | 0 | 0 | 0 |
| sub | 1 | 0 | 0 | 0 | 1 | 0 |
| nop | 0 | 0 | 0 | 0 | 0 | 0 |
| jr | 0 | 0 | 1 | 0 | 0 | 0 |
1 | module Controller( |
mips(顶层模块组装)
机器码指令
J 类型指令
本次项目中,jal, jr 是 J 类型指令。jal:
| op | target |
|---|---|
| 6bit | 26bit |
注意:将 PC 的最高 4 位与 target 拼接,最低两位补 0 作为新的 PC 值;原 PC 值 +4 存在 31 号寄存器中。jr:
| op | rs | padding |
|---|---|---|
| 6bit | 5bit | 21bit |
注意:取出 rs 寄存器的值,作为新的 PC 。
寄存器编码
| reg | name | usage |
|---|---|---|
| $0 | $zero | 常量 0 |
| $1 | $at | 保留给汇编器使用的临时变量 |
| 函数调用返回值 | ||
| 函数调用参数 | ||
| 临时变量 | ||
| 需要保存的变量 | ||
| 临时变量 | ||
| 留给操作系统使用 | ||
| $28 | $gp | 全局指针 |
| $29 | $sp | 堆栈指针 |
| $30 | $fp | 帧指针 |
| $31 | $ra | 返回地址 |
要点简析
1 | module mips( |
照着 P3 翻译即可,没啥需要具体说的。和 P3 的不同之处应该是使用 3bit 存储跳转指令了吧,其实也方便课上添加跳转。祝 P4 上机顺利!
思考题
- 阅读下面给出的 DM 的输入示例中(示例 DM 容量为 4KB,即 32bit × 1024字),根据你的理解回答,这个 addr 信号又是从哪里来的?地址信号 addr 位数为什么是 [11:2] 而不是 [9:0] ?
addr 直接与 ALUResult 相连,因为 DM 的写入地址一般是一个寄存器值与立即数偏转相加的结果;因为在 DM 中是按字存储的,需要舍弃最低两位。
- 思考上述两种控制器设计的译码方式,给出代码示例,并尝试对比各方式的优劣。
指令对应的控制信号如何取值:即为笔者使用的方法,这种方式非常直观,且符合逻辑,从机器码到操作数再到控制信号,一目了然。
控制信号每种取值所对应的指令:似乎指令与控制信号之间联系更加紧密了(?),没有太多体会。
- 在相应的部件中,复位信号的设计都是同步复位,这与 P3 中的设计要求不同。请对比同步复位与异步复位这两种方式的 reset 信号与 clk 信号优先级的关系。
同步复位,clk 优先级高;异步复位,reset 优先级高。
- C 语言是一种弱类型程序设计语言。C 语言中不对计算结果溢出进行处理,这意味着 C 语言要求程序员必须很清楚计算结果是否会导致溢出。因此,如果仅仅支持 C 语言,MIPS 指令的所有计算指令均可以忽略溢出。 请说明为什么在忽略溢出的前提下,addi 与 addiu 是等价的,add 与 addu 是等价的。提示:阅读《MIPS32® Architecture For Programmers Volume II: The MIPS32® Instruction Set》中相关指令的 Operation 部分。
addi 和 addiu 都是将 rs 与立即数(符号扩展)相加,结果存储在 rd 中;add 和 addu 都是将 rs 和 rt 相加,结果存储在 rd 中。
- Title: 计算机组成CO:P4-Verilog搭建单周期CPU
- Author: BaconToast
- Created at : 2025-11-18 11:15:55
- Updated at : 2025-11-18 12:52:49
- Link: https://bacontoast-pro.github.io/2025/11/18/co/p4/
- License: This work is licensed under CC BY-NC-SA 4.0.