FPGA实现俄罗斯方块:Zynq-7000架构与Verilog设计详解

1. 项目背景与核心目标

俄罗斯方块作为一款诞生于1984年的经典游戏,其简单的规则和极高的可玩性使其成为电子游戏史上的里程碑。在FPGA上实现俄罗斯方块系统,不仅是对经典游戏的致敬,更是验证数字逻辑设计能力的绝佳项目。这个设计案例最初由Xilinx大学计划推广,现已成为FPGA入门到进阶的经典实验。

选择Zynq-7000系列作为硬件平台主要基于三点考量:首先,其PL(可编程逻辑)部分提供充足的逻辑资源实现游戏状态机;其次,PS(处理系统)端可扩展更复杂的功能(如高分记录);最后,ZedBoard开发板自带VGA接口和按键,可快速搭建完整系统。实测表明,在Artix-7架构的XC7Z020芯片上,该设计仅消耗约15%的LUT资源。

2. 系统架构设计解析

2.1 模块化设计思路

整个系统采用经典的"输入-处理-输出"架构,划分为四个关键模块:

  1. 按键输入处理模块:负责消抖和边沿检测
  2. 控制模块:10状态FSM实现游戏逻辑
  3. 数据路径模块:维护游戏矩阵数据
  4. VGA显示模块:实现640x480@60Hz输出

这种分层设计使得各模块可独立验证。例如在Vivado中,可先单独仿真VGA时序模块,再逐步集成其他组件。

2.2 状态机设计精要

控制模块采用Moore型状态机,定义10个核心状态:

localparam S_idle = 4'd0, // 初始状态 S_new = 4'd1, // 生成新方块 S_hold = 4'd2, // 等待输入 S_move = 4'd3, // 移动判断 S_shift = 4'd4, // 更新坐标 S_down = 4'd5, // 下落判断 S_remove_1 = 4'd6, // 固化方块 S_remove_2 = 4'd7, // 行消除 S_isdie = 4'd8, // 死亡判定 S_stop = 4'd9; // 游戏结束

状态转移的关键在于正确处理三种触发条件:定时器超时(自动下落)、按键输入和碰撞检测。实测中发现,如果不在S_hold状态对按键进行边沿检测,会导致长按触发多次动作。

3. 关键技术实现细节

3.1 消抖电路的优化实现

传统消抖方案采用固定延时,但会降低响应速度。本设计创新性地结合了两种技术:

  1. 移位寄存器滤波:4级D触发器链实现信号延迟
always @(posedge clk) shift_up <= {shift_up[2:0], UP_KEY};
  1. 低频采样:50Hz时钟分频(100MHz主频下计数至50)
always @(posedge clk) begin if(clk_cnt <= 8'd49) clk_cnt <= clk_cnt + 1; else begin clk_cnt <= 0; clk_div <= ~clk_div; // 生成约50Hz采样时钟 end end

这种混合方案在实验室环境下可实现<5ms的响应延迟,同时完全消除机械抖动。

3.2 游戏矩阵的存储方案

背景矩阵采用二维寄存器组表示:

reg [9:0] R [23:0]; // 24行x10列

但实际实现时发现,Verilog不支持直接操作二维数组位域。最终解决方案是:

  1. 使用一维向量模拟二维矩阵
  2. 通过宏定义行列访问函数
`define GET_BIT(row,col) R[(row)*10+(col)]

对于活动方块,则采用坐标+类型的方式存储:

output reg [4:0] n; // 行坐标(0-23) output reg [3:0] m; // 列坐标(0-9) output reg [6:0] BLOCK; // 方块类型编码

3.3 VGA时序精准控制

VGA显示采用典型的三段式状态机:

  1. 行同步生成
if(h_count < 96) hsync_r <= 0; // 同步脉冲 else if(h_count < 144) hsync_r <= 1; // 后沿 else if(h_count < 784) begin // 显示区域 hsync_r <= 1; // 生成RGB数据 end else hsync_r <= 1; // 前沿
  1. 场同步生成:原理类似,基于行计数
  2. RGB数据生成:根据游戏矩阵实时计算像素值

实测中发现,必须严格遵循VESA标准时序参数(见下表),否则某些显示器会出现图像偏移:

参数行周期场周期同步脉冲后沿显示区前沿
像素数(640x480)800525964864016
时间(μs)31.7716.68ms3.811.9125.420.64

4. 开发中的典型问题与解决方案

4.1 方块旋转的碰撞检测

初始实现时,旋转判断仅考虑边界条件,导致可能嵌入已有方块。改进后的检测流程:

  1. 预计算旋转后的新坐标
  2. 检查是否越界(m<0或m>9,n>23)
  3. 检查新位置是否与背景矩阵冲突
if (m<=8) begin case(BLOCK) B_1: if(!(`GET_BIT(n,m+1)|`GET_BIT(n+1,m+1))) MOVE_ABLE=1; // 其他方块类型判断... endcase end

4.2 行消除的流水线优化

原始设计在消除行时采用顺序移位,导致最大延迟达24个周期。优化方案:

  1. 并行检测所有满行
  2. 使用优先级编码器确定最低满行
  3. 一次性计算新行位置
always @(*) begin full_rows = 0; for(i=0; i<24; i=i+1) if(&R[i]) full_rows[i] = 1; end

4.3 时钟域交叉处理

由于按键采样(50Hz)、游戏逻辑(10Hz)和VGA(25MHz)处于不同时钟域,必须小心处理信号同步。我们采用:

  1. 两级触发器同步链
  2. 脉冲展宽电路
always @(posedge fast_clk) begin sync_pulse <= {sync_pulse[0], slow_pulse}; if(sync_pulse == 2'b01) pulse_out <= 1; else pulse_out <= 0; end

5. 性能优化与扩展方向

5.1 资源占用分析

在XC7Z020上的实现结果:

  • LUT: 2,134 (15%)
  • FF: 1,897 (7%)
  • BRAM: 3 (5%)
  • 最大时钟频率: 142MHz

可通过以下方式进一步优化:

  1. 用Block RAM替代寄存器存储游戏矩阵
  2. 状态机采用One-Hot编码
  3. 共享部分算术运算单元

5.2 进阶功能扩展

已完成基础功能后,可考虑:

  1. PS端增强
    • 通过UART添加高分记录功能
    • 使用ARM核实现游戏音效
  2. 显示增强
    • 添加渐变色方块
    • 实现粒子消除特效
  3. 游戏性扩展
    • 添加预览下一个方块功能
    • 实现多种难度模式

实际开发中发现,在PL端实现伪随机数生成器时,线性反馈移位寄存器(LFSR)方案比计数器方案具有更好的随机性表现。推荐使用32位LFSR:next_val = {lfsr[30:0], lfsr[31] ^ lfsr[21] ^ lfsr[1] ^ lfsr[0]}

这个项目最令人惊喜的发现是:通过合理的状态机设计,仅用不到2,000行Verilog代码就实现了完整的游戏逻辑。相比基于处理器的实现,FPGA版本具有确定性的实时响应(最差延迟<100ns),这对理解硬件并行处理的优势提供了绝佳案例。下一步计划将系统移植到更小的Artix-7 FPGA上,挑战资源利用率的极限。