文章目录
- 一、前言
- 二、视频时序控制原理
- 三、Verilog实现
- 3.1 代码
- 3.2 仿真以及分析
一、前言
VTC(Video Timing Controller)是一种用于产生视频时序的控制器,在图像领域经常会输出各种分辨率和帧率的视频格式。因此为了方便,设置一个VTC控制模块,只需要在顶层修改一下需要输出的视频格式,就能自动的产生对应的时序,这样能方便处理一点。Xilinx Vivado 也有专门用于生成视频时序的 IP,叫做 Video Timing Controller 核,官方的说明在 PG016,本文使用Verilog实现VTC的基本功能。
二、视频时序控制原理
在《VGA接口时序以及FPGA实现》文章中,我们实现了1080P的时序并且成功的在显示器上显示出来了。VTC原理和VGA一样,只需要在给正确的时钟频率产生出正确的VS和HS信号即可。视频显示原理如下:
显示器显示图像主要由行同步信号和场同步信号构成:
- 每一行又分为:行同步信号H_SYNC;行后沿信号H_BACK_PORCH;行数据有效信号H_ACTIVE;行前沿信号 H_FRONT_PORCH。
- 每一列同样分为:场同步信号V_SYNC;场后沿信号V_BACK_PORCH;场数据有效信号V_ACTIVE;场前沿信号V_FRONT_PORCH。
- 只有在行数据有效信号H_ACTIVE和场数据有效信号V_ACTIVE都有效时,才输出de信号。
常见分辨率视频所对应的各信号长度如下:
显示模式 | 时钟/Mhz | 行同步hsync | 行后沿 | 行有效 | 行前沿 | 行总共 | 场同步vsync | 场后沿 | 场有效 | 场前沿 | 场总共 |
---|---|---|---|---|---|---|---|---|---|---|---|
640×480@60Hz | 25.2 | 96 | 48 | 640 | 16 | 800 | 2 | 33 | 480 | 10 | 525 |
800×600@60Hz | 40 | 128 | 88 | 800 | 40 | 1056 | 4 | 23 | 600 | 1 | 628 |
1024×768@60Hz | 65 | 136 | 160 | 1024 | 24 | 1344 | 6 | 29 | 768 | 3 | 806 |
1280×720@60Hz | 74.25 | 40 | 220 | 1280 | 110 | 1650 | 5 | 20 | 720 | 5 | 750 |
1280×1024@60Hz | 108 | 112 | 248 | 1280 | 48 | 1688 | 3 | 38 | 1024 | 1 | 1066 |
1920×1080@60Hz | 148.5 | 44 | 148 | 1920 | 88 | 2200 | 5 | 36 | 1080 | 4 | 1125 |
3840×2160@60Hz | 594 | 88 | 296 | 3840 | 196 | 4400 | 10 | 72 | 2160 | 8 | 2250 |
三、Verilog实现
3.1 代码
`timescale 1ns/1ns
module vtc#
(
parameter H_SYNC = 96, //行同步信号
parameter H_BACK_PORCH = 48, //行后沿
parameter H_ACTIVE = 640, //行有效数据
parameter H_FRONT_PORCH = 16, //行前沿
parameter V_SYNC = 2, //场同步信号
parameter V_BACK_PORCH = 33, //场后沿
parameter V_ACTIVE = 480, //场有效信号
parameter V_FRONT_PORCH = 10 //场前沿
)
(
input clk ,
input rst_n ,
output reg vs , //输出vs信号
output reg hs , //输出hs信号
output reg de , //输出de信号
output reg h_end //每一行结束信号
);
localparam hcnt_max = H_SYNC + H_BACK_PORCH + H_ACTIVE + H_FRONT_PORCH; //行扫描最大计数值
localparam vcnt_max = V_SYNC + V_BACK_PORCH + V_ACTIVE + V_FRONT_PORCH; //列扫描最大计数值
reg [12:0] h_cnt = 13'd0 ; //行扫描器,对列计数
reg [11:0] v_cnt = 12'd0 ; //列扫描器,对行计数
reg [3:0] rst_cnt = 4'd0 ; //复位计数器
reg rst_sync = 1'b0 ; //同步复位信号
reg data_valid = 1'b0 ; //输出数据有效信号
reg data_valid_reg1 =1'b0 ;
reg data_valid_reg2 =1'b0 ;
reg hs_valid = 1'b0 ; //行同步有效信号
reg hs_valid_reg1 =1'b0 ;
reg hs_valid_reg2 =1'b0 ;
reg vs_valid = 1'b0 ; //行同步有效信号
reg vs_valid_reg1 =1'b0 ;
reg vs_valid_reg2 =1'b0 ;
//异步复位,同步释放
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
rst_cnt <= 3'd0;
else if (rst_cnt[3] == 1'b1)
rst_cnt <= rst_cnt;
else
rst_cnt <= rst_cnt + 1'b1;
end
always @(posedge clk) begin
if(rst_cnt[3] == 1'b0)
rst_sync <= 1'b0;
else
rst_sync <= 1'b1;
end
//水平方向扫描,对列计数
always @(posedge clk) begin
if(rst_sync == 1'b0)
h_cnt <= 13'd0;
else if(h_cnt == hcnt_max - 1)
h_cnt <= 13'd0;
else
h_cnt <= h_cnt + 1'b1;
end
//垂直方向,对行计数
always @(posedge clk) begin
if(rst_sync == 1'b0)
v_cnt <= 12'd0;
else if((v_cnt == vcnt_max - 1) && (h_cnt == hcnt_max - 1))
v_cnt <= 12'd0;
else if(h_cnt == hcnt_max - 1)
v_cnt <= v_cnt + 1'b1;
else
v_cnt <= v_cnt;
end
//行同步信号
always @(posedge clk) begin
if(rst_sync == 1'b0)
hs_valid <= 1'b0;
else if(h_cnt <= H_SYNC - 1)
hs_valid <= 1'b1;
else
hs_valid <= 1'b0;
end
always @(posedge clk) begin
if(rst_sync == 1'b0)begin
hs_valid_reg1 <= 1'b0;
hs_valid_reg2 <= 1'b0;
end
else begin
hs_valid_reg1 <= hs_valid;
hs_valid_reg2 <= hs_valid_reg1;
end
end
//场同步信号
always @(posedge clk) begin
if(rst_sync == 1'b0)
vs_valid <= 1'b0;
else if(v_cnt <= V_SYNC - 1)
vs_valid <= 1'b1;
else
vs_valid <= 1'b0;
end
always @(posedge clk) begin
if(rst_sync == 1'b0)begin
vs_valid_reg1 <= 1'b0;
vs_valid_reg2 <= 1'b0;
end
else begin
vs_valid_reg1 <= vs_valid;
vs_valid_reg2 <= vs_valid_reg1;
end
end
//数据有效信号
always @(posedge clk) begin
if(rst_sync == 1'b0)
data_valid <= 1'b0;
else if(((h_cnt >= H_SYNC + H_BACK_PORCH)&&(h_cnt < hcnt_max - H_FRONT_PORCH))&&((v_cnt >= V_SYNC + V_BACK_PORCH)&&(v_cnt < vcnt_max - V_FRONT_PORCH)))
data_valid <= 1'b1;
else
data_valid <= 1'b0;
end
always @(posedge clk) begin
if(rst_sync == 1'b0)begin
data_valid_reg1 <= 1'b0;
data_valid_reg2 <= 1'b0;
end
else begin
data_valid_reg1 <= data_valid;
data_valid_reg2 <= data_valid_reg1;
end
end
//输出信号
always @(posedge clk) begin
if(rst_sync == 1'b0)
h_end <= 1'b0;
else if(~data_valid_reg1 & data_valid_reg2)
h_end <= 1'b1;
else
h_end <= 1'b0;
end
always @(posedge clk) begin
if(rst_sync == 1'b0)begin
de <= 1'b0;
vs <= 1'b0;
hs <= 1'b0;
end
else begin
de <= data_valid_reg2;
vs <= vs_valid_reg2;
hs <= hs_valid_reg2;
end
end
endmodule
3.2 仿真以及分析
tb文件编写
`timescale 1ns / 1ps
module tb_vtc();
reg clk ;
reg rst_n ;
wire de ;
wire hs ;
wire vs ;
wire h_end ;
initial begin
clk = 0;
rst_n = 0;
#200;
rst_n = 1;
end
always # 10 clk = ~clk;
vtc#(
.H_SYNC ( 96 ),
.H_BACK_PORCH ( 48 ),
.H_ACTIVE ( 640 ),
.H_FRONT_PORCH ( 16 ),
.V_SYNC ( 2 ),
.V_BACK_PORCH ( 33 ),
.V_ACTIVE ( 480 ),
.V_FRONT_PORCH ( 10 )
)u_vtc(
.clk ( clk ),
.rst_n ( rst_n ),
.vs ( vs ),
.hs ( hs ),
.de ( de ),
.h_end ( h_end )
);
endmodule
仿真结果如下:
由上图可以看出,V_SYNC信号共占2个v_cnt周期。
由上图可以看出,H_SYNC信号共有1920ns,1920/20=96个h_cnt周期。
由上图可以看出,de信号共有12800ns,12800/20=640个h_cnt周期。综上,本次仿真符合640*480@60hz的视频时序