一个基于 FPGA 实现的简易 8 位多周期 CPU 项目。
这个 CPU 采用 Verilog 编写,包含 4 个 8 位通用寄存器、8 位程序计数器(PC)、16 位指令寄存器(IR)、256×16 指令 ROM、256×8 数据 RAM、ALU、标志寄存器(Z/C)以及多周期控制器。项目还接入了开发板上的 数码管和 LED,可以直接在硬件上观察 CPU 的执行过程和内部状态。
从工程文件可以看出,本项目已经配置好了完整的 FPGA 工程,顶层模块为 cpu_top,并包含对应的约束文件和示例程序。
实现了一个小型教学型 CPU,具备以下特点:
- 8 位数据通路
- 16 位定长指令
- 4 个 8 位通用寄存器
R0 ~ R3 - 8 位地址空间
- 指令 ROM:256 × 16 bit
- 数据 RAM:256 × 8 bit
- 支持算术、逻辑、移位、数据搬运、访存、跳转和停机指令
- 采用 多周期控制器(FSM) 执行指令
- 支持 连续运行 / 单步执行 两种调试模式
- 可通过数码管和 LED 观察寄存器、PC、ALU、RAM 等内部状态
顶层模块为 cpu_top.v
控制器采用多周期有限状态机,主要状态包括:
FETCH:取指,同时PC <- PC + 1DECODE:译码EXEC:执行 ALU 运算或跳转判断MEM:进行存储器访问WRITEBACK:将结果写回寄存器,并在需要时更新标志位HALT:停机
所有指令均为 16 位,格式如下:
[15:12] opcode
[11:10] rd
[ 9: 8] rs
[ 7: 0] imm8 / addr
其中:
opcode:操作码rd:目标寄存器编号rs:源寄存器编号imm8/addr:8 位立即数或地址
寄存器编号约定:
00 -> R001 -> R110 -> R211 -> R3
| Opcode | 指令 | 语义 | 说明 |
|---|---|---|---|
0x0 |
LDI rd, imm8 |
R[rd] <- imm |
将 8 位立即数写入寄存器 |
0x1 |
MOV rd, rs |
R[rd] <- R[rs] |
寄存器间数据传送 |
0x2 |
LOAD rd, [addr] |
R[rd] <- MEM[addr] |
从 RAM 读取数据到寄存器 |
0x3 |
STORE rs, [addr] |
MEM[addr] <- R[rs] |
将寄存器数据写入 RAM |
0x4 |
ADD rd, rs |
R[rd] <- R[rd] + R[rs] |
加法,结果写回 rd |
0x5 |
SUB rd, rs |
R[rd] <- R[rd] - R[rs] |
减法,结果写回 rd |
0x6 |
AND rd, rs |
R[rd] <- R[rd] & R[rs] |
按位与 |
0x7 |
OR rd, rs |
R[rd] <- R[rd] | R[rs] |
按位或 |
0x8 |
XOR rd, rs |
R[rd] <- R[rd] ^ R[rs] |
按位异或 |
0x9 |
NOT rd |
R[rd] <- ~R[rd] |
按位取反 |
0xA |
SHL rd |
R[rd] <- R[rd] << 1 |
左移 1 位 |
0xB |
SHR rd |
R[rd] <- R[rd] >> 1 |
右移 1 位 |
0xC |
JMP addr |
PC <- addr |
无条件跳转 |
0xD |
JZ addr |
若 Z==1,则 PC <- addr |
当 Z=1 时跳转 |
0xE |
JC addr |
若 C==1,则 PC <- addr |
当 C=1 时跳转 |
0xF |
HLT |
停机 | 停机 |
详细请见指令集
顶层模块,负责:
- 复位与时钟分频
- 单步按键消抖
- 连接 PC、ROM、IR、寄存器堆、ALU、RAM 和控制器
- 选择数码管显示内容
- 输出 LED 调试状态
多周期控制器,内部使用有限状态机,根据当前状态和 opcode 产生:
ir_loadpc_loadpc_srcreg_wemem_weflags_wealu_opwb_selhalt
程序计数器模块,支持复位和条件装载。
指令寄存器,在取指阶段锁存 ROM 输出。
4×8 bit 寄存器堆,支持双读单写。
实现如下 ALU 操作:
- ADD
- SUB
- AND
- OR
- XOR
- NOT
- SHL
- SHR
- PASS
指令 ROM,容量 256×16,通过:
$readmemh("prog.mem", rom);从外部文件加载程序。
数据 RAM,容量 256×8,采用同步写、组合读方式。
数码管扫描显示模块,用于显示 CPU 调试信息。
根据顶层模块定义:
key[0]:复位键,低电平有效key[1]:单步执行按键(已做同步和消抖)SW[3]:运行模式选择0:连续慢速运行(约 100Hz)1:单步执行
SW[2:0]:数码管显示内容选择
SW[2:0] |
显示内容 |
|---|---|
000 |
4 个寄存器的值 {R3,R2,R1,R0} |
001 |
当前指令字段拆分:PC / opcode / rd / rs / imm8 |
010 |
R0 的值(适合十进制观察) |
011 |
寄存器堆两个读口数据 rdata1 / rdata2 |
100 |
Z/C 标志位 + ALU 输出 |
101 |
当前 imm8 地址及 RAM 读出数据 |
110 |
next_pc |
111 |
当前写回数据 rf_wdata |
led_data[3:0] 的功能与运行模式有关:
- 当
SW[3] = 0(连续运行)时:- LED 显示
R0的低 4 位
- LED 显示
- 当
SW[3] = 1(单步调试)时:- LED 显示
{halt, mem_we, reg_we, pc_load} - 便于观察控制器状态
- LED 显示
根据当前仓库内容,工程主要文件如下:
FPGA_8bit_CPU/
├─ cpu_top.v # 顶层模块
├─ control_unit.v # 多周期控制器
├─ pc.v # 程序计数器
├─ ir.v # 指令寄存器
├─ register_file.v # 寄存器堆
├─ alu.v # 算术逻辑单元
├─ instr_rom.v # 指令 ROM
├─ data_ram.v # 数据 RAM
└─ digital_show.v # 数码管显示模块
- 使用工程文件
sm_scan.al打开项目 - 检查源文件和约束文件是否完整导入
- 确保
prog.mem与工程位于正确路径下,能够被instr_rom.v成功加载 - 进行综合、布局布线并生成比特流
- 下载到开发板运行
如果你想测试自己的程序,只需要修改 prog.mem 中的 16 位机器码内容,然后重新综合下载即可。
显示斐波那契数列:
0400
0801
0001
1200
1D00
4E00
E000
1600
1B00
C003
| 地址 | 汇编指令 | 机器码 |
|---|---|---|
00 |
LDI R1, 0x00 |
0400 |
01 |
LDI R2, 0x01 |
0801 |
02 |
LDI R0, 0x01 |
0001 |
03 |
MOV R0, R2 |
1200 |
04 |
MOV R3, R1 |
1D00 |
05 |
ADD R3, R2 |
4E00 |
06 |
JC 0x00 |
E000 |
07 |
MOV R1, R2 |
1600 |
08 |
MOV R2, R3 |
1B00 |
09 |
JMP 0x03 |
C003 |
; R1 = a
; R2 = b
; R0 = 显示值
; R3 = 临时寄存器
START:
LDI R1, 0x00 ; a = 0
LDI R2, 0x01 ; b = 1
LDI R0, 0x01 ; 先显示第一个 1
LOOP:
MOV R0, R2 ; 显示当前项 b
MOV R3, R1 ; R3 = a
ADD R3, R2 ; R3 = a + b
JC START ; 若溢出,则重新开始
MOV R1, R2 ; a = b
MOV R2, R3 ; b = a + b
JMP LOOP