📖 核心概念
- 矩形碰撞检测:判断像素(x,y)是否在矩形(left,right,top,bottom)内:x>=left && x=top && y
- 优先级渲染:多个矩形重叠时,先判断的矩形优先级高。本课红>绿>蓝
- 组合逻辑:矩形判断是组合逻辑,不需要时钟。在时钟上升沿采样结果输出
💡 关键思路:本课的核心是矩形碰撞检测——判断像素(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输出会拖慢仿真速度,验证通过后可以减少打印频率