VGA HDMI 帧缓冲 实时渲染
二维CA的实时可视化是一个完整的系统工程——需要帧缓冲、颜色映射、时序控制、交互接口。本课构建一个完整的显示引擎,将CA计算结果呈现在屏幕上。
完整的CA可视化系统由5个核心模块组成:
┌────────────────────────────────────────────┐ │ CA Display Engine │ │ │ │ ┌──────────┐ ┌──────────────┐ │ │ │ CA Core │────▶│ Frame Buffer │ │ │ │ (计算) │ │ (双Bank) │ │ │ └──────────┘ └──────┬───────┘ │ │ │ │ │ ┌──────────┐ ┌──────▼───────┐ │ │ │ Control │ │ Color Mapper │ │ │ │ (FSM) │ │ (调色板) │ │ │ └──────────┘ └──────┬───────┘ │ │ │ │ │ ┌──────────┐ ┌──────▼───────┐ │ │ │ Input │ │ VGA/HDMI │ │ │ │ Handler │ │ Timing Gen │ │ │ └──────────┘ └──────────────┘ │ └────────────────────────────────────────────┘
双缓冲的核心思想:CA核心写入一个Bank,显示控制器读取另一个Bank。每帧完成后交换。这消除了"撕裂"现象——显示的是完整的一帧,而不是半新半旧的混合。
// ============================================================================
// frame_buffer_dual.v - 双缓冲帧缓冲控制器
// 写端口连接CA核心,读端口连接显示控制器
// 两个Bank交替使用,消除画面撕裂
// ============================================================================
module frame_buffer_dual #(
parameter WIDTH = 160,
parameter HEIGHT = 120,
parameter DEPTH = 4 // 每像素位深
)(
input wire clk,
input wire rst_n,
// 写接口(CA核心)
input wire [7:0] wr_x,
input wire [7:0] wr_y,
input wire [DEPTH-1:0] wr_data,
input wire wr_en,
input wire frame_swap,
// 读接口(显示控制器)
input wire [7:0] rd_x,
input wire [7:0] rd_y,
output wire [DEPTH-1:0] rd_data,
output wire wr_bank
);
localparam TOTAL = WIDTH * HEIGHT;
localparam ADDR_W = $clog2(TOTAL);
reg [DEPTH-1:0] bank_a [0:TOTAL-1];
reg [DEPTH-1:0] bank_b [0:TOTAL-1];
reg write_sel;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
write_sel <= 1'b0;
else if (frame_swap)
write_sel <= ~write_sel;
end
wire [ADDR_W-1:0] wr_addr = wr_y * WIDTH + wr_x;
wire [ADDR_W-1:0] rd_addr = rd_y * WIDTH + rd_x;
always @(posedge clk) begin
if (wr_en) begin
if (write_sel) bank_b[wr_addr] <= wr_data;
else bank_a[wr_addr] <= wr_data;
end
end
assign rd_data = write_sel ? bank_a[rd_addr] : bank_b[rd_addr];
assign wr_bank = write_sel;
endmodule
简单的黑白显示不够有趣。我们可以根据元胞的年龄、邻居密度等属性映射颜色。
// ============================================================================
// color_mapper.v - CA颜色映射器
// 支持:黑白、热力图、年龄渐变、密度着色、自定义调色板
// ============================================================================
module color_mapper #(
parameter MODE = 0 // 0=黑白 1=热力图 2=年龄 3=密度 4=调色板
)(
input wire [3:0] cell_age,
input wire [3:0] neighbor_cnt,
input wire cell_alive,
input wire [11:0] palette [0:15], // 16色调色板(仅MODE=4)
output wire [11:0] rgb // 12位RGB (4-4-4)
);
reg [3:0] r, g, b;
always @(*) begin
r = 4'd0; g = 4'd0; b = 4'd0;
case (MODE)
0: begin // 黑白模式
r = cell_alive ? 4'hF : 4'h0;
g = cell_alive ? 4'hF : 4'h0;
b = cell_alive ? 4'hF : 4'h0;
end
1: begin // 热力图(年龄→温度色)
if (cell_alive) begin
if (cell_age < 4) begin
r = cell_age * 4; g = 4'd0; b = 4'd0;
end else if (cell_age < 8) begin
r = 4'hF; g = (cell_age - 4) * 4; b = 4'd0;
end else begin
r = 4'hF; g = 4'hF; b = (cell_age - 8) * 5;
end
end
end
2: begin // 粉红渐变(匹配课程主色#ec4899)
if (cell_alive) begin
r = 4'hF;
g = {1'b0, cell_age};
b = {2'b01, cell_age[3:2]};
end
end
3: begin // 密度着色
if (cell_alive) begin
r = 4'hF;
g = {neighbor_cnt, 1'b0};
end else begin
b = {neighbor_cnt, 1'b0};
end
end
4: begin // 调色板模式
{r, g, b} = cell_alive ? palette[cell_age] : 12'd0;
end
endcase
end
assign rgb = {r, g, b};
endmodule
// ============================================================================
// vga_controller.v - VGA 640x480 @60Hz 完整时序控制器
// 像素时钟: 25.175MHz (实际使用25MHz,误差<1%)
// ============================================================================
module vga_controller (
input wire clk25,
input wire rst_n,
output reg [9:0] hcount, // 水平像素计数
output reg [9:0] vcount, // 垂直行计数
output wire hsync, // 水平同步
output wire vsync, // 垂直同步
output wire blank, // 消隐(1=不显示)
output wire active // 有效显示区域
);
localparam H_VIS = 640, H_FP = 16, H_SYNC = 96, H_BP = 48, H_TOTAL = 800;
localparam V_VIS = 480, V_FP = 10, V_SYNC = 2, V_BP = 33, V_TOTAL = 525;
always @(posedge clk25 or negedge rst_n) begin
if (!rst_n) begin
hcount <= 10'd0;
vcount <= 10'd0;
end else begin
if (hcount == H_TOTAL - 1) begin
hcount <= 10'd0;
if (vcount == V_TOTAL - 1)
vcount <= 10'd0;
else
vcount <= vcount + 10'd1;
end else begin
hcount <= hcount + 10'd1;
end
end
end
assign hsync = ~((hcount >= H_VIS + H_FP) && (hcount < H_VIS + H_FP + H_SYNC));
assign vsync = ~((vcount >= V_VIS + V_FP) && (vcount < V_VIS + V_FP + V_SYNC));
assign blank = (hcount >= H_VIS) || (vcount >= V_VIS);
assign active = ~blank;
endmodule
| VGA分辨率 | 像素时钟 | CA网格 | 映射方式 | 帧率 |
|---|---|---|---|---|
| 640×480 | 25MHz | 640×480 | 1:1直接映射 | 60Hz |
| 640×480 | 25MHz | 160×120 | 4×4像素/元胞 | 60Hz |
| 800×600 | 40MHz | 200×150 | 4×4像素/元胞 | 60Hz |
| 1024×768 | 65MHz | 128×96 | 8×8像素/元胞 | 60Hz |
二维CA阶段完成!你已经构建了完整的可视化系统,从双缓冲帧缓冲到VGA时序到颜色映射。
VGA时序约束分析:
640×480 @60Hz需要25MHz像素时钟
每像素时间 = 40ns
在这个时间内必须完成:地址计算 + 存储器读取 + 颜色映射 + 输出
关键路径:
地址生成(2ns) + BRAM读取(3ns) + 颜色映射(5ns) + 输出寄存器(1ns) = 11ns
裕量 = 40 - 11 = 29ns,非常充足
更高分辨率的挑战:
1024×768 @60Hz需要65MHz像素时钟
每像素时间 = 15.4ns,关键路径11ns → 裕量仅4.4ns
需要优化:流水线化颜色映射,使用更快的BRAM
// 帧率控制器 - 自适应帧率
module frame_rate_ctrl #(
parameter TARGET_FPS = 60
)(
input wire clk,
input wire rst_n,
input wire ca_done, // CA一步完成
output wire display_en, // 显示使能
output wire ca_step, // CA步进信号
output wire [31:0] actual_fps // 实际帧率
);
localparam CLK_PER_FRAME = 100_000_000 / TARGET_FPS;
reg [31:0] frame_timer;
reg [31:0] fps_counter;
reg [31:0] fps_timer;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
frame_timer <= 32'd0;
fps_counter <= 32'd0;
fps_timer <= 32'd0;
end else begin
frame_timer <= frame_timer + 32'd1;
fps_timer <= fps_timer + 32'd1;
if (frame_timer >= CLK_PER_FRAME) begin
frame_timer <= 32'd0;
fps_counter <= fps_counter + 32'd1;
end
if (fps_timer >= 100_000_000) begin
fps_timer <= 32'd0;
end
end
end
assign display_en = (frame_timer < CLK_PER_FRAME);
assign ca_step = ca_done;
assign actual_fps = fps_counter;
endmodule
双缓冲vs三缓冲:
双缓冲:CA写Bank A,显示读Bank B。每帧交换。问题:如果CA写完一帧时显示还在读同一帧,需要等待。
三缓冲:CA写Bank C,等待显示读完Bank A后,Bank A变为新的写入目标。避免了等待,但多用了50%存储。
推荐:对于60Hz显示,双缓冲通常够用(CA计算远快于显示刷新)。对于高分辨率+慢CA,用三缓冲。
元胞自动机课程 · 从Conway到Langton到Lattice Gas