🖥️ 第01课:8位复古电脑概述

📖 本课目标

理解8位复古电脑的整体架构,了解我们将从零搭建的每个模块,建立对整个项目的全局认识。这不仅是理论——我们将亲手用Verilog实现每一个部件。

🌍 为什么要造一台8位电脑?

在2026年,造一台8位电脑似乎毫无意义。但恰恰相反——这是理解计算机本质的最佳途径。现代CPU有数十亿晶体管,复杂到任何人都无法完全理解。而8位电脑的每一个晶体管、每一条数据通路,都可以被你完全掌握。

这台电脑的灵感来自那些改变了世界的经典机器:

机器年份CPU内存特点
Apple II197765024-64KB个人电脑先驱
Commodore 641982651064KB史上最畅销
ZX Spectrum1982Z8016-48KB8位色彩
NES198365022KB+8KB游戏革命
MSX1983Z808-64KB统一标准
💡 关键洞察:这些机器的CPU加起来不到1万个晶体管,而一个现代手机CPU有数十亿。但它们运行的程序——BASIC解释器、游戏、文字处理——和今天软件的本质是一样的。理解8位,就理解了计算的根基。

🏗️ 整体架构蓝图

我们将构建的8位复古电脑采用经典的冯·诺依曼架构,包含以下核心子系统:

┌─────────────────────────────────────────────────────────────┐ │ 8位复古电脑系统架构 │ │ │ │ ┌─────────┐ ┌──────────┐ ┌───────────┐ ┌───────────┐ │ │ │ ROM │ │ RAM │ │ I/O端口 │ │ VGA缓冲 │ │ │ │ (程序) │ │ (数据) │ │ (外设) │ │ (显示) │ │ │ └────┬─────┘ └────┬─────┘ └─────┬─────┘ └─────┬─────┘ │ │ │ │ │ │ │ │ ═════╪══════════════╪══════════════╪══════════════╪═════ │ │ │ 系统总线 (地址/数据/控制) │ │ │ ═════╪══════════════╪══════════════╪══════════════╪═════ │ │ │ │ │ │ │ │ ┌────┴──────────────┴──────────────┴──────────────┴─────┐ │ │ │ CPU 核心 │ │ │ │ ┌────────┐ ┌──────┐ ┌────────┐ ┌────────┐ │ │ │ │ │ ALU │ │寄存器│ │解码器 │ │ PC控制 │ │ │ │ │ │算术逻辑│ │ 文件 │ │ │ │ │ │ │ │ │ └────────┘ └──────┘ └────────┘ └────────┘ │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────┐ ┌──────────┐ ┌───────────┐ │ │ │ PS/2 │ │ VGA │ │ UART │ │ │ │ 键盘 │ │ 显示 │ │ 串口 │ │ │ └─────────┘ └──────────┘ └───────────┘ │ │ │ │ ┌─────────┐ ┌──────────┐ ┌───────────┐ │ │ │ 定时器 │ │ GPIO │ │ 中断控制器│ │ │ └─────────┘ └──────────┘ └───────────┘ │ └─────────────────────────────────────────────────────────────┘

📐 技术规格定义

在开始实现之前,我们需要明确定义这台电脑的技术规格:

🔧 Retro8 技术规格

参数规格说明
数据位宽8位ALU、寄存器、数据总线均为8位
地址位宽16位可寻址64KB内存空间
通用寄存器8个 (R0-R7)R7用作堆栈指针
时钟频率仿真10MHzVerilator仿真不依赖真实时钟
指令集56条指令包含算术、逻辑、移位、分支、I/O
寻址模式8种立即、直接、间接、变址等
内存映射I/O顶层256字节$FF00-$FFFF映射到I/O设备
中断3个中断源定时器、键盘、UART
显示80×25文本VGA文本模式,16色

🗂️ 内存映射规划

64KB地址空间的分配是我们整个系统设计的基石:

地址空间分配 ($0000 - $FFFF) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ $0000 ┌─────────────────────┐ │ │ │ RAM (30KB) │ 用户程序和数据区 │ $0000 - $77FF │ │ │ $7800 ├─────────────────────┤ │ VGA文本缓冲 │ 80×25 = 2000字符 │ $7800 - $7FFF │ + 属性缓冲 │ │ $8000 ├─────────────────────┤ │ │ │ ROM (30KB) │ 固化程序区 │ $8000 - $F9FF │ 监控程序、BASIC │ │ $FA00 ├─────────────────────┤ │ 字符ROM │ 字符点阵数据 │ $FA00 - $FEFF │ 256字符 × 8字节 │ │ $FF00 ├─────────────────────┤ │ I/O映射区 │ │ $FF00 - $FFFF │ │ ┌───────────────┐ │ │ │$FF00 UART数据 │ │ │ │$FF01 UART状态 │ │ │ │$FF02 键盘数据 │ │ │ │$FF03 键盘状态 │ │ │ │$FF04 定时器低 │ │ │ │$FF05 定时器高 │ │ │ │$FF06 定时器控 │ │ │ │$FF07 GPIO数据 │ │ │ │$FF08 GPIO方向 │ │ │ │$FF09 中断挂起 │ │ │ │$FF0A 中断屏蔽 │ │ │ │$FF0B VGA游标 │ │ │ │$FF0C VGA属性 │ │ │ │$FF0D 系统控制 │ │ │ │$FF0E-$FF0F保留│ │ │ └───────────────┘ │ $FFFF └─────────────────────┘

🔧 开发环境搭建

我们将使用Verilator作为仿真和验证工具。Verilator是一个高性能的Verilog仿真器,它将Verilog代码编译成C++,然后编译运行,速度远超传统事件驱动仿真器。

安装Verilator

# 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         # 指令集手册

⚡ 第一个Verilog模块——系统常量定义

让我们从定义系统级常量开始。这个模块将被其他所有模块引用:

// ========================================
// 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
⚠️ 注意:Verilator不支持SystemVerilog的package关键字(取决于版本)。如果遇到编译错误,我们可以改用`define宏定义。本课程中我们会确保所有代码兼容Verilator 5.x。

🔄 CPU执行周期

我们的CPU采用多阶段执行周期。每条指令的执行分为以下阶段:

指令执行流程 (多周期设计) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ 取指 │────▶│ 译码 │────▶│ 执行 │────▶│ 写回 │ │ FETCH │ │ DECODE │ │ EXECUTE │ │ WRITEBACK│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ │ 从ROM/RAM │ 解析操作码 │ ALU运算 │ 结果写入 │ 读取指令字节 │ 确定寻址模式 │ 地址计算 │ 目标寄存器 │ PC←PC+1 │ 读取操作数 │ 内存访问 │ 更新标志位 │ │ │ │ PC更新 各阶段耗时: · FETCH: 1个时钟周期 · DECODE: 1个时钟周期 · EXECUTE: 1-3个时钟周期(取决于操作) · WRITEBACK: 1个时钟周期

🧪 仿真验证方法

在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核心018位电脑概述系统设计文档
02ALU设计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/栈帧
内存系统13ROM设计程序存储ROM
14RAM控制器读写控制器
15内存映射I/OMMIO桥接
16VGA文本缓冲显示缓冲区
17键盘缓冲按键队列
18DMA基础直接内存访问
I/O外设19PS/2键盘接口键盘驱动
20VGA文本模式视频输出
21串口UART串行通信
22定时器可编程定时
23GPIO通用IO
24中断控制器中断管理
软件集成25汇编器汇编工具
26简单监控程序命令行Shell
27BASIC解释器基础词法分析+执行
28BASIC运算与IO表达式+打印
29完整系统集成全系统联调
30毕业项目完整8位电脑

🛠️ Verilog基础回顾

如果你对Verilog不太熟悉,这里快速回顾关键概念:

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
💡 编码风格约定:本课程遵循以下规则:

📝 练习

练习1:理解内存映射

根据上面的内存映射规划,回答以下问题:

  1. 地址 $7800 属于哪个区域?如果向这个地址写入字符 'A'(ASCII 0x41),会发生什么?
  2. 如果我们需要增加一个SPI控制器,应该映射到哪个地址?为什么?
  3. 为什么VGA文本缓冲放在RAM区域的顶部而不是I/O区域?

练习2:设计一个简单的顶层模块框架

编写一个Verilog模块 retro_top,包含以下端口:

先只写模块声明和端口定义,不需要实现。

练习3:计算寻址范围

假设我们要把RAM扩展到48KB,同时保持VGA缓冲和ROM不变。计算新的地址分配方案,并说明VGA缓冲需要移到哪里。

🏆 成就解锁

🏅 架构规划师

达成条件:完成以下所有项:

奖励:你已拥有全局视野,接下来30课的每一课,你都知道自己在造什么部件、它如何融入整体。这是大多数人缺失的理解层次。

📚 拓展阅读