简述: 本指南会在连线之前先定义一套完整的教学架构:8 位数据通路和地址空间、字节宽统一 RAM、以两个字节取回的 16 位指令、四个通用寄存器、ALU 与标志位、一条共享总线,以及一个微序列控制器。逐一构建并验证各子系统,确保任一时刻只有一个总线驱动器,随后运行一个五指令程序,计算
5 + 3并将8存入内存。
“构建 CPU”既可以指连接现成模块,也可以指进行晶体管级布局。本教程介于两者之间:使用寄存器、多路复用器、译码器、ALU、存储器和控制存储器等数字构件搭建处理器,同时将各构件内部的逻辑门视为可独立验证的设计单元。
目标不是模仿现代商用处理器核心,而是揭示每条指令实现都必须满足的约定:
- 取回正确的指令字节;
- 在译码期间保留这些字节;
- 精确传送预定的操作数;
- 执行定义好的操作;
- 只更新允许更新的体系结构状态;
- 选择下一条指令的正确地址。
1. 先固定架构
CPU 教学项目中的常见错误,是在构建过程中改变存储器宽度、指令宽度或 PC 递增方式。本文后续内容统一采用下列规格:
| 属性 | 教学 CPU 定义 |
|---|---|
| 数据宽度 | 8 位 |
| 地址宽度 | 8 位 |
| 存储器 | 256 个按字节寻址的位置,指令和数据统一存放 |
| 指令宽度 | 16 位,存为两个连续字节 |
| 指令字节顺序 | 高字节先存于指令起始地址 |
| 顺序 PC 前进 | 取指期间每次前进一个字节,共递增两次 |
| 通用寄存器 | 四个 8 位寄存器:R0–R3 |
| 操作数锁存器 | A 和 B,各 8 位 |
| 指令寄存器 | 16 位 |
| 标志位 | Z(零)、N(负)、C(进位)、V(有符号溢出) |
| 互连 | 一条 8 位教学总线 |
| 控制 | 微步骤计数器加控制 ROM |
8 位地址选择 256 个位置;此架构明确规定每个位置为一个字节,因此存储器为 256 字节。由于每条指令占用两个字节,其起始地址通常为偶数。
方框图
┌──────────────────────┐
│控制 ROM + │
│微步骤序列发生器 │
└──────────┬───────────┘
│控制信号
▼
┌────┐ ┌─────┐ ┌──────────────8 位共享总线 ───────────────┐
│PC │──▶│MAR ││││││││
└────┘ └──┬──┘ ┌▼──┐ ┌▼──┐ ┌▼──┐ ┌▼──┐ ┌▼──┐ ┌▼──┐ ┌▼───┐
││MDR││R0││R1││R2││R3││A ││B │
▼└─┬─┘ └───┘ └───┘ └───┘ └───┘ └┬──┘ └┬───┘
┌────────┐ │└──┬───┘
│256 × 8 ││┌────▼────┐
│RAM │││8 位 │
└────────┘ ││ALU │
│└────┬────┘
└──────▶ 16 位 IR ◀────────────────────┘
│
▼
操作码/ 字段
该图是概念性的。IR 从总线装入两个彼此独立的字节,ALU 的结果经由其自身被选中的输出路径抵达总线。
可打开顺序指令执行器作为参考,但本练习应以本文架构为基准规范。若模板采用不同的内部约定,应记录差异,不能在未作说明的情况下混用两套约定。
2. 定义指令格式
这套自定义 ISA 使用两种 16 位格式。
寄存器格式
15 12 11 8 7 4 3 0
┌───────────────┬────────────────┬────────────────┬────────────────┐
│操作码[3:0] │RA [3:0] │RB [3:0] │RD [3:0] │
└───────────────┴────────────────┴────────────────┴────────────────┘
立即数/地址格式
15 12 11 8 7 0
┌───────────────┬────────────────┬─────────────────────────────────┐
│操作码[3:0] │RD [3:0] │立即数 / 地址[7:0] │
└───────────────┴────────────────┴─────────────────────────────────┘
在这台四寄存器机器中,只有寄存器编码 0–3 合法。译码器必须拒绝或安全地陷阱处理 4–15,不能意外选中某根控制线。
| 操作码 | 助记符 | 格式 | 定义的效果 |
|---|---|---|---|
0 | NOP | — | 除 PC 在取指阶段的正常递增外,不改变体系结构状态 |
1 | LDI | 立即数 | RD ← imm8 |
2 | LD | 立即数 | RD ← RAM[addr8] |
3 | ST | 立即数 | RAM[addr8] ← RD |
4 | ADD | 寄存器 | RD ← RA + RB;更新标志位 |
5 | SUB | 寄存器 | RD ← RA − RB;更新标志位 |
6 | JMP | 立即数 | PC ← addr8 |
7 | JZ | 立即数 | 若 Z = 1,则 PC ← addr8 |
8 | HLT | — | 进入停机的控制器状态 |
其余操作码均为非法。调试期间应设置显式的 illegal_instruction 路径,或将其映射到安全的陷阱/停机状态。
3. 构建寄存器时不要门控时钟
一个 8 位寄存器由八个沿触发 D 触发器组成,它们共享一个时钟和一个逻辑装载使能。
对于每一位:
可在 D 输入前用一个 2:1 多路复用器实现该方程,或使用器件专用的时钟使能功能。让每个状态元件都使用同一个全局时钟。
不要通过将全局时钟与 load 做 AND 来产生本地时钟。未经控制的时钟门控在硬件中可能引入偏斜和毛刺。集成时钟门控单元与 FPGA 时钟控制资源是独立且具备时序意识的机制。
每个连接到总线的寄存器都需要:
- 一个来自总线的 8 位输入;
- 一个存储的 8 位输出;
- 一个
load_x控制信号;以及 - 一条允许驱动教学总线的
oe_x路径。
PC 还需要递增路径和显式装载路径。IR 需要独立的高字节和低字节装载。MAR 保存存储器地址;MDR 捕获或提供一个存储器数据字节。
复制寄存器之前先验证一个
测试以下情形:
load = 0:多个时钟周期后 Q 保持不变。load = 1:Q 在有效边沿捕获总线值。- 数据在边沿之外变化:Q 只在下一个启用的边沿变化。
- 若提供复位:Q 到达文档规定的状态。
- 输出使能关闭:寄存器不驱动总线。
数字逻辑仿真器能够验证该模型的逻辑时序,但不能复现模拟域亚稳态,也不能证明物理建立时间和保持时间满足要求。
4. 构建 ALU 并定义每个标志位
使用 A 和 B 作为稳定的操作数锁存器。ALU 是组合逻辑:它不得包含隐藏的体系结构状态。
alu_op | 操作 | 结果 |
|---|---|---|
000 | ADD | A + B |
001 | SUB | A − B |
010 | AND | A & B |
011 | OR | `A |
100 | XOR | A ^ B |
101 | NOT | ~A |
110 | SHL | A << 1,零填充 |
111 | SHR | 逻辑 A >> 1,零填充 |
ALU 指南详细介绍了各运算通路及输出多路复用器。核心算术设计包括:
- 一个八级全加器链;
- 以
A + (~B) + 1实现减法; - 并行按位逻辑门;
- 固定的一位移位器连线;以及
- 由
alu_op选择的输出多路复用器。
标志位定义
把标志位作为 ISA 的一部分来定义,包括逻辑和移位指令如何处理 C 与 V。
- Z: 结果的每一位均为零时为 1。
- N: 结果的第 7 位;仅对有符号运算将其解释为符号位。
- ADD 时的 C: 第 7 位的进位输出。
- SUB 时的 C: 发生无符号借位时为 1()。这与 DigiSim 内置 4 位和 8 位 ALU 的约定一致;加法器/减法器实现通过反相最终进位输出得到该值。
- ADD 时的 V: 操作数符号相同而结果符号相反。
- SUB 时的 V: 操作数符号不同且结果符号与 A 不同。
当减法通路使用反相 B 操作数和进位输入实现时,进位输入/进位输出的 XOR 等价公式适用于二进制补码加法与减法。参见CPU 标志位中的演算示例。
这套教学 ISA 在执行 ADD 和 SUB 时更新 Z、N、C 与 V。对于逻辑和移位操作,必须在编写微代码前明确并记录策略:保留 C/V,或为其定义新的含义。未明确定义的线路瞬时值不能作为体系结构行为。
ALU 验证表
至少测试:
| 情形 | 预期关注点 |
|---|---|
0x00 + 0x00 | Z |
0xFF + 0x01 | 无符号进位和零 |
0x7F + 0x01 | 有符号溢出和 N |
0x80 + 0x80 | 进位加有符号溢出 |
0x00 − 0x01 | 借位约定和 N |
0x80 − 0x01 | 有符号溢出 |
| AND/OR/XOR 恒等式 | 按位逻辑通路 |
0x81 的移位 | 方向、填充位、移出位策略 |
5. 构建共享总线
教学总线由八根导线组成,可有多个可能的源。一个核心不变量是:
可能的驱动源包括 PC、MDR、R0–R3、A、B、一个 IR 字节以及 ALU 结果。驱动源清单不能仅由“寄存器数量”推断,必须在控制规范中逐项列明。
在 DigiSim 中,可使用三态缓冲器对共享总线建模:
- 在一次传送期间恰好启用一个源;
- 为下一个有效边沿置位一个或多个目的端装载信号;以及
- 总线没有源驱动时绝不捕获总线。
争用是设计错误。物理输出冲突可能导致错误电压和过大电流;后果取决于工艺和持续时间,因此“芯片总会烧毁”并不是有用的规则。
现代 FPGA 通常以多路复用器而非真正的内部三态布线实现共享总线,ASIC 的实现方式也各不相同。三态总线仍是说明总线所有权与数据传送的清晰教学模型。
总线仲裁测试
在 R0 和 R1 中放入已知值:
- R0 =
0xAA - R1 =
0x55
分别验证仅 R0、仅 R1、无驱动器和两个驱动器。后两种都应被视为无效的控制器状态。然后添加断言或可见指示器,以便立即发现后续微代码错误。
6. 连接字节宽存储器
将仿真器中的 RAM 元件用作体系结构存储器模型。不能据此断言物理 RAM 就是 D 锁存器阵列:SRAM、DRAM、块 RAM 和综合得到的寄存器阵列采用不同的存储单元与时序机制。
该 CPU 的接口为:
ADDR[7:0],由 MAR 驱动;DATA[7:0],经由 MDR 或受控总线接口传送;mem_read;mem_write;以及- 若模型支持可变延迟,则使用可选的
ready。
本演练假定读请求可在一个专用微步骤内发出并捕获。若所选存储器为同步存储器或具有可见延迟,则应插入等待状态,并且仅在数据有效时捕获数据。
指令和数据共用相同的 256 字节。采用独立存储器的哈佛设计同样有效,但那会是不同的架构和取指时序。
有关可写存储与非易失/常量存储抽象之间的区别,参见RAM 与 ROM。
7. 构建微序列控制器
控制器将操作码、微步骤、标志位和机器状态转换为:
- 总线输出使能;
- 寄存器装载使能;
- ALU 操作;
- 存储器读/写控制;
- PC 递增/装载控制;
- 标志位装载;
- IR 高/低字节装载;以及
- 下一微步骤或结束指令控制。
一种实现采用控制 ROM:
控制地址= {操作码,微步骤}
控制字 = {oe 信号,load 信号,alu_op, 存储器控制,下一控制}
标志位可以限定所选操作——例如,对于 JZ,load_pc = microcode_load_pc AND Z——也可以参与控制存储器地址。显式的下一状态逻辑通常比假定微步骤计数器总会递增更清晰。
这是一个用于教学的微代码控制器。真实处理器会采用组合译码、可编程控制存储器、状态机和序列发生器的多种组合。不要把 RISC/CISC 标签当作实现上的捷径。
安全默认值
每个未使用的控制 ROM 条目都应:
- 禁用所有总线驱动器;
- 禁用存储器写;
- 禁用体系结构寄存器写;以及
- 进入已定义的非法指令或停机路径。
未初始化的控制字绝不能变成“将任意数据写入任意存储器”。
8. 一致地取回两个指令字节
由于 RAM 与数据通路均为八位宽,而每条指令为 16 位,取指需要两次字节读取。按照定义,先存储高字节。
假定所选 RAM 的一次读取只需一个微步骤:
| 步骤 | 寄存器传送 | 重要控制 |
|---|---|---|
| F0 | MAR ← PC | oe_pc, load_mar |
| F1 | MDR ← RAM[MAR]; PC ← PC + 1 | mem_read, load_mdr, inc_pc |
| F2 | IR[15:8] ← MDR | oe_mdr, load_ir_hi |
| F3 | MAR ← PC | oe_pc, load_mar |
| F4 | MDR ← RAM[MAR]; PC ← PC + 1 | mem_read, load_mdr, inc_pc |
| F5 | IR[7:0] ← MDR | oe_mdr, load_ir_lo |
F5 之后,IR 含有一条完整指令,PC 已指向下一条指令。译码会选择执行序列。
若 RAM 从地址有效到数据有效之间存在等待时间,则应将 F1 和 F4 分别拆分为请求、等待和捕获步骤。不能把一次 16 位取指隐含为一次字节宽传送。
9. 定义执行微操作
ADD RD, RA, RB
| 步骤 | 传送 |
|---|---|
| E0 | A ← R[RA] |
| E1 | B ← R[RB] |
| E2 | R[RD] ← A + B; FLAGS ← ALU flags |
| E3 | 结束指令;返回 F0 |
RA、RB 和 RD 通过译码使能选择 R0–R3 中的一个。无效寄存器编码必须进入非法指令路径。
LDI RD, imm8
| 步骤 | 传送 |
|---|---|
| E0 | R[RD] ← IR[7:0] |
| E1 | 结束指令 |
LD RD, addr8
| 步骤 | 传送 |
|---|---|
| E0 | MAR ← IR[7:0] |
| E1 | MDR ← RAM[MAR] |
| E2 | R[RD] ← MDR |
| E3 | 结束指令 |
ST RD, addr8
| 步骤 | 传送 |
|---|---|
| E0 | MAR ← IR[7:0] |
| E1 | MDR ← R[RD] |
| E2 | RAM[MAR] ← MDR |
| E3 | 结束指令 |
JMP 和 JZ
- JMP:
PC ← IR[7:0],随后结束。 - JZ:仅当 Z 为 1 时装载 PC,但无论如何都结束。
HLT
进入一个不发出写操作的稳定控制器状态。手动停止仿真器时钟较为方便,但即使时钟边沿继续到达,体系结构状态也必须保持明确定义。
10. 手工汇编并装载测试程序
该程序装入 5 和 3,将它们相加到 R2,把结果存到地址 0x10,然后停机。
| 起始地址 | 指令 | 16 位机器字 | 内存中的字节 |
|---|---|---|---|
0x00 | LDI R0, #5 | 0x1005 | 0x10 0x05 |
0x02 | LDI R1, #3 | 0x1103 | 0x11 0x03 |
0x04 | ADD R2, R0, R1 | 0x4012 | 0x40 0x12 |
0x06 | ST R2, 0x10 | 0x3210 | 0x32 0x10 |
0x08 | HLT | 0x8000 | 0x80 0x00 |
将这十个字节装入地址 0x00 至 0x09,然后复位:
- PC =
0x00 - 控制器微步骤 = F0
- halted = 0
- 除非控制字选中某个驱动器,否则所有总线驱动器均禁用
单步执行第一次取指,并确认:
- F2 之后,IR 高字节为
0x10; - F5 之后,IR 为
0x1005; - PC 为
0x02; - LDI 将
0x05放入 R0;以及 - 没有其他寄存器或内存位置发生变化。
停机时:
- R0 =
0x05 - R1 =
0x03 - R2 =
0x08 - RAM[
0x10] =0x08 - PC =
0x0A
这些最终值是必要条件,但并不充分。损坏的 CPU 也可能破坏另一个位置,却仍然得到这些值。
11. 验证不变量,而不只是最终答案
添加以下检查:
- 任一时刻总线输出使能不超过一个;
- 仅在 ST 写步骤期间置位存储器写;
- 仅在 F2 和 F5 期间装载 IR 字节;
- 每次正常取指中 PC 恰好递增两次;
- 仅由定义为更新标志位的指令写入标志位;
- 无效操作码或寄存器编码不会导致意外写入;
- 停机状态不产生体系结构副作用;以及
- 复位进入已知的控制器状态。
对每条指令,记录如下轨迹:
时钟,微步骤,PC, IR, 总线所有者,总线值,
寄存器装载,存储器读/写,ALU 操作,标志位
测试失败时,应定位第一个偏离预期轨迹的周期。若从最终错误值反向调试,往往要经过多个周期才能定位根因。
示波器调试指南说明如何检查建模波形,同时不把它们与物理时序签核混为一谈。
12. 常见故障模式
| 症状 | 可能原因 | 首先检查 |
|---|---|---|
| IR 含有互换的字节 | 装载器与取指使用不同的指令字节顺序 | 地址 PC 和 PC+1 |
| PC 前进四个字节 | 每个字节递增两次,或混用了宽度假设 | F0–F5 轨迹 |
| 总线发生争用 | 两个输出使能共用一个控制位 | 译码后的控制字 |
| 捕获时总线无驱动 | 缺少源使能 | 总线所有者断言 |
| ADD 写入错误寄存器 | RD 字段译码或寄存器编码顺序有误 | IR[3:0] 与装载线 |
| 存储操作破坏了指令存储器 | 地址/立即数或 mem_write 的时序 | MAR 和 ST E2 |
| 分支使用过期的 Z | 标志位未写入,或 JZ 采样了错误状态 | ADD/SUB E2 和 JZ |
| 控制器从不返回取指 | 缺少结束/下一状态条目 | 微步骤转换 |
| 非法操作码执行了操作 | 未安全初始化未使用的控制 ROM | 默认控制字 |
13. 教学范围与模型边界
此设计教授寄存器传送、指令编码、总线所有权、ALU 标志位、存储器时序和控制器状态。
它不模拟:
- 晶体管级 RAM 或逻辑;
- 缓存层次结构或虚拟内存;
- 推测执行或乱序执行;
- 时钟的模拟电气特性或亚稳态;
- 物理传播、布线、功耗或热限制;或
- 生产级安全或异常架构。
它的单条共享总线有意保持易于观察,并非声称现代 CPU 的内部拓扑如此。
功能扩展的建议顺序
- 添加自动化指令测试。 在添加功能前验证每个操作码和非法编码。
- 添加 AND、OR、XOR 和移位。 先定义标志位行为,再添加控制序列。
- 添加使用直接比较的条件分支。 这会揭示基于标志位与比较后分支 ISA 的区别。
- 添加栈指针和调用/返回。 规定栈增长方向、字节顺序和溢出行为。
- 添加中断状态机。 装载向量前保存返回地址和标志位;定义优先级和屏蔽。
- 分离指令与数据存储器。 将新的取指时序与统一存储器设计进行比较。
- 仅在多周期参考实现通过后再流水线化。 然后定义流水级有效性、停顿、前递、冲刷和异常。
在尝试流水线扩展前,可先阅读取指–译码–执行深入解析和指令寄存器/译码指南。
综合验证
可使用顺序指令执行器理解整体流程,使用8 位 ALU 系统独立验证算术通路,并使用 CPU 标志位寄存器测试状态行为。原理图应配套保存书面的体系结构规范和微操作表。
验证目标不只是观察到一次结果 8,而是能够说明每次状态变化的控制依据,在轨迹中复现该变化,并证明其他状态未被意外修改。



