第04课: 画矩形

3色矩形渲染,VGA图形绘制基础

🏆 3色矩形渲染正确 ✅ Verilator仿真验证通过

📖 核心概念

💡 关键思路:本课的核心是矩形碰撞检测——判断像素(x,y)是否在矩形(left,right,top,bottom)内:x>=left && x=top && y

💻 Verilog设计代码

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

// 第04课: 画矩形 - 3色矩形渲染正确 // 第4课: 画矩形 - 3色矩形渲染正确 module rect_draw ( input wire clk, input wire rst_n, input wire [9:0] hcount, input wire [9:0] vcount, input wire video_on, output reg [2:0] rgb ); // Rectangle 1: Red (100,80) to (250,180) // Rectangle 2: Green (300,100) to (450,250) // Rectangle 3: Blue (200,280) to (400,380) wire in_rect1 = (hcount >= 100 && hcount < 250 && vcount >= 80 && vcount < 180); wire in_rect2 = (hcount >= 300 && hcount < 450 && vcount >= 100 && vcount < 250); wire in_rect3 = (hcount >= 200 && hcount < 400 && vcount >= 280 && vcount < 380); always @(posedge clk or negedge rst_n) begin if (!rst_n) rgb <= 3'b000; else if (video_on) begin if (in_rect1) rgb <= 3'b100; // Red else if (in_rect2) rgb <= 3'b010; // Green else if (in_rect3) rgb <= 3'b001; // Blue else rgb <= 3'b000; // Black background end else rgb <= 3'b000; 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; reg [9:0] hcount, vcount; reg video_on; wire [2:0] rgb; rect_draw uut ( .clk(clk), .rst_n(rst_n), .hcount(hcount), .vcount(vcount), .video_on(video_on), .rgb(rgb) ); always clk = #10 ~clk; integer red_count, green_count, blue_count, bg_count; initial begin $dumpfile("sim.vcd"); $dumpvars(0, tb); clk = 0; rst_n = 0; video_on = 1; hcount = 0; vcount = 0; repeat(5) @(posedge clk); rst_n = 1; $display("=== 画矩形仿真 ==="); $display("3色矩形渲染正确"); $display(""); // Test 1: Inside rect1 (Red) $display("--- 测试1: 红色矩形 ---"); hcount = 150; vcount = 100; video_on = 1; repeat(3) @(posedge clk); if (rgb == 3'b100) $display(" ✅ 红色矩形内部: RGB=%b", rgb); else $display(" ❌ 红色矩形内部: RGB=%b(期望100)", rgb); // Test 2: Inside rect2 (Green) $display(""); $display("--- 测试2: 绿色矩形 ---"); hcount = 350; vcount = 150; repeat(3) @(posedge clk); if (rgb == 3'b010) $display(" ✅ 绿色矩形内部: RGB=%b", rgb); else $display(" ❌ 绿色矩形内部: RGB=%b(期望010)", rgb); // Test 3: Inside rect3 (Blue) $display(""); $display("--- 测试3: 蓝色矩形 ---"); hcount = 300; vcount = 300; repeat(3) @(posedge clk); if (rgb == 3'b001) $display(" ✅ 蓝色矩形内部: RGB=%b", rgb); else $display(" ❌ 蓝色矩形内部: RGB=%b(期望001)", rgb); // Test 4: Background $display(""); $display("--- 测试4: 背景区域 ---"); hcount = 50; vcount = 50; repeat(3) @(posedge clk); if (rgb == 3'b000) $display(" ✅ 背景区域: RGB=%b(黑色)", rgb); else $display(" ❌ 背景区域: RGB=%b(期望000)", rgb); // Test 5: Rect1 boundary $display(""); $display("--- 测试5: 边界测试 ---"); hcount = 100; vcount = 80; repeat(3) @(posedge clk); if (rgb == 3'b100) $display(" ✅ 矩形1左上角(100,80)在矩形内"); hcount = 99; vcount = 80; repeat(3) @(posedge clk); if (rgb == 3'b000) $display(" ✅ 矩形1左侧外部(99,80)不在矩形内"); hcount = 249; vcount = 179; repeat(3) @(posedge clk); if (rgb == 3'b100) $display(" ✅ 矩形1内部(249,179)正确"); // Test 6: Scan count $display(""); $display("--- 测试6: 像素统计 ---"); red_count = 0; green_count = 0; blue_count = 0; bg_count = 0; for (vcount = 0; vcount < 480; vcount = vcount + 1) begin for (hcount = 0; hcount < 640; hcount = hcount + 1) begin video_on = 1; repeat(2) @(posedge clk); case (rgb) 3'b100: red_count = red_count + 1; 3'b010: green_count = green_count + 1; 3'b001: blue_count = blue_count + 1; default: bg_count = bg_count + 1; endcase end end $display(" 红: %0d像素 (期望15000)", red_count); $display(" 绿: %0d像素 (期望22500)", green_count); $display(" 蓝: %0d像素 (期望20000)", blue_count); $display(" 背景: %0d像素", bg_count); if (red_count > 0 && green_count > 0 && blue_count > 0) $display(" ✅ 3色矩形均有像素渲染"); else $display(" ❌ 某色矩形未渲染"); $display(""); $display("✅ 3色矩形渲染正确验证通过!"); $display("🏆 成就解锁: 3色矩形渲染正确!"); $finish; end endmodule

✅ 仿真输出

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

=== 画矩形仿真 === 3色矩形渲染正确 --- 测试1: 红色矩形 --- ✅ 红色矩形内部: RGB=100 --- 测试2: 绿色矩形 --- ✅ 绿色矩形内部: RGB=010 --- 测试3: 蓝色矩形 --- ✅ 蓝色矩形内部: RGB=001 --- 测试4: 背景区域 --- ✅ 背景区域: RGB=000(黑色) --- 测试5: 边界测试 --- ✅ 矩形1左上角(100,80)在矩形内 ✅ 矩形1左侧外部(99,80)不在矩形内 ✅ 矩形1内部(249,179)正确 --- 测试6: 像素统计 --- 红: 15000像素 (期望15000) 绿: 22500像素 (期望22500) 蓝: 20000像素 (期望20000) 背景: 249700像素 ✅ 3色矩形均有像素渲染 ✅ 3色矩形渲染正确验证通过! 🏆 成就解锁: 3色矩形渲染正确! - tb.sv:93: 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
矩形1:红色:左上角(100,80)到右下角(250,180),150×100像素=15000像素。这是游戏中血条/能量条的常见形状
2
矩形2:绿色:左上角(300,100)到右下角(450,250),150×150像素=22500像素。绿色常用于表示友方/安全区域
3
矩形3:蓝色:左上角(200,280)到右下角(400,380),200×100像素=20000像素。蓝色可用于表示水面/障碍物
4
像素统计:总活跃像素=15000+22500+20000=57500,背景=640×480-57500=249700像素

🎮 游戏开发知识

硬件加速:GPU本质就是大规模并行执行矩形/三角形碰撞检测。现代GPU每秒处理数十亿个像素

深度排序:多个矩形重叠时需要确定显示顺序。游戏引擎使用Z-buffer或画家算法解决此问题

精灵系统:矩形是精灵(Sprite)的基础。精灵=矩形+透明度+纹理映射

🏆
3色矩形渲染正确
✅ Verilator仿真验证通过

🧠 知识扩展

硬件加速:GPU本质就是大规模并行执行矩形/三角形碰撞检测。现代GPU每秒处理数十亿个像素

深度排序:多个矩形重叠时需要确定显示顺序。游戏引擎使用Z-buffer或画家算法解决此问题

精灵系统:矩形是精灵(Sprite)的基础。精灵=矩形+透明度+纹理映射

⚡ 性能提示

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

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

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

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