FPGA视频仿真
视频图像处理仿真测试系统
最近看《基于FPGA的数字图像处理原理及应用》看到了第五章,本章内容主要讲如何搭建一个视频图像处理仿真测试系统,我参考了书上的内容,自己设计了一个基于Qt creator的仿真测试系统。
1.仿真测试系统框架
仿真测试系统所包含的功能:
(1)模拟可配置的视频流(单帧的视频即为一副图像)
(2)模拟视频捕获,生成视频数据
(3)测试系统与testbench及视频流的数据共享
(4)可视化的图像及视频操作
(5)对FPGA的处理结果进行验证
仿真测试系统结构如下图所示:
上面是从书上的截图,这里我将Qt测试程序代替MFC测试程序。
仿真测试系统由Qt平台和Modelsim共同搭建完成。模拟测试的完整过程如下:
1.Qt将一张图片所有的RGB颜色信息存储在文本中。
2.modelsim模拟视频流传输,读取(1)中图像信息生成视频流,并模拟捕获视频流,并将这些图像信息写入另一个文本中。
3.Qt将模拟捕获的图像文本信息读取并生成图像。
通过以上可知,Qt平台与FPGA模拟视频流及处理结果通过文本实现数据共享。
2 视频时序模拟
2.1 基本视频时序
图2-1是一个典型的CMOS视频输出时序图:
图2-1
下面是上面时序各个信号的说明:
(1)HSYNC:行同步信号
(2)FILED:场信号,表示当前的场是奇数场还是偶数场
(3)VSYNC:帧同步信号
(4)CLK:像素时钟即PCLK
(5)DATA:有效像素数据,仅在行同步有效时才有效
(6)h_totol:一行的总像素个数
(7)v_totol:一副图像总的行数
(8)sync_h:行消隐
(9)sync_v:场同步脉冲
(10)torch_f:场前肩
(11)torch_b:场后肩
2.2 Verilog示例代码
参考书上的代码,我自己实现了上述视频流的模拟时序(像素数据从文本中得到,一个像素RGB各占一行)
这里自定义模拟视频流的参数信息如下:
图像有效宽度iw = 640;
图像有效高度ih = 480;
一行总像素个数h_total = 2000;
总行数v_total = 600 ;
场前肩torch_f = 5;
场同步脉冲sync_v = 20;
场后肩torch_b = 20;
/************************************************************************
* Author : Ye Qiang
* Email : 1247836708@qq.com
* Create time : 2019-03-22 11:12
* Last modified : 2019-03-25 15:51
* Filename : image_src.v
* Description :
* *********************************************************************/
module image_src(
input clk ,//系统时钟
input rst_n ,//系统复位
input [ 3: 0] src_sel ,//图像选择
output wire pclk ,//像素时钟
output wire Vsync ,//帧同步
output reg Hsync ,//行同步
output wire [ 7: 0] img_data //8位图像数据
);
//======================================================================\
//************** Define Parameter and Internal Signals *****************
//======================================================================/
//图像大小为640x480,RGB格式
parameter iw = 640*3 ;//1920
parameter ih = 480 ;
parameter h_total = 2000 ;
parameter v_total = 600 ;
parameter torch_f = 5 ;//场前肩
parameter sync_v = 20 ;//场同步脉冲
parameter torch_b = 20 ;//场后肩
parameter sync_b = torch_f ;
parameter sync_e = sync_b+sync_v ;
parameter vld_b = sync_e+torch_b ;
parameter sync_h = h_total-iw ;
reg [10: 0] h_cnt ;
wire add_h_cnt ;
wire end_h_cnt ;
reg [ 9: 0] v_cnt ;
wire add_v_cnt ;
wire end_v_cnt ;
reg Hsync_temp ;
reg Vsync_temp ;
wire h_flag ;
integer p_cnt = 0 ;
integer fp_r ;
reg [ 7: 0] test_data_reg ;
//======================================================================\
//**************************** Main Code *******************************
//======================================================================/
assign pclk = clk ;
//h_cnt
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
h_cnt <= 0;
end
else if(add_h_cnt)begin
if(end_h_cnt)
h_cnt <= 0;
else
h_cnt <= h_cnt + 1;
end
end
assign add_h_cnt = 1'b1;
assign end_h_cnt = add_h_cnt && h_cnt == h_total-1;
//v_cnt 行计数
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
v_cnt <= 0;
end
else if(add_v_cnt)begin
if(end_v_cnt)
v_cnt <= 0;
else
v_cnt <= v_cnt + 1;
end
end
assign add_v_cnt = end_h_cnt;
assign end_v_cnt = add_v_cnt && v_cnt == v_total;
//Vsync_temp
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
Vsync_temp <= 1'b1;
end
else if(v_cnt >= sync_b && v_cnt <= sync_e )begin
Vsync_temp <= 1'b0;
end
else begin
Vsync_temp <= 1'b1;
end
end
assign Vsync = Vsync_temp;
//h_flag
assign h_flag = (v_cnt >= vld_b) && (v_cnt < (vld_b+ih)) ;
//Hsync_temp
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
Hsync_temp <= 1'b0;
end
else if(h_flag)begin
if(h_cnt == 0)begin
Hsync_temp <= 1'b1;
end
else if(h_cnt == iw)
Hsync_temp <= 1'b0;
end
else begin
Hsync_temp <= 1'b0;
end
end
//Hsync
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
Hsync <= 1'b0;
end
else begin
Hsync <= Hsync_temp;
end
end
//当行同步有效时,从文件读取像素数据输出到数据线上
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
p_cnt <= 0;
test_data_reg <= {8{1'b0}};
end
else if(Vsync_temp == 1'b0)begin
p_cnt <= 0;
end
else begin
if(Hsync_temp)begin
case(src_sel)
4'b0000 :fp_r = $fopen("txt_source/test_src0.txt", "r");
4'b0001 :fp_r = $fopen("txt_source/test_src1.txt", "r");
4'b0010 :fp_r = $fopen("txt_source/test_src2.txt", "r");
4'b0011 :fp_r = $fopen("txt_source/test_src3.txt", "r");
4'b0100 :fp_r = $fopen("txt_source/test_src4.txt", "r");
4'b0101 :fp_r = $fopen("txt_source/test_src5.txt", "r");
4'b0110 :fp_r = $fopen("txt_source/test_src6.txt", "r");
4'b0111 :fp_r = $fopen("txt_source/test_src7.txt", "r");
4'b1000 :fp_r = $fopen("txt_source/test_src8.txt", "r");
4'b1001 :fp_r = $fopen("txt_source/test_src9.txt", "r");
4'b1010 :fp_r = $fopen("txt_source/test_src10.txt", "r");
4'b1011 :fp_r = $fopen("txt_source/test_src11.txt", "r");
4'b1100 :fp_r = $fopen("txt_source/test_src12.txt", "r");
4'b1101 :fp_r = $fopen("txt_source/test_src13.txt", "r");
4'b1110 :fp_r = $fopen("txt_source/test_src14.txt", "r");
4'b1111 :fp_r = $fopen("txt_source/test_src15.txt", "r");
default :fp_r = $fopen("txt_source/test_src0.txt", "r" );
endcase
$fseek(fp_r, p_cnt, 0);//查找当前需要读取的文件位置
$fscanf(fp_r, "%02x\n", test_data_reg);//将数据按指定格式读入test_data_reg寄存器
p_cnt <= p_cnt + 4;//移动文件指针到下一个数据
$fclose(fp_r);//关闭文件
//$display("%02x", test_data_reg);
end
end
end
assign img_data = test_data_reg ;
endmodule
3 视频捕获模拟
本模块的目的是对上一节产生的视频信号进行捕获并解析
3.1 完成内容
(1) 位宽转换:将八位视频流数据转换成24位数据。这里通过一个深度为1024的fifo来辅助实现
(2) 生成本地帧同步和行同步信号
(3) 将图像数据写入文本中,以供Qt平台读取显示
3.2 Verilog代码设计
/************************************************************************
* Author : Ye Qiang
* Email : 1247836708@qq.com
* Create time : 2019-03-25 15:55
* Last modified : 2019-03-27 16:58
* Filename : video_cap.v
* Description : video_cap
* *********************************************************************/
module video_cap(
input pclk ,//像素时钟
input rst_n ,//系统复位
input Vsync ,//帧同步
input Hsync ,//行同步
input [ 7: 0] img_data ,//八位图像数据
input cap_clk ,//本地时钟
output wire cap_Vsync ,//本行帧同步
output reg cap_Hsync ,//本地行同步
output wire [23: 0] cap_img_data //捕获的24位图像数据
);
//======================================================================\
//************** Define Parameter and Internal Signals *****************
//======================================================================/
parameter iw = 10'd640 ;
parameter ih = 9'd480 ;
reg [ 2: 0] cap_Vsync_r ;
wire cap_Vsync_pos ;
wire cap_Vsync_neg ;
reg [23: 0] fifo_in_data ;
reg rdreq ;
reg wrreq ;
wire [23: 0] fifo_out_data ;
wire rdempty ;
wire [ 9: 0] rdusedw ;
wire wrfull ;
wire [ 9: 0] wrusedw ;
reg [ 1: 0] pix_cnt ;
wire add_pix_cnt ;
wire end_pix_cnt ;
reg [ 9: 0] rdata_cnt ;
wire add_rdata_cnt ;
wire end_rdata_cnt ;
//======================================================================\
//**************************** Main Code *******************************
//======================================================================/
//bit width conversion 8 to 24
//pix_cnt
always @(posedge pclk or negedge rst_n)begin
if(!rst_n)begin
pix_cnt <= 0;
end
else if(add_pix_cnt)begin
if(end_pix_cnt)
pix_cnt <= 0;
else
pix_cnt <= pix_cnt + 1;
end
end
assign add_pix_cnt = Vsync && Hsync;
assign end_pix_cnt = add_pix_cnt && pix_cnt == 3-1;
//fifo_in_data
always @(posedge pclk or negedge rst_n)begin
if(!rst_n)begin
fifo_in_data <= {24{1'b0}};
end
else if(add_pix_cnt)begin
fifo_in_data <= {fifo_in_data[15:0], img_data};
end
end
//wrreq
always @(posedge pclk or negedge rst_n)begin
if(!rst_n)begin
wrreq <= 1'b0;
end
else if(!wrfull && end_pix_cnt)begin
wrreq <= 1'b1;
end
else begin
wrreq <= 1'b0;
end
end
//rdreq
always @(posedge cap_clk or negedge rst_n)begin
if(!rst_n)begin
rdreq <= 1'b0;
end
else if(wrusedw == iw)begin
rdreq <= 1'b1;
end
else if(end_rdata_cnt)begin
rdreq <= 1'b0;
end
end
//cap_Vsync_r
always @(posedge cap_clk or negedge rst_n)begin
if(!rst_n)begin
cap_Vsync_r <= {3{1'b1}};
end
else begin
cap_Vsync_r <= {cap_Vsync_r[1:0], Vsync};
end
end
assign cap_Vsync = cap_Vsync_r[2] ;
assign cap_Vsync_neg = cap_Vsync_r[2] && ~cap_Vsync_r[1] ;
assign cap_Vsync_pos = ~cap_Vsync_r[2] && cap_Vsync_r[1] ;
//cap_Hsync
always @(posedge cap_clk or negedge rst_n)begin
if(!rst_n)begin
cap_Hsync <= 1'b0;
end
else begin
cap_Hsync <= rdreq;
end
end
//rdata_cnt
always @(posedge cap_clk or negedge rst_n)begin
if(!rst_n)begin
rdata_cnt <= 0;
end
else if(add_rdata_cnt)begin
if(end_rdata_cnt)
rdata_cnt <= 0;
else
rdata_cnt <= rdata_cnt + 1;
end
end
assign add_rdata_cnt = rdreq;
assign end_rdata_cnt = add_rdata_cnt && rdata_cnt == iw-1;
//cap_img_data
assign cap_img_data = fifo_out_data ;
//将图像数据写入文本
integer fp_w ;
integer p_cnt ;
reg [ 3: 0] v_pos_cnt ;
wire add_v_pos_cnt ;
wire end_v_pos_cnt ;
//v_pos_cnt
always @(posedge cap_clk or negedge rst_n)begin
if(!rst_n)begin
v_pos_cnt <= 0;
end
else if(add_v_pos_cnt)begin
if(end_v_pos_cnt)
v_pos_cnt <= 0;
else
v_pos_cnt <= v_pos_cnt + 1;
end
end
assign add_v_pos_cnt = cap_Vsync_pos;
assign end_v_pos_cnt = add_v_pos_cnt && v_pos_cnt == 4-1;
//当行同步有效时,从文件读取像素数据输出到数据线上
always @(posedge cap_clk or negedge rst_n)begin
if(!rst_n)begin
p_cnt <= 0;
end
else if(cap_Vsync_pos)begin
p_cnt <= 0;
case(v_pos_cnt)
4'd0: fp_w = $fopen("txt_result/imgDataOut0.txt", "w");
4'd1: fp_w = $fopen("txt_result/imgDataOut1.txt", "w");
4'd2: fp_w = $fopen("txt_result/imgDataOut2.txt", "w");
4'd3: fp_w = $fopen("txt_result/imgDataOut3.txt", "w");
4'd4: fp_w = $fopen("txt_result/imgDataOut4.txt", "w");
4'd5: fp_w = $fopen("txt_result/imgDataOut5.txt", "w");
4'd6: fp_w = $fopen("txt_result/imgDataOut6.txt", "w");
4'd7: fp_w = $fopen("txt_result/imgDataOut7.txt", "w");
4'd8: fp_w = $fopen("txt_result/imgDataOut8.txt", "w");
4'd9: fp_w = $fopen("txt_result/imgDataOut9.txt", "w");
4'd10: fp_w = $fopen("txt_result/imgDataOut10.txt", "w");
4'd11: fp_w = $fopen("txt_result/imgDataOut11.txt", "w");
4'd12: fp_w = $fopen("txt_result/imgDataOut12.txt", "w");
4'd13: fp_w = $fopen("txt_result/imgDataOut13.txt", "w");
4'd14: fp_w = $fopen("txt_result/imgDataOut14.txt", "w");
4'd15: fp_w = $fopen("txt_result/imgDataOut15.txt", "w");
default:fp_w = $fopen("txt_result/imgDataOut0.txt", "w");
endcase
end
else if(cap_Vsync_neg)begin
$fclose(fp_w);//关闭文件,写完一帧再关闭,否则仿真速度会很慢
p_cnt <= 0;
end
else if(cap_Hsync)begin
$fseek(fp_w, p_cnt, 0);//查找当前需要读取的文件位置
$fwrite(fp_w, "%06x\n", fifo_out_data);//将图像数据按指定格式写入文本中
p_cnt <= p_cnt + 8;//移动文件指针到下一个数据 一个字符占一个字节,回车占两个字符6+2=8
end
end
//例化
myFifo myFifo_inst(
.data(fifo_in_data),
.rdclk(cap_clk),
.rdreq(rdreq),
.wrclk(pclk),
.wrreq(wrreq),
.q(fifo_out_data),
.rdempty(rdempty),
.rdusedw(rdusedw),
.wrfull(wrfull),
.wrusedw(wrusedw)
);
endmodule
4 激励文件
4.1 捕获要求
假定视频分辨率为64048024Bit RGB数据,传输数据位宽为8位,扫描为60Hz,每一帧的有效像素数为:
pixel_total = 3640480*60
本地的图像数据位数为24位,如果我们要完全捕获视频流,我们要求
本地像素时钟频率>模拟像素输出时钟频率/3
这里,自定义模拟像素输出时钟周期为10ns,本地像素时钟周期为24ns
下面是Verilog代码
`timescale 1ns/1ns
module tb_top;
//=====================================================================\
// ********** Define Parameter and Internal Signals *************
//=====================================================================/
reg clk ;
reg rst_n ;
reg [ 3: 0] src_sel ;
wire Vsync ;
wire Hsync ;
wire [ 7: 0] img_data ;
reg cap_clk ;
wire cap_Vsync ;
wire cap_Hsync ;
wire [23: 0] cap_img_data ;
//======================================================================
// *************** Main Code ****************
//======================================================================
always #5 clk = ~clk ;
always #12 cap_clk = ~cap_clk ;
initial begin
clk <= 1'b1;
cap_clk <= 1'b1;
rst_n <= 1'b0;
src_sel <= 4'd0;
#100
rst_n <= 1'b1;
#11000000
src_sel <= 4'd1;
end
//例化
image_src image_src_inst(
.clk (clk ),
.rst_n (rst_n ),
.src_sel (src_sel ),
.pclk (pclk ),
.Vsync (Vsync ),
.Hsync (Hsync ),
.img_data (img_data )
);
video_cap video_cap_inst(
.pclk (pclk ),
.rst_n (rst_n ),
.Vsync (Vsync ),
.Hsync (Hsync ),
.img_data (img_data ),
.cap_clk (cap_clk ),
.cap_Vsync (cap_Vsync ),
.cap_Hsync (cap_Hsync ),
.cap_img_data (cap_img_data )
);
endmodule
5 仿真结果
图5-1是整个系统的仿真情况。图中两根光标所指示的位置是图像的帧同步信号。
图5-1
图5-2是一副图像中的一行数据,两根光标所指示的就是行同步信号。
图5-2
图5-3是将原始图像和经过FPGA处理后的图像经过Qt测试平台显示后的结果
图5-3
下面是系统仿真所有程序源码下载链接,直接用Qt软件打开工程,读懂代码,就可以自己搭建仿真测试系统
源码下载链接:
https://download.csdn.net/download/qq_31348733/11065858