RGB颜色原理 + 彩色条纹生成器
🏆 成就:色彩魔术师 ✅ Verilator验证
VGA用加色法:3位RGB = 8种颜色。640像素分8条,每条80像素。
// hcount[9:7]选择第几条(0~7)
wire [2:0] bar_index = ~hcount[9:7]; // 反转顺序
reg [2:0] bar_color;
always @(*) case(bar_index)
0: bar_color=3'b000; // 黑
1: bar_color=3'b001; // 蓝
2: bar_color=3'b010; // 绿
3: bar_color=3'b011; // 青
4: bar_color=3'b100; // 红
5: bar_color=3'b101; // 品红
6: bar_color=3'b110; // 黄
7: bar_color=3'b111; // 白
endcase
assign rgb = video_on ? bar_color : 3'b000;module vga_colorbars_tb;
logic clk,rst_n; logic hsync,vsync; logic [2:0] rgb;
vga_colorbars uut(.*);
initial clk=0; always #20 clk=~clk;
int bar_count[8], black_in_blank;
always @(posedge clk) begin
if(uut.video_on) bar_count[rgb]++;
else if(rgb!==3'b000) $display("FAIL:blank RGB=%b",rgb);
else black_in_blank++;
end
int frame_count=0; logic vsync_prev;
always @(posedge clk) begin
vsync_prev<=vsync;
if(vsync&&!vsync_prev) begin
frame_count++;
if(frame_count==2) begin
$display("========== 颜色条纹验证 ==========");
$display("颜色条像素 (每条应为480×80=38400):");
for(int i=0;i<8;i++)
$display(" 颜色%0d: %0d像素",i,bar_count[i]);
int total=0;
for(int i=0;i<8;i++) total+=bar_count[i];
if(total==640*480)
$display("总可见像素: %0d (640×480=%0d) ✓",total,640*480);
$display("消隐区黑像素: %0d ✓",black_in_blank);
$display("==========================================");
$finish;
end
end
end
initial begin rst_n=0;#100;rst_n=1;end
initial #100_000_000 $finish;
endmodulemodule vga_gradient(
input clk,rst_n, output hsync,vsync, output [5:0] rgb
);
wire [9:0] hcount,vcount; wire video_on;
vga_sync u_vga_sync(.*);
wire [1:0] red = video_on ? hcount[8:7] : 2'b00;
wire [1:0] green = video_on ? vcount[8:7] : 2'b00;
wire [1:0] blue = video_ok ? (hcount[8:7]^vcount[8:7]) : 2'b00;
assign rgb = {red, green, blue};
endmodule| RGB位宽 | 每通道 | 颜色数 | 用途 |
|---|---|---|---|
| 3位 | 1位 | 8色 | 入门教学 |
| 6位 | 2位 | 64色 | 简单游戏 |
| 12位 | 4位 | 4096色 | 常用 |
| 24位 | 8位 | 16.7M色 | 真彩色 |
挑战1:画出俄罗斯方块的7种方块图案(用不同颜色)
挑战2:实现一个3×3棋盘格图案
挑战3:画出法国国旗(蓝白红三等分)
挑战4:用6位RGB实现彩虹渐变条
• 改 hcount[9:7](不反转):条纹顺序反转
• 改 vcount[8:6]:水平条纹
• 用 hcount[6] 做棋盘格
• 组合 hcount 和 vcount 做更复杂的图案
💡 DAC转换:FPGA输出数字信号(0/1),VGA需要模拟电压(0~0.7V)。实际硬件用电阻分压网络做DAC。Verilator仿真只关心数字值!
电阻DAC方案(1位/通道):
• FPGA引脚 → 270Ω电阻 → VGA R/G/B引脚
• 270Ω + VGA 75Ω终端 → 分压约0.7V
• 多位DAC用R-2R梯形电阻网络
# 需要vga_sync.v(第2课的模块)
verilator --binary -j 0 --trace \
vga_sync.v vga_colorbars.v vga_colorbars_tb.sv
./obj_dir/Vvga_colorbars_tb
# 预期输出:
# ========== 颜色条纹验证 ==========
# 颜色条像素 (每条应为480×80=38400):
# 颜色0: 38400像素
# ...
# 总可见像素: 307200 (640×480=307200) ✓
# 消隐区黑像素: ... ✓
# ==========================================
# 查看波形
gtkwave vga_colorbars.vcd &✅ RGB加色法:红+绿=黄,红+蓝=品红,绿+蓝=青
✅ 为什么3位RGB只能表示8种颜色?
✅ 如何用hcount的高位选择不同的颜色条?
✅ 消隐区(video_on=0)时RGB应该输出什么?
✅ 如何从3位扩展到6位、24位颜色?
✅ 加色法 vs 减色法的区别?
✅ DAC电阻分压的原理?
下一课我们将学习VGA位图显示——不再是简单的彩色条纹,而是在屏幕上显示任意图案!我们会用ROM存储8×8的笑脸图案,通过坐标映射让它在屏幕上出现。这是游戏开发的基础——精灵(Sprite)的前置知识!
module vga_gradient(input clk,rst_n,output hsync,vsync,output [5:0] rgb);
wire [9:0] hcount,vcount; wire video_on;
vga_sync u_vga_sync(.*);
wire [1:0] red=video_on?hcount[8:7]:2'b00; // 水平渐变
wire [1:0] green=video_on?vcount[8:7]:2'b00; // 垂直渐变
wire [1:0] blue=video_on?(hcount[8:7]^vcount[8:7]):2'b00;
assign rgb={red,green,blue};
endmodule| RGB位宽 | 每通道位数 | 颜色数 | 用途 |
|---|---|---|---|
| 3位 | 1位 | 8色 | 入门教学 |
| 6位 | 2位 | 64色 | 简单游戏 |
| 12位 | 4位 | 4096色 | 常用 |
| 24位 | 8位 | 16.7M色 | 真彩色 |
💡 DAC转换:FPGA输出数字信号(0/1),VGA需要模拟电压(0~0.7V)。实际硬件用电阻分压网络做DAC。Verilator仿真只关心数字值!
• 改 hcount[9:7](不反转):条纹顺序反转
• 改 vcount[8:6]:水平条纹
• 用 hcount[6] 做棋盘格
module vga_colorbars_tb;
logic clk,rst_n; logic hsync,vsync; logic [2:0] rgb;
vga_colorbars uut(.*);
initial clk=0; always #20 clk=~clk;
int bar_count[8], black_in_blank;
always @(posedge clk) begin
if(uut.video_on) bar_count[rgb]++;
else if(rgb!==3'b000) $display("FAIL: blank RGB=%b",rgb);
else black_in_blank++;
end
int frame_count=0; logic vsync_prev;
always @(posedge clk) begin
vsync_prev<=vsync;
if(vsync&&!vsync_prev) begin
frame_count++;
if(frame_count==2) begin
$display("========== 颜色条纹验证 ==========");
$display("颜色条像素 (每条480×80=38400):");
for(int i=0;i<8;i++)
$display(" 颜色%0d: %0d像素",i,bar_count[i]);
int total=0;
for(int i=0;i<8;i++) total+=bar_count[i];
if(total==640*480)
$display("总可见像素: %0d ✓",total);
$display("==========================================");
$finish;
end
end
end
initial begin rst_n=0;#100;rst_n=1;end
initial #100_000_000 $finish;
endmodule