一、前言
最近在看牟新刚写的《基于FPGA的数字图像处理原理及应用》,书中关于FPGA数字图像处理的原理的原理写的非常透彻,在网上寻找了很久都没有找到完整的源代码工程,因此尝试自己做了补充/仿真,并与书中结果进行了比对。2020-02-16 15:40:21
二、视频时序模拟
如图一所示,为参考书中的代码及说明整理出来的时序图,其中仿真视频图像宽度(IW=640),高度(IH=512),sync_b为场前肩参数,sync_e为场同步脉冲参数,vld为场后肩参数。从时序图中可以看出dvsync标记了一副新图像的起始点,书中并没有标记图像传输结束到下一帧图像开始(图像的场前肩)需要多少时间,通过v_total可以计算出有23行图像的时间(不知道实际场景下这个参数是否也是按照v_total来换算)。同理书中也没有标注sync_h(行消隐脉冲的时间),通过计算可知为(h_total-640)=800个像素时钟。
三、代码注解与分析
(1)image_src.v文件分析,该部分代码主要用于实现图一中视频流的模拟时序包括行/场信号的生成及像素数据的生成。
1 /* 2 *********************************************************************************************************** 3 ** Input file: None 4 ** Component name: image_src.v 5 ** Author: zhengXiaoliang 6 ** Company: WHUT 7 ** Description: to simulate dvd stream 8 *********************************************************************************************************** 9 */ 10 11 `timescale 1ns/1ns 12 13 `define SEEK_SET 0 14 `define SEEK_CUR 1 15 `define SEEK_END 2 16 17 module image_src( 18 reset_l, //全局复位 19 clk, //同步时钟 20 src_sel, //数据源通道选择 21 test_vsync, //场同步输出 22 test_dvalid, //像素有效输出 23 test_data, //像素数据输出 24 clk_out //像素时钟输出 25 ); 26 27 parameter iw = 640; //默认视频宽度 28 parameter ih = 512; //默认视频高度 29 parameter dw = 8; //默认像素数据位宽 30 31 parameter h_total = 1440; //行总数 32 parameter v_total = 600; //垂直总数 33 34 parameter sync_b = 5; //场前肩 35 parameter sync_e = 55; //场同步脉冲 36 parameter vld_b = 65; //场后肩 37 38 //port decleared 39 input reset_l,clk; 40 input [3:0] src_sel; //to select the input file 41 output test_vsync, test_dvalid,clk_out; 42 output [dw-1:0] test_data; 43 44 45 //variable decleared 46 reg [dw-1:0] test_data_reg; 47 reg test_vsync_temp; 48 reg test_dvalid_tmp; 49 reg [1:0] test_dvalid_r; 50 51 reg [10:0] h_cnt; 52 reg [10:0] v_cnt; 53 54 integer fp_r; 55 integer cnt = 0; 56 57 //输出像素时钟 58 assign clk_out = clk; //output the dv clk 59 60 //输出像素数据 61 assign test_data = test_data_reg; //test data output 62 63 //当行同步有效时,从文件读取像素数据输出到数据线上 64 always@(posedge clk or posedge test_vsync_temp)begin 65 if(((~(test_vsync_temp))) == 1'b0) //场同步清零文件指针 66 cnt <= 0; //clear file pointer when a new frame comes 67 else begin 68 if(test_dvalid_tmp == 1'b1)begin //行同步有效,说明当前时钟数据有效 69 case(src_sel) //选择不同的数据源 70 4'b0000: fp_r = $fopen("E:/Modelsim/image_src/txt_source/test_src3.txt","r"); 71 4'b0001: fp_r = $fopen("txt_source/test_scr1.txt","r"); 72 4'b0010: fp_r = $fopen("txt_source/test_scr2.txt","r"); 73 4'b0011: fp_r = $fopen("txt_source/test_scr3.txt","r"); 74 4'b0100: fp_r = $fopen("txt_source/test_scr4.txt","r"); 75 4'b0101: fp_r = $fopen("txt_source/test_scr5.txt","r"); 76 4'b0110: fp_r = $fopen("txt_source/test_scr6.txt","r"); 77 4'b0111: fp_r = $fopen("txt_source/test_scr7.txt","r"); 78 4'b1000: fp_r = $fopen("txt_source/test_scr8.txt","r"); 79 4'b1001: fp_r = $fopen("txt_source/test_scr9.txt","r"); 80 4'b1010: fp_r = $fopen("txt_source/test_scr10.txt","r"); 81 4'b1011: fp_r = $fopen("txt_source/test_scr11.txt","r"); 82 4'b1100: fp_r = $fopen("txt_source/test_scr12.txt","r"); 83 4'b1101: fp_r = $fopen("txt_source/test_scr13.txt","r"); 84 4'b1110: fp_r = $fopen("txt_source/test_scr14.txt","r"); 85 4'b1111: fp_r = $fopen("txt_source/test_scr15.txt","r"); 86 default: fp_r = $fopen("txt_source/test_src3.txt","r"); 87 endcase 88 89 $fseek(fp_r,cnt,`SEEK_SET); //查找当前需要读取的文件位置 90 $fscanf(fp_r,"%02x ",test_data_reg); //将数据按指定格式读入test_data_reg寄存器 91 92 cnt <= cnt + 4; //移动文件指针到下一个数据 93 $fclose(fp_r); //关闭文件 94 $display("h_cnt = %d,v_cnt = %d, pixdata = %d",h_cnt,v_cnt,test_data_reg); //for debug use 95 end 96 end 97 end 98 99 //水平计数器,每来一个时钟就+1,加到h_total置零重新计数 100 always@(posedge clk or negedge reset_l)begin 101 if(((~(reset_l))) == 1'b1) 102 h_cnt <= #1 {11{1'b0}}; 103 else begin 104 if(h_cnt == ((h_total -1))) 105 h_cnt <= #1 {11{1'b0}}; 106 else 107 h_cnt <= #1 h_cnt + 11'b00000000001; 108 end 109 end 110 111 //垂直计数器:水平计数器计满后+1,计满后清零 112 always@(posedge clk or negedge reset_l)begin 113 if(((~(reset_l))) == 1'b1) 114 v_cnt <= #1 {11{1'b0}}; 115 else begin 116 if(h_cnt == ((h_total - 1)))begin 117 if(v_cnt == ((v_total - 1))) 118 v_cnt <= #1 {11{1'b0}}; 119 else 120 v_cnt <= #1 v_cnt + 11'b00000000001; 121 end 122 end 123 end 124 125 //场同步信号生成 126 always@(posedge clk or negedge reset_l)begin 127 if(((~(reset_l))) == 1'b1) 128 test_vsync_temp <= #1 1'b1; 129 else begin 130 if(v_cnt >= sync_b & v_cnt <= sync_e) 131 test_vsync_temp <= #1 1'b1; 132 else 133 test_vsync_temp <= #1 1'b0; 134 end 135 end 136 137 assign test_vsync = (~test_vsync_temp); 138 139 //水平同步信号生成 140 always@(posedge clk or negedge reset_l)begin 141 if(((~(reset_l))) == 1'b1) 142 test_dvalid_tmp <= #1 1'b0; 143 else begin 144 if(v_cnt >= vld_b & v_cnt < ((vld_b + ih)))begin 145 if(h_cnt == 10'b0000000000) 146 test_dvalid_tmp <= #1 1'b1; 147 else if(h_cnt == iw) 148 test_dvalid_tmp <= #1 1'b0; 149 end 150 else 151 test_dvalid_tmp <= #1 1'b0; 152 end 153 end 154 155 //水平同步信号输出 156 assign test_dvalid = test_dvalid_tmp; 157 158 always@(posedge clk or negedge reset_l)begin 159 if(((~(reset_l))) == 1'b1) 160 test_dvalid_r <= #1 2'b00; 161 else 162 test_dvalid_r <= #1 ({test_dvalid_r[0],test_dvalid_tmp}); 163 end 164 165 endmodule
(2)视频流仿真代码image_src_tb.v的补充编写。
1 `timescale 1ns/1ns 2 3 module image_src_tb; 4 5 parameter iw = 640; //图像宽度 6 parameter ih = 512; //图像高度 7 parameter dvd_dw = 8; //数据位宽 8 9 //视频时序参数 10 parameter h_total = 1440; 11 parameter v_total = 600; 12 parameter sync_b = 5; 13 parameter sync_e = 55; 14 parameter vld_b = 65; 15 16 reg clk; 17 reg reset_l; 18 19 wire dv_clk; 20 wire dvsyn; 21 wire dhsyn; 22 wire [dvd_dw-1:0] dvd; 23 24 /*根据时序参数例化一个视频源*/ 25 image_src #(iw,ih,dvd_dw,h_total,v_total,sync_b,sync_e,vld_b) 26 image_src_ins( 27 .clk(clk), 28 .reset_l(reset_l), 29 .src_sel(4'b0000), 30 .test_data(dvd), 31 .test_dvalid(dhsyn), 32 .test_vsync(dvsyn), 33 .clk_out(dv_clk) 34 ); 35 36 initial begin 37 clk = 0; 38 reset_l = 1; 39 #100; 40 reset_l = 0; 41 #100; 42 reset_l = 1; 43 end 44 45 //时钟产生:≈51.84MHz 46 always@(reset_l or clk)begin 47 if((~(reset_l)) == 1'b1) 48 clk <= 1'b0; 49 else 50 clk <= #9645 (~(clk)); 51 end 52 53 endmodule
(3)视频码流文件的生成,由于书中并没有描述如何生成视频码流的文件,因此用Maltab编写了一个rgb2txt文件,生成了一份码流文件经过测试可用。
1 %将256位的BMP灰度图像128*128大小生成TXT文档; 2 clc 3 close all 4 5 I_rgb = imread('lena_512x512.jpg'); 6 subplot(1,3,1),imshow(I_rgb),title('lena-rgb') 7 8 I_gray = rgb2gray(I_rgb); 9 subplot(1,3,2),imshow(I_gray),title('lena-gray') 10 11 %% 改变图像尺寸 12 I_resize = imresize(I_gray,[640 512],'nearest');%nearest(默认值) 最近邻插值 ‘bilinear’双线性插值 ‘bicubic’ 双三次插值 13 % I = imresize(I_gray,0.25); 14 % subplot(1,3,3),imshow(I),title('lena-qtr') 15 16 fid = fopen('./lena_640x512_hex.txt','wt'); 17 for i = 1:size(I_resize,1) 18 for j = 1:size(I_resize,2) 19 fprintf(fid,'%2x ',I_resize(i,j));%每个数据之间用空格分开 20 end 21 %fprintf(fid,' '); 22 end 23 24 fid = fclose(fid); 25 I_data = load('./lena_640x512.txt');
(4)用于Modelsim仿真的.do文件的编写,image_src.do代码如下:
1 #切换至工程目录 2 cd E:/Modelsim/image_src 3 4 #打开工程 5 project open E:/Modelsim/image_src 6 7 #编译相关文件(一般情况下为频繁改动的文件) 8 vlog E:/Modelsim/image_src/image_src.v 9 vlog E:/Modelsim/image_src/image_src_tb.v 10 11 #开始仿真 12 vsim -novopt -L altera_lib work.image_src_tb 13 14 #添加顶层所有的信号 15 add wave * 16 17 #取消警告 18 set StdArithNoWarnings 1 19 20 #开始运行 21 run -all
四、仿真结果
如图二所示,为测试仿真结果与书中仿真结果图像一致。