🌈 第3课:VGA彩色

RGB颜色原理 + 彩色条纹生成器

🏆 成就:色彩魔术师 ✅ Verilator验证

🌈
色彩魔术师
Verilator仿真画出RGB彩色条纹

🎨 RGB加色法

VGA用加色法:3位RGB = 8种颜色。640像素分8条,每条80像素。

Verilogvga_colorbars.v核心
// 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;

🎨 RGB加色法详解

RGB加色法混合: R=0 G=0 B=0 → 黑色 (全关) R=1 G=0 B=0 → 红色 R=0 G=1 B=0 → 绿色 R=0 G=0 B=1 → 蓝色 R=1 G=1 B=0 → 黄色 (红+绿) R=1 G=0 B=1 → 品红 (红+蓝) R=0 G=1 B=1 → 青色 (绿+蓝) R=1 G=1 B=1 → 白色 (全开) 这和画画用的"减色法"不同! 加色法:越多越亮,全开=白 减色法:越多越暗,全混=黑

🧪 Verilator完整测试台

SystemVerilogvga_colorbars_tb.sv
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;
endmodule

🔬 进阶:渐变色彩条

Verilogvga_gradient.v — 6位渐变色
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_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色真彩色

🔬 从3色到16M色:FPGA颜色进化之路

3位RGB (1位/通道) → 8色 每通道只有开(1)和关(0)两种状态 适合入门演示,但颜色太粗糙 6位RGB (2位/通道) → 64色 每通道4级亮度:00,01,10,11 可以做简单渐变和游戏 实际硬件:2个电阻做2位DAC 9位RGB (3位/通道) → 512色 每通道8级亮度 比较平滑的色彩过渡 12位RGB (4位/通道) → 4096色 每通道16级亮度 常用FPGA开发板配置 4个电阻做4位R-2R DAC 24位RGB (8位/通道) → 16,777,216色 真彩色,人眼无法区分更多 需要8位DAC或视频DAC芯片 如ADV7123等专用视频DAC 实际硬件DAC实现: 1位→1个电阻(2级) 2位→2个电阻(4级) 4位→4个电阻(16级) 8位→专用DAC芯片(256级) 电阻值选择(以3.3V FPGA为例): VGA标准:0.7V对应全亮 R-2R梯形网络最常用

🎯 实战挑战

挑战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梯形电阻网络

🛠️ 编译运行

Bash编译运行步骤
# 需要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)的前置知识!

🔬 进阶:渐变色彩条

Verilogvga_gradient.v — 渐变色彩
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] 做棋盘格

🧪 Verilator完整测试台

SystemVerilogvga_colorbars_tb.sv
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