理解8位复古电脑的整体架构,了解我们将从零搭建的每个模块,建立对整个项目的全局认识。这不仅是理论——我们将亲手用Verilog实现每一个部件。
在2026年,造一台8位电脑似乎毫无意义。但恰恰相反——这是理解计算机本质的最佳途径。现代CPU有数十亿晶体管,复杂到任何人都无法完全理解。而8位电脑的每一个晶体管、每一条数据通路,都可以被你完全掌握。
这台电脑的灵感来自那些改变了世界的经典机器:
| 机器 | 年份 | CPU | 内存 | 特点 |
|---|---|---|---|---|
| Apple II | 1977 | 6502 | 4-64KB | 个人电脑先驱 |
| Commodore 64 | 1982 | 6510 | 64KB | 史上最畅销 |
| ZX Spectrum | 1982 | Z80 | 16-48KB | 8位色彩 |
| NES | 1983 | 6502 | 2KB+8KB | 游戏革命 |
| MSX | 1983 | Z80 | 8-64KB | 统一标准 |
我们将构建的8位复古电脑采用经典的冯·诺依曼架构,包含以下核心子系统:
在开始实现之前,我们需要明确定义这台电脑的技术规格:
| 参数 | 规格 | 说明 |
|---|---|---|
| 数据位宽 | 8位 | ALU、寄存器、数据总线均为8位 |
| 地址位宽 | 16位 | 可寻址64KB内存空间 |
| 通用寄存器 | 8个 (R0-R7) | R7用作堆栈指针 |
| 时钟频率 | 仿真10MHz | Verilator仿真不依赖真实时钟 |
| 指令集 | 56条指令 | 包含算术、逻辑、移位、分支、I/O |
| 寻址模式 | 8种 | 立即、直接、间接、变址等 |
| 内存映射I/O | 顶层256字节 | $FF00-$FFFF映射到I/O设备 |
| 中断 | 3个中断源 | 定时器、键盘、UART |
| 显示 | 80×25文本 | VGA文本模式,16色 |
64KB地址空间的分配是我们整个系统设计的基石:
我们将使用Verilator作为仿真和验证工具。Verilator是一个高性能的Verilog仿真器,它将Verilog代码编译成C++,然后编译运行,速度远超传统事件驱动仿真器。
# Ubuntu/Debian
sudo apt install verilator
# macOS
brew install verilator
# 从源码编译(获取最新版)
git clone https://github.com/verilator/verilator
cd verilator
autoconf
./configure
make -j$(nproc)
sudo make install
retro-computer/
├── verilog/ # Verilog源文件
│ ├── cpu/ # CPU核心模块
│ │ ├── alu.v # 算术逻辑单元
│ │ ├── regfile.v # 寄存器文件
│ │ ├── decoder.v # 指令解码器
│ │ ├── pc_ctrl.v # 程序计数器
│ │ └── cpu.v # CPU顶层
│ ├── memory/ # 存储子系统
│ │ ├── rom.v
│ │ ├── ram.v
│ │ └── mmio.v
│ ├── io/ # I/O外设
│ │ ├── uart.v
│ │ ├── keyboard.v
│ │ ├── vga.v
│ │ ├── timer.v
│ │ ├── gpio.v
│ │ └── interrupt.v
│ └── top.v # 系统顶层
├── sim/ # 仿真测试
│ ├── tb_alu.cpp # C++测试台
│ └── ...
├── software/ # 软件工具
│ ├── assembler/ # 汇编器
│ └── basic/ # BASIC解释器
└── docs/ # 文档
└── isa.md # 指令集手册
让我们从定义系统级常量开始。这个模块将被其他所有模块引用:
// ========================================
// retro_defs.v - 系统常量定义
// 8位复古电脑 - 全局参数
// ========================================
package retro_defs;
// 数据和地址位宽
localparam DATA_WIDTH = 8; // 数据总线宽度
localparam ADDR_WIDTH = 16; // 地址总线宽度
// 内存大小
localparam RAM_SIZE = 30720; // 30KB RAM
localparam ROM_SIZE = 30720; // 30KB ROM
localparam VGA_BUF_SIZE = 2048; // 2KB VGA缓冲
// I/O地址映射
localparam IO_BASE = 16'hFF00;
localparam UART_DATA = 16'hFF00;
localparam UART_STATUS = 16'hFF01;
localparam KBD_DATA = 16'hFF02;
localparam KBD_STATUS = 16'hFF03;
localparam TIMER_LOW = 16'hFF04;
localparam TIMER_HIGH = 16'hFF05;
localparam TIMER_CTRL = 16'hFF06;
localparam GPIO_DATA = 16'hFF07;
localparam GPIO_DIR = 16'hFF08;
localparam IRQ_PENDING = 16'hFF09;
localparam IRQ_MASK = 16'hFF0A;
localparam VGA_CURSOR = 16'hFF0B;
localparam VGA_ATTR = 16'hFF0C;
localparam SYS_CTRL = 16'hFF0D;
// ALU操作码
localparam ALU_ADD = 4'd0;
localparam ALU_SUB = 4'd1;
localparam ALU_AND = 4'd2;
localparam ALU_OR = 4'd3;
localparam ALU_XOR = 4'd4;
localparam ALU_NOT = 4'd5;
localparam ALU_SHL = 4'd6;
localparam ALU_SHR = 4'd7;
localparam ALU_INC = 4'd8;
localparam ALU_DEC = 4'd9;
localparam ALU_CMP = 4'd10;
localparam ALU_ROL = 4'd11;
localparam ALU_ROR = 4'd12;
// 指令格式 (2字节最大)
// [操作码7:2][寻址模式1:0] + [操作数7:0]
localparam INSN_WIDTH = 8;
// CPU状态标志位
localparam FLAG_C = 0; // 进位
localparam FLAG_Z = 1; // 零
localparam FLAG_N = 2; // 负
localparam FLAG_V = 3; // 溢出
localparam FLAG_I = 4; // 中断屏蔽
localparam FLAG_B = 5; // BCD模式
endpackage
我们的CPU采用多阶段执行周期。每条指令的执行分为以下阶段:
在Verilator中,我们使用C++ testbench来驱动仿真。基本流程如下:
// tb_template.cpp - Verilator测试台模板
#include "Vtop.h"
#include "verilated.h"
#include <cstdio>
int main(int argc, char** argv) {
Verilated::commandArgs(argc, argv);
// 实例化顶层模块
Vtop* top = new Vtop;
// 初始化复位
top->rst_n = 0;
top->clk = 0;
// 复位序列
for (int i = 0; i < 10; i++) {
top->clk = !top->clk;
top->eval();
}
top->rst_n = 1;
// 运行仿真
for (int cycle = 0; cycle < 1000; cycle++) {
top->clk = !top->clk;
top->eval();
if (top->clk) {
// 上升沿采样输出
printf("Cycle %d: PC=%04X D=%02X\n",
cycle/2, top->pc_out, top->data_out);
}
}
// 清理
top->final();
delete top;
return 0;
}
30课内容分为5个阶段,每阶段6课。以下是详细的课程安排:
| 阶段 | 课程 | 主题 | 核心产出 |
|---|---|---|---|
| CPU核心 | 01 | 8位电脑概述 | 系统设计文档 |
| 02 | ALU设计 | 8位ALU模块 | |
| 03 | 寄存器文件 | 8×8寄存器组 | |
| 04 | 指令解码器 | 操作码解码逻辑 | |
| 05 | 程序计数器 | 16位PC与分支控制 | |
| 06 | 完整CPU数据通路 | 可执行指令的CPU | |
| 指令集 | 07 | 指令集设计 | ISA文档 |
| 08 | 寻址模式 | 8种寻址实现 | |
| 09 | 算术指令 | ADD/SUB/MUL | |
| 10 | 逻辑与移位 | AND/OR/SHL/SHR | |
| 11 | 分支与跳转 | JMP/CALL/RET | |
| 12 | 堆栈操作 | PUSH/POP/栈帧 | |
| 内存系统 | 13 | ROM设计 | 程序存储ROM |
| 14 | RAM控制器 | 读写控制器 | |
| 15 | 内存映射I/O | MMIO桥接 | |
| 16 | VGA文本缓冲 | 显示缓冲区 | |
| 17 | 键盘缓冲 | 按键队列 | |
| 18 | DMA基础 | 直接内存访问 | |
| I/O外设 | 19 | PS/2键盘接口 | 键盘驱动 |
| 20 | VGA文本模式 | 视频输出 | |
| 21 | 串口UART | 串行通信 | |
| 22 | 定时器 | 可编程定时 | |
| 23 | GPIO | 通用IO | |
| 24 | 中断控制器 | 中断管理 | |
| 软件集成 | 25 | 汇编器 | 汇编工具 |
| 26 | 简单监控程序 | 命令行Shell | |
| 27 | BASIC解释器基础 | 词法分析+执行 | |
| 28 | BASIC运算与IO | 表达式+打印 | |
| 29 | 完整系统集成 | 全系统联调 | |
| 30 | 毕业项目 | 完整8位电脑 |
如果你对Verilog不太熟悉,这里快速回顾关键概念:
1. 模块(Module)——Verilog的基本设计单元
module my_module (
input wire clk, // 时钟
input wire rst_n, // 低有效复位
input wire [7:0] data_in, // 8位输入
output reg [7:0] data_out // 8位输出寄存器
);
// 模块实现
endmodule
2. 组合逻辑 vs 时序逻辑
// 组合逻辑 - 用assign或always @(*)
assign result = a & b; // 与门
// 时序逻辑 - 用always @(posedge clk)
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
counter <= 8'd0; // 复位
else
counter <= counter + 1; // 递增
end
3. 阻塞 vs 非阻塞赋值
// = 阻塞赋值 → 用于组合逻辑
// <= 非阻塞赋值 → 用于时序逻辑
always @(posedge clk) begin
a <= b; // 正确:时序逻辑用<=
c <= a; // c得到旧a的值
end
rst_nalways @(posedge clk)<==DATA_WIDTH根据上面的内存映射规划,回答以下问题:
$7800 属于哪个区域?如果向这个地址写入字符 'A'(ASCII 0x41),会发生什么?编写一个Verilog模块 retro_top,包含以下端口:
先只写模块声明和端口定义,不需要实现。
假设我们要把RAM扩展到48KB,同时保持VGA缓冲和ROM不变。计算新的地址分配方案,并说明VGA缓冲需要移到哪里。
达成条件:完成以下所有项:
奖励:你已拥有全局视野,接下来30课的每一课,你都知道自己在造什么部件、它如何融入整体。这是大多数人缺失的理解层次。