ROM存储位图 + 坐标映射像素
🏆 成就:像素画家 ✅ Verilator验证
把图像像素颜色存进ROM,用坐标作为地址读取对应颜色。我们用8×8笑脸图案演示。
reg [7:0] rom [0:7]; // 8行×8位
initial begin
rom[0]=8'b00111100; rom[1]=8'b01111110;
rom[2]=8'b01011010; rom[3]=8'b01111110;
rom[4]=8'b01111110; rom[5]=8'b01011010;
rom[6]=8'b01100110; rom[7]=8'b00111100;
end
wire [2:0] img_x=(hcount-POS_X)>>3;
wire [2:0] img_y=(vcount-POS_Y)>>3;
wire pixel_on=rom[img_y][7-img_x];
assign rgb=video_on?(in_image?(pixel_on?3'b110:3'b000):3'b011):3'b000;module vga_bitmap_tb;
logic clk,rst_n; logic hsync,vsync; logic [2:0] rgb;
vga_bitmap uut(.*);
initial clk=0; always #20 clk=~clk;
int yellow_count, cyan_count;
int frame_count=0; logic vsync_prev;
always @(posedge clk) begin
vsync_prev<=vsync;
if(uut.video_on) case(rgb)
3'b110: yellow_count++;
3'b011: cyan_count++;
endcase
if(vsync&&!vsync_prev) begin
frame_count++;
if(frame_count==2) begin
$display("========== 位图验证 ==========");
$display("笑脸黄色像素: %0d", yellow_count);
$display("背景青色像素: %0d", cyan_count);
$display("总可见像素: %0d (640×480=%0d)",
yellow_count+cyan_count, 640*480);
if(yellow_count>0 && cyan_count>0)
$display("✓ 笑脸图案和背景均可见!");
$display("==============================");
$finish;
end
end
end
initial begin rst_n=0;#100;rst_n=1;end
initial #100_000_000 $finish;
endmodule| 图案尺寸 | 位深 | ROM容量 | 备注 |
|---|---|---|---|
| 8×8 | 1位 | 8字节 | 最简演示 |
| 8×8 | 3位 | 24字节 | 8色图案 |
| 16×16 | 3位 | 96字节 | 经典精灵 |
| 32×32 | 4位 | 512字节 | 中等精灵 |
| 64×64 | 8位 | 4KB | 大角色 |
| 256×224 | 8位 | 56KB | 整屏背景 |
💡 FPGA中的ROM:小ROM直接用Verilog reg数组综合,工具自动映射到LUT。大ROM使用Block RAM (BRAM)。
1位ROM只能表示"亮/暗"。要显示多色图案,每个像素需要更多位。下面是3位色ROM的实现方式:
// 3位色ROM:每个像素3位(R,G,B)
// 8×8像素 = 64个3位值
reg [2:0] color_rom [0:63];
initial begin
// 蘑菇图案(红白相间)
color_rom[0+:8] = '{3'b000,3'b000,3'b100,3'b100,3'b100,3'b100,3'b000,3'b000};
color_rom[8+:8] = '{3'b000,3'b100,3'b100,3'b100,3'b100,3'b100,3'b100,3'b000};
color_rom[16+:8] = '{3'b000,3'b100,3'b111,3'b100,3'b100,3'b111,3'b100,3'b000};
color_rom[24+:8] = '{3'b100,3'b111,3'b100,3'b111,3'b111,3'b100,3'b111,3'b100};
color_rom[32+:8] = '{3'b100,3'b111,3'b100,3'b111,3'b111,3'b100,3'b111,3'b100};
color_rom[40+:8] = '{3'b000,3'b100,3'b111,3'b111,3'b111,3'b111,3'b100,3'b000};
color_rom[48+:8] = '{3'b000,3'b100,3'b100,3'b110,3'b110,3'b100,3'b100,3'b000};
color_rom[56+:8] = '{3'b000,3'b000,3'b110,3'b110,3'b110,3'b110,3'b000,3'b000};
end
// 地址计算
wire [5:0] rom_addr = {img_y, img_x}; // 6位: y[2:0] & x[2:0]
wire [2:0] pixel_rgb = color_rom[rom_addr];挑战1:设计一个16×16的心形图案
挑战2:实现多图案切换(按按钮切换显示不同图案)
挑战3:用ROM显示数字0~9(为Pong计分板做准备)
1. 在方格纸上画出8×8图案
2. 把每个格子转换为1(亮)或0(暗)
3. 每行8位组成一个字节
4. 修改rom[0]~rom[7]的值
5. 重新编译仿真看你的图案!
verilator --binary -j 0 --trace \
vga_sync.v vga_bitmap.v vga_bitmap_tb.sv
./obj_dir/Vvga_bitmap_tb
# 预期输出:
# ========== 位图验证 ==========
# 笑脸黄色像素: ...
# 背景青色像素: ...
# 总可见像素: 307200 (640×480=307200)
# ✓ 笑脸图案和背景均可见!
# ==============================💡 FPGA中的ROM:小ROM(几十字节)用Verilog reg数组综合,工具自动映射到LUT。大ROM(几KB+)使用Block RAM (BRAM)。Verilator对这两种都支持。
✅ ROM如何存储位图数据?
✅ 如何用屏幕坐标计算ROM地址?
✅ 为什么要放大(SCALE)?小图案在大屏幕上会怎样?
✅ 1位ROM和3位ROM的区别是什么?
✅ 如何让图案在屏幕上居中显示?
✅ FPGA中ROM用什么实现?LUT vs BRAM?
下一课我们让图案动起来!精灵(Sprite)= 位置寄存器 + 图案ROM。通过每帧更新位置,实现弹跳移动效果。这是游戏开发的核心概念!
Q: 图案不显示? 检查POS_X和POS_Y是否在640×480范围内。
Q: 图案位置偏移? 确认SCALE和坐标计算是否正确:img_x = (hcount-POS_X) >> $clog2(SCALE)
Q: 图案变形? 检查ROM位序——最高位对应最左像素。
Q: Verilator报错? 确保vga_sync.v在同一目录下。
Verilog的initial块在仿真中有效,但综合到真实FPGA时行为不同!大多数FPGA工具链会忽略initial块。实际部署时需要:①使用复位逻辑初始化 ②使用$readmemh从文件加载 ③使用BRAM的初始化属性。Verilator仿真中initial完全OK,所以本课代码可以直接跑。
| 图案尺寸 | 位深 | ROM容量 |
|---|---|---|
| 8×8 | 1位 | 8字节 |
| 16×16 | 3位 | 96字节 |
| 32×32 | 4位 | 512字节 |
| 64×64 | 8位 | 4KB |
| 256×224 | 8位 | 56KB |
💡 FPGA中的ROM:小ROM直接用Verilog的reg数组综合,工具自动映射到LUT。大ROM需要Block RAM (BRAM)。
1. 在方格纸上画8×8图案
2. 每格转换为1(亮)或0(暗)
3. 每行8位组成一个字节
4. 修改rom[0]~rom[7]的值
5. 重新编译仿真看你的图案!
module vga_bitmap_tb;
logic clk,rst_n; logic hsync,vsync; logic [2:0] rgb;
vga_bitmap uut(.*);
initial clk=0; always #20 clk=~clk;
int yellow_count, cyan_count;
int frame_count=0; logic vsync_prev;
always @(posedge clk) begin
vsync_prev<=vsync;
if(uut.video_on) case(rgb)
3'b110: yellow_count++;
3'b011: cyan_count++;
endcase
if(vsync&&!vsync_prev) begin
frame_count++;
if(frame_count==2) begin
$display("========== 位图验证 ==========");
$display("笑脸黄色像素: %0d",yellow_count);
$display("背景青色像素: %0d",cyan_count);
$display("总可见像素: %0d",yellow_count+cyan_count);
if(yellow_count>0&&cyan_count>0)
$display("✓ 笑脸图案和背景均可见!");
$display("==============================");
$finish;
end
end
end
initial begin rst_n=0;#100;rst_n=1;end
initial #100_000_000 $finish;
endmodule