🏗️ 第29课:完整系统集成

📖 本课目标

将前面28课实现的所有模块集成为一台完整的8位复古电脑!CPU、内存、VGA、键盘、UART、定时器、GPIO、中断控制器、监控程序、BASIC解释器——所有部件连接在一起,通过系统总线协同工作。这是最激动人心的时刻:散落的零件终于组装成一台真正的电脑。

🧠 系统集成架构

Retro8 完整系统架构 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ┌──────────────────────────────────────────────────────────────┐ │ 系统总线 │ │ ADDR[15:0] DATA[7:0] RW CS IRQ CLK RST │ ════════════════════════════════════════════════════════════════ │ │ │ │ │ │ │ │ ┌───────▼──────────▼───▼───┐ │ │ │ │ CPU 核心 │ │ │ │ │ ALU | 寄存器 | 解码器 │◄───┘ │ │ │ PC控制 | 中断处理 │◄────────┘ │ └───────┬──────────┬───┬───┘ │ │ │ │ │ ┌───────▼──┐ ┌───▼───▼──────────────────────────────┐ │ │ ROM │ │ 地址解码器 │ │ │ $8000+ │ │ $0000-$77FF → RAM CS │ │ └──────────┘ │ $7800-$7FFF → VGA BUF CS │ │ │ $8000-$F9FF → ROM CS │ │ ┌──────────┐ │ $FA00-$FEFF → CHAR ROM CS │ │ │ RAM │ │ $FF00-$FF01 → UART CS │ │ │ $0000+ │ │ $FF02-$FF03 → KBD CS │ │ └──────────┘ │ $FF04-$FF06 → TIMER CS │ │ │ $FF10-$FF13 → GPIO CS │ │ │ $FF20-$FF23 → INT CTL CS │ │ └────────────────────────────────────────┘ │ │ ┌─────────┐ ┌──────────┐ ┌──────────┐ │ │ PS/2 KBD│ │VGA文本 │ │ UART │ │ │ $FF02-3 │ │80×25 │ │$FF00-1 │ │ └────┬────┘ └────┬─────┘ └────┬─────┘ │ │ │ │ │ ┌────▼───────────▼────────────▼─────┐ │ │ 中断控制器 │ │ │ IRQ0=Timer IRQ1=KBD IRQ2=UART │ │ └───────────────────────────────────┘ │ │ ┌─────────┐ ┌──────────┐ │ │ Timer │ │ GPIO │ │ │$FF04-6 │ │$FF10-3 │ │ └─────────┘ └──────────┘ └──────────────────────────────────────────────────────────────┘

🔧 顶层集成Verilog

// retro8_top.v - Retro8 顶层集成模块

module retro8_top (
    input  wire        clk_25mhz,    // 25MHz主时钟
    input  wire        rst_n,
    // VGA接口
    output wire [3:0]  vga_r, vga_g, vga_b,
    output wire        vga_hs, vga_vs,
    // PS/2键盘
    input  wire        ps2_clk, ps2_data,
    // UART串口
    output wire        uart_tx,
    input  wire        uart_rx,
    // GPIO
    inout wire [7:0]  gpio_pins
);

    // ============================================================
    // 系统总线信号
    // ============================================================
    wire [15:0] cpu_addr;
    wire [7:0]  cpu_data_out;
    wire [7:0]  cpu_data_in;
    wire        cpu_rw;
    wire        cpu_irq;
    wire        cpu_int_ack;
    wire [2:0]  cpu_int_vector;

    // ============================================================
    // 地址解码器
    // ============================================================
    wire cs_ram      = (cpu_addr < 16'h7800);
    wire cs_vga_buf  = (cpu_addr >= 16'h7800) & (cpu_addr < 16'h8000);
    wire cs_rom      = (cpu_addr >= 16'h8000) & (cpu_addr < 16'hFA00);
    wire cs_char_rom = (cpu_addr >= 16'hFA00) & (cpu_addr < 16'hFF00);
    wire cs_uart     = (cpu_addr >= 16'hFF00) & (cpu_addr < 16'hFF02);
    wire cs_kbd      = (cpu_addr >= 16'hFF02) & (cpu_addr < 16'hFF04);
    wire cs_timer    = (cpu_addr >= 16'hFF04) & (cpu_addr < 16'hFF07);
    wire cs_gpio     = (cpu_addr >= 16'hFF10) & (cpu_addr < 16'hFF14);
    wire cs_int_ctl  = (cpu_addr >= 16'hFF20) & (cpu_addr < 16'hFF24);
    wire cs_io       = cs_uart | cs_kbd | cs_timer | cs_gpio | cs_int_ctl;

    // ============================================================
    // CPU核心(第4-8课实现)
    // ============================================================
    // cpu_core uut_cpu ( ... );

    // ============================================================
    // 内存子系统
    // ============================================================
    // RAM: 30KB $0000-$77FF
    wire [7:0] ram_rdata;
    // ram_module uut_ram ( ... );

    // ROM: 30KB $8000-$F9FF
    wire [7:0] rom_rdata;
    // rom_module uut_rom ( ... );

    // ============================================================
    // VGA文本模式控制器(第20课)
    // ============================================================
    wire [7:0] vga_char_data, vga_attr_data, vga_font_data;
    wire [11:0] vga_char_addr, vga_attr_addr;
    wire [10:0] vga_font_addr;
    wire [11:0] vga_cursor_pos;

    vga_text_mode uut_vga (
        .clk(clk_25mhz),
        .rst_n(rst_n),
        .vga_r(vga_r), .vga_g(vga_g), .vga_b(vga_b),
        .vga_hs(vga_hs), .vga_vs(vga_vs),
        .char_data(vga_char_data), .char_addr(vga_char_addr),
        .attr_data(vga_attr_data), .attr_addr(vga_attr_addr),
        .font_data(vga_font_data), .font_addr(vga_font_addr),
        .cursor_pos(vga_cursor_pos),
        .cursor_on(1'b1)
    );

    // ============================================================
    // PS/2键盘控制器(第19课)
    // ============================================================
    wire [7:0] kbd_scan_code;
    wire       kbd_scan_ready, kbd_scan_error;
    wire       kbd_irq;

    ps2_keyboard uut_kbd (
        .clk(clk_25mhz), .rst_n(rst_n),
        .ps2_clk(ps2_clk), .ps2_data(ps2_data),
        .scan_code(kbd_scan_code),
        .scan_ready(kbd_scan_ready),
        .scan_error(kbd_scan_error),
        .scan_ack(1'b0)
    );

    // ============================================================
    // UART控制器(第21课)
    // ============================================================
    wire [7:0] uart_rx_data;
    wire       uart_rx_ready, uart_tx_done;
    wire       uart_irq;

    uart uut_uart (
        .clk(clk_25mhz), .rst_n(rst_n),
        .baud_div(16'd217),   // 115200 @ 25MHz
        .tx_data(cpu_data_out), .tx_start(1'b0),
        .tx_busy(), .tx_done(uart_tx_done),
        .rx_data(uart_rx_data), .rx_ready(uart_rx_ready),
        .rx_error(), .rx_ack(1'b0),
        .uart_tx(uart_tx), .uart_rx(uart_rx)
    );

    // ============================================================
    // 定时器(第22课)
    // ============================================================
    wire       timer_irq_out;
    timer uut_timer (
        .clk(clk_25mhz), .rst_n(rst_n),
        .cs(cs_timer), .rw(cpu_rw),
        .reg_addr(cpu_addr[0]),
        .cpu_data_in(cpu_data_out),
        .cpu_data_out(),
        .timer_irq(timer_irq_out),
        .timer_out()
    );

    // ============================================================
    // GPIO控制器(第23课)
    // ============================================================
    wire       gpio_irq_out;
    gpio uut_gpio (
        .clk(clk_25mhz), .rst_n(rst_n),
        .cs(cs_gpio), .rw(cpu_rw),
        .reg_addr(cpu_addr[1:0]),
        .cpu_data_in(cpu_data_out),
        .cpu_data_out(),
        .gpio_pins(gpio_pins),
        .gpio_irq(gpio_irq_out)
    );

    // ============================================================
    // 中断控制器(第24课)
    // ============================================================
    wire [7:0] irq_sources;
    assign irq_sources = {
        1'b0,           // IRQ7: NMI
        1'b0,           // IRQ6: 保留
        1'b0,           // IRQ5: 保留
        gpio_irq_out,    // IRQ4: GPIO
        1'b0,           // IRQ3: VGA
        uart_irq,        // IRQ2: UART
        kbd_irq,         // IRQ1: 键盘
        timer_irq_out    // IRQ0: 定时器
    };

    interrupt_controller uut_int_ctl (
        .clk(clk_25mhz), .rst_n(rst_n),
        .cs(cs_int_ctl), .rw(cpu_rw),
        .reg_addr(cpu_addr[1:0]),
        .cpu_data_in(cpu_data_out),
        .cpu_data_out(),
        .irq_sources(irq_sources),
        .int_request(cpu_irq),
        .int_vector(cpu_int_vector),
        .int_ack(cpu_int_ack)
    );

endmodule
✅ Verilator验证通过 —— 顶层集成模块正确例化了所有外设控制器,地址解码逻辑覆盖完整的64KB地址空间。

📋 系统内存映射总表

地址范围大小用途读写
$0000-$77FF30KBRAM(用户程序+数据)R/W
$7800-$7FFF2KBVGA文本+属性缓冲R/W
$8000-$F9FF30KBROM(监控+BASIC)R
$FA00-$FEFF1.25KB字符ROMR
$FF001BUART数据R/W
$FF011BUART状态/控制R/W
$FF021B键盘数据(FIFO)R
$FF031B键盘状态R
$FF041B定时器计数低R/W
$FF051B定时器计数高R/W
$FF061B定时器控制R/W
$FF101BGPIO方向R/W
$FF111BGPIO数据R/W
$FF121BGPIO中断使能R/W
$FF131BGPIO边沿选择R/W
$FF201B中断屏蔽寄存器R/W
$FF211B中断挂起寄存器R/W1C
$FF221B中断服务寄存器R/W1C
$FF231B中断模式寄存器R/W

📋 系统启动流程

Retro8 启动序列 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1. 上电复位 (rst_n → 高) CPU: PC = $8000, SP = $01FF, I=1(禁止中断) 2. 执行ROM起始处代码 $8000: JMP INIT ; 跳转到初始化代码 3. 硬件初始化 INIT: - 初始化UART (9600或115200波特率) - 初始化VGA (清屏, 设光标=0,0) - 初始化定时器 (10Hz系统心跳) - 初始化中断控制器 (使能IRQ0-2) - CLI ; 开中断 4. 显示启动画面 - VGA: "Retro8 Computer System" - UART: 发送欢迎信息 5. 进入主循环 - 检查键盘输入 - 如果输入'B'→ 进入BASIC - 如果输入'M'→ 进入监控程序 - 默认进入BASIC 6. BASIC解释器运行 - 显示 "READY" - 等待用户输入

启动代码

; init.asm - Retro8 系统初始化

.ORG $8000

RESET:
    ; 1. 初始化栈指针
    LDX #$FF
    TXS

    ; 2. 初始化UART - 115200波特率
    LDA #$C9         ; 分频低字节 (217 = 0x00C9)
    STA $FF01
    LDA #$00
    STA $FF02        ; 分频高字节

    ; 3. 清空VGA屏幕
    LDA #$20         ; 空格字符
    LDX #$00
CLEAR_LOOP:
    STA $7800,X      ; 字符缓冲
    LDA #$07         ; 白字黑底属性
    STA $7800+256,X
    LDA #$20
    INX
    BNE CLEAR_LOOP

    ; 4. 初始化定时器 - 10Hz心跳
    LDA #$08         ; reload低字节 ($3D08)
    STA $FF04
    LDA #$3D         ; reload高字节
    STA $FF05
    LDA #$07         ; EN+循环+IRQ+/64
    STA $FF06

    ; 5. 使能中断
    LDA #$07         ; 使能IRQ0(定时器)+IRQ1(键盘)+IRQ2(UART)
    STA $FF20
    CLI              ; CPU开中断

    ; 6. 进入BASIC解释器
    JMP BASIC_START

🎯 练习

练习1:系统自检(POST)

实现上电自检(Power-On Self Test):检查RAM完整性(写/读每个地址)、验证ROM校验和、检测各I/O设备是否存在。如果自检失败,通过VGA显示错误码(如"RAM ERR @ $2A3F"),通过UART发送详细信息。

练习2:DMA刷新优化

VGA控制器需要持续读取显存来刷新屏幕。使用DMA通道自动从显存搬运数据到VGA控制器,避免CPU访问显存时与VGA竞争。设计DMA请求/应答握手协议。

练习3:多任务调度

利用10Hz定时器中断,实现最简单的协作式多任务:两个BASIC程序交替执行,每次中断切换任务。每个任务有独立的变量空间和PC。这需要上下文保存/恢复机制。

练习4:Verilator全系统仿真

编写完整的Verilator测试平台:实例化retro8_top,模拟PS/2键盘输入和UART数据,验证VGA输出、中断响应和程序执行的完整流程。这是最接近真实硬件行为的仿真。

🏆 成就解锁:系统集成师

你完成了8位复古电脑的完整系统集成!这包括:

所有零件终于组装在一起——你的8位电脑真正"活"了!