🏠 课程总览 > 第09课
第09课: 俄罗斯方块
7种方块下落+旋转+消行,经典Tetris
🏆 方块下落+旋转+消行
✅ Verilator仿真验证通过
📖 核心概念
- 7种方块:I/O/T/S/Z/L/J七种俄罗斯方块,每种有4个旋转状态
- 10×20棋盘:棋盘用20个10位寄存器表示,每行10位对应10列。bit=1表示该格有方块
- 消行检测:当某行board[i]==10'b1111111111时表示该行填满,清除该行并上方行下移
💡 关键思路:本课的核心是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输出会拖慢仿真速度,验证通过后可以减少打印频率