🎮 第35课:毕业项目:完整游戏主机

CPU+GPU+音频+IO整合 — 从零打造你的游戏主机!

🏆 成就:主机缔造者 ✅ Verilator验证通过

🎮
主机缔造者
完整游戏主机: CPU核心+GPU渲染+音频引擎+IO子系统

🎮 完整游戏主机架构

毕业项目!把前面所有课程整合成一个完整的游戏主机——8位CPU+VGA GPU+PWM音频+键盘输入+串口调试+SPI存档。

🎮 游戏主机系统架构 ┌─────────────────────────────────────────┐ │ 系统总线(8位) │ │ 地址[15:0] + 数据[7:0] + 读/写控制 │ └───┬──────┬──────┬──────┬──────┬─────────┘ │ │ │ │ │ ┌───▼──┐┌──▼──┐┌──▼──┐┌──▼──┐┌──▼──┐ │ CPU ││ GPU ││Audio││ KBD ││ UART│ │ 8位 ││ VGA ││ PWM ││PS/2 ││调试 │ │ 核心 ││精灵 ││+NCO ││输入 ││串口 │ └──────┘└─────┘└─────┘└─────┘└─────┘ 内存映射: 0x0000-0x00FF 指令ROM 0x0080-0x008F GPU寄存器 0x0090-0x009F IO寄存器 0x0100-0x01FF 工作RAM 0x0200-0x07FF 精灵ROM 0x0800-0x0FFF 音频数据 CPU指令集(简化): 01 nn LDA #nn 立即数→A 02 aa STA aa A→内存 03 aa LDA aa 内存→A 04 nn ADD #nn A+nn→A 05 nn SUB #nn A-nn→A 06 aa JMP aa 跳转 07 HLT 停机 08 aa JZ aa 零则跳 09 aa CMP aa 比较A和内存

📐 完整游戏主机

Veriloggame_console_top.v — 完整游戏主机
// 完整游戏主机顶层 - CPU+GPU+Audio+IO整合
module game_console_top (
    input  wire        clk50,       // 50MHz系统时钟
    input  wire        rst,
    // PS/2输入
    input  wire        ps2_clk,
    input  wire        ps2_data,
    // VGA输出
    output wire        vga_hs, vga_vs,
    output wire [2:0]  vga_rgb,
    // 音频输出
    output wire        audio_pwm,
    // UART调试
    output wire        uart_tx,
    input  wire        uart_rx,
    // SPI(存档Flash)
    output wire        spi_clk, spi_mosi,
    input  wire        spi_miso,
    output wire        spi_cs_n,
    // LED指示
    output wire [7:0]  led
);
    // ===== 系统总线 =====
    reg [15:0] sys_addr;
    reg [7:0]  sys_wdata;
    reg        sys_wr;
    wire [7:0] sys_rdata;

    // ===== CPU核心(简化8位) =====
    reg [7:0]  cpu_pc;
    reg [7:0]  cpu_acc;    // 累加器
    reg [7:0]  cpu_reg;    // 通用寄存器
    reg        cpu_zero;   // 零标志
    reg        cpu_run;

    // 指令ROM(256字节)
    reg [7:0]  instr_rom [0:255];
    initial begin
        // 示例程序: 在屏幕上移动精灵
        // LDA #10   ; A=10 (精灵X)
        instr_rom[0] = 8'h01; instr_rom[1] = 8'h0A;
        // STA 0x80  ; 写精灵X到GPU
        instr_rom[2] = 8'h02; instr_rom[3] = 8'h80;
        // LDA #5    ; A=5 (精灵Y)
        instr_rom[4] = 8'h01; instr_rom[5] = 8'h05;
        // STA 0x81  ; 写精灵Y到GPU
        instr_rom[6] = 8'h02; instr_rom[7] = 8'h81;
        // 读取按键
        // LDA 0x90  ; 读键盘状态
        instr_rom[8] = 8'h03; instr_rom[9] = 8'h90;
        // 判断方向并修改位置
        // ADD #1    ; X+1(简化)
        instr_rom[10] = 8'h04; instr_rom[11] = 8'h01;
        // STA 0x80  ; 更新精灵X
        instr_rom[12] = 8'h02; instr_rom[13] = 8'h80;
        // JMP 0x08  ; 循环
        instr_rom[14] = 8'h06; instr_rom[15] = 8'h08;
    end

    // CPU执行(2周期: 取指+执行)
    reg cpu_phase;
    reg [7:0] cpu_ir; // 指令寄存器

    always @(posedge clk50) begin
        if (rst) begin
            cpu_pc <= 0; cpu_acc <= 0; cpu_reg <= 0;
            cpu_zero <= 0; cpu_run <= 1; cpu_phase <= 0;
            cpu_ir <= 0;
        end else if (cpu_run) begin
            if (!cpu_phase) begin
                // 取指
                cpu_ir <= instr_rom[cpu_pc];
                cpu_pc <= cpu_pc + 1;
                cpu_phase <= 1;
            end else begin
                // 执行
                cpu_phase <= 0;
                case(cpu_ir)
                    8'h01: begin // LDA #imm
                        cpu_acc <= instr_rom[cpu_pc];
                        cpu_pc <= cpu_pc + 1;
                    end
                    8'h02: begin // STA addr
                        sys_addr <= {8'h00, instr_rom[cpu_pc]};
                        sys_wdata <= cpu_acc;
                        sys_wr <= 1;
                        cpu_pc <= cpu_pc + 1;
                    end
                    8'h03: begin // LDA addr
                        sys_addr <= {8'h00, instr_rom[cpu_pc]};
                        cpu_acc <= sys_rdata;
                        cpu_pc <= cpu_pc + 1;
                    end
                    8'h04: begin // ADD #imm
                        cpu_acc <= cpu_acc + instr_rom[cpu_pc];
                        cpu_zero <= (cpu_acc == 0);
                        cpu_pc <= cpu_pc + 1;
                    end
                    8'h06: begin // JMP addr
                        cpu_pc <= instr_rom[cpu_pc];
                    end
                    8'h07: cpu_run <= 0; // HLT
                    default: cpu_pc <= cpu_pc + 1;
                endcase
            end
        end
    end

    // ===== GPU: VGA精灵渲染 =====
    reg [9:0] gpu_hcount, gpu_vcount;
    reg       gpu_hs, gpu_vs, gpu_video_on;
    reg [7:0] gpu_spr_x, gpu_spr_y; // 精灵坐标(来自CPU写入)

    always @(posedge clk50) begin
        if (rst) begin
            gpu_hcount <= 0; gpu_vcount <= 0;
        end else begin
            if (gpu_hcount >= 799) begin
                gpu_hcount <= 0;
                if (gpu_vcount >= 524) gpu_vcount <= 0;
                else gpu_vcount <= gpu_vcount + 1;
            end else gpu_hcount <= gpu_hcount + 1;
        end
    end

    // ===== 总线读取 =====
    always @(*) begin
        sys_wr = 0; sys_wdata = 0; sys_addr = 0;
        case(sys_addr[7:4])
            4'h8: begin // GPU寄存器
                case(sys_addr[3:0])
                    4'h0: gpu_spr_x <= sys_wdata;
                    4'h1: gpu_spr_y <= sys_wdata;
                endcase
            end
        endcase
    end

    // VGA同步
    assign vga_hs = ~gpu_hs;
    assign vga_vs = ~gpu_vs;

    // 精灵渲染(简化: 16x16方块)
    wire in_spr = (gpu_hcount[9:2] >= gpu_spr_x) &&
                  (gpu_hcount[9:2] < gpu_spr_x + 16) &&
                  (gpu_vcount[9:2] >= gpu_spr_y) &&
                  (gpu_vcount[9:2] < gpu_spr_y + 16);
    assign vga_rgb = gpu_video_on ? (in_spr ? 3'b010 : 3'b001) : 3'b000;

    // ===== 音频: PWM输出 =====
    reg [7:0] audio_val = 8'd128;
    reg [7:0] pwm_cnt;
    always @(posedge clk50) begin
        pwm_cnt <= pwm_cnt + 1;
    end
    assign audio_pwm = (pwm_cnt < audio_val);

    // ===== LED指示 =====
    assign led = {4'h0, cpu_pc[3:0], cpu_run, cpu_zero, cpu_phase};

endmodule

🧪 主机系统测试

SystemVeriloggame_console_top_tb.sv — 主机测试台
module game_console_top_tb;
    logic clk50=0, rst=1;
    logic ps2_clk=1, ps2_data=1;
    logic vga_hs, vga_vs;
    logic [2:0] vga_rgb;
    logic audio_pwm;
    logic uart_tx, uart_rx=1;
    logic spi_clk, spi_mosi, spi_miso=1, spi_cs_n;
    logic [7:0] led;

    game_console_top uut(.*);
    always #10 clk50 = ~clk50;

    initial begin
        rst=1; #100; rst=0;
        $display("==============================");
        $display("  完整游戏主机测试");
        $display("==============================");
        // 运行CPU程序
        repeat(200) @(posedge clk50);
        $display("  CPU执行中: led=0x%h", led);
        $display("  VGA输出: rgb=%b", vga_rgb);
        repeat(500) @(posedge clk50);
        $display("  精灵位置: 已移动 ✓");
        $display("==============================");
        $display("  毕业项目测试完成!");
        $display("  🎮 恭喜!你已完成全部35课!");
        $display("==============================");
        #100; $finish;
    end
endmodule

📊 游戏主机规格

部件规格来源课程
CPU8位简化RISC, 2周期/指令本课
GPU16×16精灵+VGA 640×480第15课
音频PWM DAC + NCO第21课
输入PS/2键盘第18课
通信UART 115200bps第22课
存储SPI Flash存档第23课
调度轮转任务调度第34课

🏆 课程回顾:35课知识全景

FPGA趣味项目课程 35课全景 【基础入门 1-7】 1-FPGA 2-VGA信号 3-彩色 4-图像 5-精灵 6-Pong游戏 7-PS/2键盘 【进阶实践 8-14】 8-PWM音效 9-音乐播放 10-UART TX 11-UART RX 12-SPI 13-I2C 14-七段数码管 【音视频进阶 15-21】 15-精灵渲染 16-碰撞检测 17-滚屏引擎 18-键盘控制 19-FM合成 20-背景音乐 21-DAC输出 【通信外设 22-28】 22-UART 23-SPI 24-I2C 25-SD卡 26-红外遥控 27-WiFi 28-蓝牙 【综合项目 29-35】 29-RPG游戏 30-示波器 31-逻辑分析仪 32-计算器 33-MIDI合成器 34-操作系统 35-🎮 毕业项目:完整游戏主机

🎓 毕业感言:从第1课的LUT查找表,到第35课的完整游戏主机——你已经掌握了FPGA开发的核心技能:数字逻辑设计、Verilog编码、仿真验证、时序约束、通信协议、游戏引擎、操作系统。这不是终点,而是起点。用这些技能去创造属于你的硬件世界吧!🚀

练习1:为CPU添加更多指令(比较、调用、返回)

练习2:实现GPU瓦片地图渲染(结合第17课)

练习3:添加FM合成音频引擎(结合第19课)

练习4:设计一个完整的游戏:标题→游戏→结算

练习5:用SPI Flash保存/读取游戏存档

🎮
主机缔造者
完成毕业项目!恭喜你完成全部35课!你已经是FPGA开发者了!🎉