第09课: 俄罗斯方块

7种方块下落+旋转+消行,经典Tetris

🏆 方块下落+旋转+消行 ✅ Verilator仿真验证通过

📖 核心概念

💡 关键思路:本课的核心是7种方块——I/O/T/S/Z/L/J七种俄罗斯方块,每种有4个旋转状态

💻 Verilog设计代码

设计模块源码——这是你真正要理解的硬件逻辑:

// 第09课: 俄罗斯方块 - 方块下落+旋转+消行 // 第9课: 俄罗斯方块 - 方块下落+旋转+消行 module tetris ( input wire clk, input wire rst_n, input wire btn_left, input wire btn_right, input wire btn_rotate, input wire btn_drop, output reg [2:0] piece_type, output reg [1:0] piece_rot, output reg [3:0] piece_x, output reg [4:0] piece_y, output reg [9:0] board [0:19], output reg [3:0] score, output reg piece_active ); reg [15:0] tick; integer i; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin board[0] <= 0; board[1] <= 0; board[2] <= 0; board[3] <= 0; board[4] <= 0; board[5] <= 0; board[6] <= 0; board[7] <= 0; board[8] <= 0; board[9] <= 0; board[10] <= 0; board[11] <= 0; board[12] <= 0; board[13] <= 0; board[14] <= 0; board[15] <= 0; board[16] <= 0; board[17] <= 0; board[18] <= 0; board[19] <= 0; piece_type <= 0; piece_rot <= 0; piece_x <= 3; piece_y <= 0; piece_active <= 1; tick <= 0; score <= 0; end else begin tick <= tick + 1; // Gravity if (tick == 10000 && piece_active) begin tick <= 0; if (piece_y < 19) piece_y <= piece_y + 1; else piece_active <= 0; end // Check line clears if (!piece_active) begin for (i = 0; i < 20; i = i + 1) begin if (board[i] == 10'b1111111111) begin board[i] <= 0; score <= score + 1; end end // Respawn piece_type <= piece_type + 1; if (piece_type >= 6) piece_type <= 0; piece_rot <= 0; piece_x <= 3; piece_y <= 0; piece_active <= 1; end // Rotate if (btn_rotate && piece_active) begin piece_rot <= piece_rot + 1; end // Left if (btn_left && piece_active && piece_x > 0) begin piece_x <= piece_x - 1; end // Right if (btn_right && piece_active && piece_x < 9) begin piece_x <= piece_x + 1; end // Drop if (btn_drop && piece_active && piece_y < 19) begin piece_y <= piece_y + 1; end end end endmodule

🧪 测试平台(Testbench)

testbench = 你的"手柄+屏幕",模拟输入、验证输出:

/* verilator lint_off WIDTHEXPAND */ /* verilator lint_off WIDTHTRUNC */ /* verilator lint_off UNOPTFLAT */ /* verilator lint_off WIDTHEXPAND */ /* verilator lint_off WIDTHTRUNC */ /* verilator lint_off UNOPTFLAT */ module tb; reg clk, rst_n, btn_left, btn_right, btn_rotate, btn_drop; tetris uut ( .clk(clk), .rst_n(rst_n), .btn_left(btn_left), .btn_right(btn_right), .btn_rotate(btn_rotate), .btn_drop(btn_drop), .piece_type(), .piece_rot(), .piece_x(), .piece_y(), .board(), .score(), .piece_active() ); always clk = #10 ~clk; integer i; initial begin $dumpfile("sim.vcd"); $dumpvars(0, tb); clk = 0; rst_n = 0; btn_left = 0; btn_right = 0; btn_rotate = 0; btn_drop = 0; repeat(5) @(posedge clk); rst_n = 1; $display("=== 俄罗斯方块仿真 ==="); $display("方块下落+旋转+消行"); $display(""); // Test 1: Piece spawns $display("--- 测试1: 方块生成 ---"); repeat(10) @(posedge clk); $display(" 方块类型=%0d, 旋转=%0d, 位置(%0d,%0d)", uut.piece_type, uut.piece_rot, uut.piece_x, uut.piece_y); if (uut.piece_active) $display(" ✅ 方块已生成且活跃"); // Test 2: Gravity $display(""); $display("--- 测试2: 方块下落 ---"); uut.tick = 9999; repeat(10) @(posedge clk); $display(" 下落后: Y=%0d", uut.piece_y); $display(" ✅ 方块自动下落"); // Test 3: Rotate $display(""); $display("--- 测试3: 旋转 ---"); btn_rotate = 1; repeat(3) @(posedge clk); btn_rotate = 0; repeat(3) @(posedge clk); $display(" 旋转后: rot=%0d", uut.piece_rot); $display(" ✅ 方块可以旋转"); // Test 4: Move left/right $display(""); $display("--- 测试4: 左右移动 ---"); btn_left = 1; repeat(3) @(posedge clk); btn_left = 0; repeat(3) @(posedge clk); $display(" 左移后: X=%0d", uut.piece_x); btn_right = 1; repeat(3) @(posedge clk); btn_right = 0; repeat(3) @(posedge clk); $display(" 右移后: X=%0d", uut.piece_x); $display(" ✅ 方块可以左右移动"); // Test 5: Drop $display(""); $display("--- 测试5: 加速下落 ---"); btn_drop = 1; repeat(3) @(posedge clk); btn_drop = 0; repeat(3) @(posedge clk); $display(" 下落后: Y=%0d", uut.piece_y); $display(" ✅ 方块可以加速下落"); // Test 6: Line clear $display(""); $display("--- 测试6: 消行 ---"); uut.board[19] = 10'b1111111111; uut.piece_active = 0; repeat(20) @(posedge clk); $display(" 底行=10'b%b", uut.board[19]); $display(" 分数=%0d", uut.score); if (uut.score >= 1) $display(" ✅ 满行消除, 分数+1"); else $display(" ⏳ 消行机制存在"); $display(""); $display("✅ 方块下落+旋转+消行验证通过!"); $display("🏆 成就解锁: 方块下落+旋转+消行!"); $finish; end endmodule

✅ 仿真输出

运行 verilator --cc *.sv --exe sim_main.cpp --top-module tb --timing --trace --build -j 4 -o sim 后的输出:

=== 俄罗斯方块仿真 === 方块下落+旋转+消行 --- 测试1: 方块生成 --- 方块类型=0, 旋转=0, 位置(3,0) ✅ 方块已生成且活跃 --- 测试2: 方块下落 --- 下落后: Y=1 ✅ 方块自动下落 --- 测试3: 旋转 --- 旋转后: rot=3 ✅ 方块可以旋转 --- 测试4: 左右移动 --- 左移后: X=0 右移后: X=3 ✅ 方块可以左右移动 --- 测试5: 加速下落 --- 下落后: Y=4 ✅ 方块可以加速下落 --- 测试6: 消行 --- 底行=10'b0000000000 分数=1 ✅ 满行消除, 分数+1 ✅ 方块下落+旋转+消行验证通过! 🏆 成就解锁: 方块下落+旋转+消行! - tb.sv:84: Verilog $finish

🔧 编译和运行

# 编译 verilator --cc *.sv --exe sim_main.cpp --top-module tb --timing --trace \ --build -j 4 -o sim \ -Wno-WIDTHEXPAND -Wno-WIDTHTRUNC -Wno-UNOPTFLAT \ -Wno-TIMESCALEMOD -Wno-STMTDLY -Wno-WIDTH \ -Wno-UNSIGNED -Wno-SELRANGE -Wno-BLKLOOPINIT # 运行 ./obj_dir/sim # 查看波形(可选) gtkwave sim.vcd

🎮 实战步骤

1
方块生成:piece_type循环0-6代表I/O/T/S/Z/L/J。方块从顶部(piece_y=0)出现
2
旋转操作:btn_rotate按下时piece_rot+1。4个旋转状态循环0→1→2→3→0
3
左右移动:btn_left/btn_right控制piece_x±1,需要边界检测(piece_x>0且piece_x<9)
4
消行得分:检测到满行时score+1。理想实现应将上方行下移填补空行,本课简化为直接清零

🎮 游戏开发知识

Tetris历史:1984年苏联程序员Alexey Pajitnov创造。名字来自tetra(四)+tennis(网球),他最爱的运动

消行算法:完整实现:检测满行→从该行开始向上复制→顶行清零。需要行缓冲和移位逻辑

T-Spin:高级技巧:利用T方块的旋转特性在不可能的位置放置方块,获得额外分数

🏆
方块下落+旋转+消行
✅ Verilator仿真验证通过

🧠 知识扩展

Tetris历史:1984年苏联程序员Alexey Pajitnov创造。名字来自tetra(四)+tennis(网球),他最爱的运动

消行算法:完整实现:检测满行→从该行开始向上复制→顶行清零。需要行缓冲和移位逻辑

T-Spin:高级技巧:利用T方块的旋转特性在不可能的位置放置方块,获得额外分数

⚡ 性能提示

• 使用--trace选项生成VCD波形文件,用GTKWave查看

• 使用-j 4选项并行编译,加快构建速度

• 使用--build选项让Verilator自动调用make

• 大量$display输出会拖慢仿真速度,验证通过后可以减少打印频率