第07课: 打砖块

16块砖+挡板+球碰撞消除,经典Breakout

🏆 砖块碰撞消除正确 ✅ Verilator仿真验证通过

📖 核心概念

💡 关键思路:本课的核心是砖块矩阵——16个砖块分2行8列,每个80×16像素。bricks寄存器的每一位代表一个砖块的存在状态

💻 Verilog设计代码

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

// 第07课: 打砖块 - 砖块碰撞消除正确 // 第7课: 打砖块 - 砖块碰撞消除正确 module breakout ( input wire clk, input wire rst_n, input wire btn_left, input wire btn_right, output reg [9:0] ball_x, output reg [8:0] ball_y, output reg [9:0] paddle_x, output reg [15:0] bricks, output reg ball_dir_x, output reg ball_dir_y, output reg [4:0] score ); // 16 bricks in top 2 rows (8 per row) // Paddle at bottom // Ball bounces around reg [15:0] tick; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin ball_x <= 10'd320; ball_y <= 9'd400; paddle_x <= 10'd300; bricks <= 16'hFFFF; // All bricks present ball_dir_x <= 1; ball_dir_y <= 0; // going up tick <= 0; score <= 0; end else begin tick <= tick + 1; // Move ball every 500 ticks if (tick == 500) begin tick <= 0; if (ball_dir_x) ball_x <= ball_x + 1; else ball_x <= ball_x - 1; if (ball_dir_y) ball_y <= ball_y + 1; else ball_y <= ball_y - 1; // Wall bounces if (ball_x <= 0) ball_dir_x <= 1; if (ball_x >= 632) ball_dir_x <= 0; if (ball_y <= 0) ball_dir_y <= 1; if (ball_y >= 472) ball_dir_y <= 0; // Paddle bounce (paddle at y=450, width=80) if (ball_y >= 450 && ball_y <= 458 && ball_x >= paddle_x && ball_x < paddle_x + 80) begin ball_dir_y <= 0; // bounce up end // Brick collision - row 0 at y=40, row 1 at y=60 if (ball_y >= 40 && ball_y < 56) begin // Row 0: bricks 0-7 if (ball_x >= 0 && ball_x < 80 && bricks[0]) begin bricks[0] <= 0; ball_dir_y <= 1; score <= score + 1; end else if (ball_x >= 80 && ball_x < 160 && bricks[1]) begin bricks[1] <= 0; ball_dir_y <= 1; score <= score + 1; end else if (ball_x >= 160 && ball_x < 240 && bricks[2]) begin bricks[2] <= 0; ball_dir_y <= 1; score <= score + 1; end else if (ball_x >= 240 && ball_x < 320 && bricks[3]) begin bricks[3] <= 0; ball_dir_y <= 1; score <= score + 1; end else if (ball_x >= 320 && ball_x < 400 && bricks[4]) begin bricks[4] <= 0; ball_dir_y <= 1; score <= score + 1; end else if (ball_x >= 400 && ball_x < 480 && bricks[5]) begin bricks[5] <= 0; ball_dir_y <= 1; score <= score + 1; end else if (ball_x >= 480 && ball_x < 560 && bricks[6]) begin bricks[6] <= 0; ball_dir_y <= 1; score <= score + 1; end else if (ball_x >= 560 && ball_x < 640 && bricks[7]) begin bricks[7] <= 0; ball_dir_y <= 1; score <= score + 1; end end else if (ball_y >= 60 && ball_y < 76) begin // Row 1: bricks 8-15 if (ball_x >= 0 && ball_x < 80 && bricks[8]) begin bricks[8] <= 0; ball_dir_y <= 1; score <= score + 1; end else if (ball_x >= 80 && ball_x < 160 && bricks[9]) begin bricks[9] <= 0; ball_dir_y <= 1; score <= score + 1; end else if (ball_x >= 160 && ball_x < 240 && bricks[10]) begin bricks[10] <= 0; ball_dir_y <= 1; score <= score + 1; end else if (ball_x >= 240 && ball_x < 320 && bricks[11]) begin bricks[11] <= 0; ball_dir_y <= 1; score <= score + 1; end else if (ball_x >= 320 && ball_x < 400 && bricks[12]) begin bricks[12] <= 0; ball_dir_y <= 1; score <= score + 1; end else if (ball_x >= 400 && ball_x < 480 && bricks[13]) begin bricks[13] <= 0; ball_dir_y <= 1; score <= score + 1; end else if (ball_x >= 480 && ball_x < 560 && bricks[14]) begin bricks[14] <= 0; ball_dir_y <= 1; score <= score + 1; end else if (ball_x >= 560 && ball_x < 640 && bricks[15]) begin bricks[15] <= 0; ball_dir_y <= 1; score <= score + 1; end end end // Paddle control if (btn_left && paddle_x > 0) paddle_x <= paddle_x - 1; if (btn_right && paddle_x < 560) paddle_x <= paddle_x + 1; 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; wire [9:0] ball_x, paddle_x; wire [8:0] ball_y; wire [15:0] bricks; wire ball_dir_x, ball_dir_y; wire [4:0] score; breakout uut ( .clk(clk), .rst_n(rst_n), .btn_left(btn_left), .btn_right(btn_right), .ball_x(ball_x), .ball_y(ball_y), .paddle_x(paddle_x), .bricks(bricks), .ball_dir_x(ball_dir_x), .ball_dir_y(ball_dir_y), .score(score) ); 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; repeat(5) @(posedge clk); rst_n = 1; $display("=== 打砖块仿真 ==="); $display("砖块碰撞消除正确"); $display(""); // Test 1: All bricks present at start $display("--- 测试1: 初始砖块 ---"); repeat(10) @(posedge clk); $display(" 砖块=16'h%04h", bricks); if (bricks == 16'hFFFF) $display(" ✅ 16个砖块全部存在"); else $display(" ❌ 初始砖块错误"); // Test 2: Paddle control $display(""); $display("--- 测试2: 挡板控制 ---"); btn_right = 1; repeat(100) @(posedge clk); btn_right = 0; $display(" 按右后挡板X=%0d", paddle_x); $display(" ✅ 挡板可左右移动"); // Test 3: Ball hits brick[0] $display(""); $display("--- 测试3: 砖块碰撞 ---"); // Force ball into brick[0] region (x=40, y=45) uut.ball_x = 40; uut.ball_y = 45; uut.ball_dir_y = 0; uut.tick = 499; repeat(10) @(posedge clk); $display(" 碰撞后: 砖块=16'h%04h, 分数=%0d", bricks, score); if (bricks[0] == 0 && score >= 1) $display(" ✅ 砖块[0]被消除, 分数+1"); else $display(" ❌ 砖块碰撞未生效"); // Test 4: Ball hits brick[4] $display(""); $display("--- 测试4: 另一砖块碰撞 ---"); uut.ball_x = 350; uut.ball_y = 45; uut.ball_dir_y = 0; uut.tick = 499; repeat(10) @(posedge clk); $display(" 碰撞后: 砖块=16'h%04h, 分数=%0d", bricks, score); if (bricks[4] == 0 && score >= 2) $display(" ✅ 砖块[4]被消除, 分数累加"); else $display(" ❌ 砖块碰撞未生效"); // Test 5: Ball bounces off paddle $display(""); $display("--- 测试5: 挡板反弹 ---"); uut.ball_x = 300; uut.ball_y = 450; uut.ball_dir_y = 1; uut.tick = 499; uut.paddle_x = 280; repeat(10) @(posedge clk); $display(" 碰挡板后方向Y=%b", ball_dir_y); if (ball_dir_y == 0) $display(" ✅ 球碰挡板向上反弹"); else $display(" ❌ 挡板反弹失败"); // Test 6: Score tracking $display(""); $display("--- 测试6: 分数统计 ---"); $display(" 当前分数=%0d", score); if (score >= 2) $display(" ✅ 分数正确累加"); 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: 初始砖块 --- 砖块=16'hffff ✅ 16个砖块全部存在 --- 测试2: 挡板控制 --- 按右后挡板X=400 ✅ 挡板可左右移动 --- 测试3: 砖块碰撞 --- 碰撞后: 砖块=16'hfffe, 分数=1 ✅ 砖块[0]被消除, 分数+1 --- 测试4: 另一砖块碰撞 --- 碰撞后: 砖块=16'hffee, 分数=2 ✅ 砖块[4]被消除, 分数累加 --- 测试5: 挡板反弹 --- 碰挡板后方向Y=0 ✅ 球碰挡板向上反弹 --- 测试6: 分数统计 --- 当前分数=2 ✅ 分数正确累加 ✅ 砖块碰撞消除正确验证通过! 🏆 成就解锁: 砖块碰撞消除正确! - tb.sv:90: 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
砖块布局:第0行砖块y=40-56,第1行y=60-76。砖块[0]在x=0-79,砖块[7]在x=560-639
2
碰撞映射:根据球的(x,y)坐标判断碰到哪个砖块:y判断行号,x判断列号
3
消除机制:bricks[i]=0表示砖块i已消除。已消除的砖块不再参与碰撞检测
4
挡板反弹:球y>=450且在挡板x范围内时反弹向上。挡板宽度80像素

🎮 游戏开发知识

Breakout历史:1976年Atari Breakout由Steve Wozniak和Steve Jobs开发,后者当时还在Atari工作

砖块类型:高级版本中不同颜色砖块有不同耐久度,需要多次击打才能消除

道具系统:现代打砖块游戏加入道具:加宽挡板、多球、激光等,极大增加游戏性

🏆
砖块碰撞消除正确
✅ Verilator仿真验证通过

🧠 知识扩展

Breakout历史:1976年Atari Breakout由Steve Wozniak和Steve Jobs开发,后者当时还在Atari工作

砖块类型:高级版本中不同颜色砖块有不同耐久度,需要多次击打才能消除

道具系统:现代打砖块游戏加入道具:加宽挡板、多球、激光等,极大增加游戏性

⚡ 性能提示

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

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

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

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