一、前言
针对牟新刚编著的《基于FPGA的数字图像处理原理及应用》中第五章系统仿真中关于视频捕获模拟的例子进行补充和仿真验证,简言之,吊书袋子。
2020-02-27 21:09:05
二、视频捕获原理
假定输入视频分辨率为640*512*24Bit RGB数据,传输数据位宽为8位,扫描频率为60Hz,那么每一帧的像素数为pixel_total = 3*640*512*60。设h_total = 2000,
v_total = 600;那么1s之内的数据总量为pixel_total = 60 * h_total * v_total = 72,000,000,即像素时钟最少需要72MHz。根据需求,需将三个RGB通道分别解出,也
就是捕获后的数据位宽为24Bit,则很容易地计算出本地时钟所需的最小频率:CLKlocal ≥ CLKpixel /3 = 24MHz。因此,选择25MHz作为本地处理逻辑的时钟。
本地时钟不宜选得过大,否则,会造成带宽匹配困难:可能需要一块很大的缓存来进行带宽匹配。一般情况下,输出带宽略大于输入带宽即可。如下图所示为视
频捕获电路示意图;
确定了本地时钟之后,接下来便是如何捕获的问题。首先必须进行跨时钟域转换,即将输入视频同步到本地时钟域。其次由于输入带宽略小于输出带宽,如何进行输入/
输出带宽的匹配也是需要首要考虑的问题。输入是8位的像素数据,需要将8位的像素数据转换为3个同时输出的RGB通道,位宽转换电路也是必不可少的。
1.位宽转换电路
位宽转换电路相对比较简单,我们的目的是将8位的DV数据转换为24位的RGB数据,因此需要将输入数据缓存两拍与当前数据合并即可。
2.跨时钟域转换电路
我们通常会用一个异步fifo来完成异步时钟转换。异步fifo就是读写时钟分开的fifo。
3.带宽匹配
根据我们的设计目标,本地所能提供的最大带宽要大于输入视频流的带宽,这时就必须进行带宽匹配。异步fifo是解决带宽匹配问题的关键部件,如何控制此fifo的读写时机
是匹配的核心问题:不能在fifo空时去读fifo,也不能在fifo满时写数据。
由于本地带宽略大,本地逻辑负责从异步fifo中读取数据,因此我们很容易地知道,读速度要大于写速度,从而读操作必然要延迟于写操作,这样不至于出现“读空”。同时,
读时机也不能太迟,因为“写满”也是我们所不能接受的。
我们不会在fifo中只要一有数据就去读,这样会出现数据断流。实际上,合理的设计是每次读取一整行像素数据后停止,再等待一段固定间隔后读取下一行。这样可以保证
输出数据为连续的像素流和行消隐,同时可以对图像进行行计数,保证我们可以有效地掌握视频流。
这个“固定的间隔”的设计非常重要,它保证了整个输出视频流的连续性。如果这个间隔很小,读取速率频繁,那么行消隐时间就比较窄,可能出现的问题是“读空”。如果间隔
设计比较大,那么行消隐时间就比较长,这个情况下,增加fifo的深度有可能避免“写满”的问题,但是过长的消隐时间可能会“侵占”下一帧的处理时间,并且极大地浪费了带宽,因
此不合适。
输入带宽、输出带宽、trig_value与图像分辨率等决定了fifo深度。实际工作时,可以根据需要来进行测试,获得最好的匹配。
4.命令行缓存与读取电路
对输出图像进行行计数,有效图像结束后的那一行记为命令行,将此命令行写入一个同步fifo,等待外部读出请求读出即可。需要注意的是,必须在下一帧的命令行来之前读走,在此之前需要对这个fifo进行复位操作。
三、代码注释与解析
代码由三部分组成,视频流生成image_src.v,视频捕获video_cap.v及仿真测试文件video_cap_tb.v。书中例程是对24位的RGB图像进行采集,因此上一章节中的image_src.v中的视频参数要稍做调整。video_cap.v中的视频缓存img_fifo及cmd_buf分别位异步fifo和同步fifo。但是书中给出的却是两种不同平台(cmd_buf为Xilinx平台下的同步FIFO,img_fifo为Altera平台下的异步fifo)下的模块例化示例,实际过程中都采用统一平台下的fifo就可以。
(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/video_cap/sim/lena_rgb_3.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,0); //查找当前需要读取的文件位置 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)video_cap.v代码如下,对书中的例程代码进行了补充。
1 //2020-02-17 2 //Huang.Wei 3 `timescale 1ns/1ns 4 5 module video_cap( 6 reset_l, //异步复位信号 7 DVD, //输入视频流 8 DVSYN, //输入场同步信号 9 DHSYN, //输入行同步 10 DVCLK, //输入DV时钟 11 cap_dat, //输出RGB通道像素流,24位 12 cap_dvalid, //输出数据有效 13 cap_vsync, //输出场同步 14 cap_clk, //本地逻辑时钟 15 img_en, 16 cmd_rdy, //命令行准备好,代表可以读取 17 cmd_rdat, //命令行数据输出 18 cmd_rdreq //命令行读取请求 19 ); 20 21 parameter TRIG_VALUE = 250; //读触发值,也即行消隐时间 22 parameter IW = 640; //图像宽度 23 parameter IH = 512; //图像高度 24 25 parameter DW_DVD = 8; //输入像素宽度 26 parameter DVD_CHN = 3; //输入像素通道: RGB 3通道 27 parameter DW_LOCAL = 24; //本地捕获的数据宽度24位 28 parameter DW_CMD = 24; //命令行数据宽度 29 parameter VSYNC_WIDTH = 100; //9 //场同步宽度,9个时钟 30 31 parameter CMD_FIFO_DEPTH = 1024; //行缓存位宽 32 parameter CMD_FIFO_DW_DEPTH = 10; 33 parameter IMG_FIFO_DEPTH = 512; //异步fifo深度,选512 34 parameter IMG_FIFO_DW_DEPTH = 9; 35 36 //Port Declared 37 input reset_l; 38 input [DW_DVD-1:0] DVD; 39 input DVSYN; 40 input DHSYN; 41 input DVCLK; 42 43 output reg [DW_LOCAL-1:0] cap_dat; 44 output reg cap_dvalid; 45 output cap_vsync; 46 input cap_clk; 47 output img_en; 48 49 output reg cmd_rdy; 50 output [DW_CMD-1:0] cmd_rdat; 51 input cmd_rdreq; 52 53 //首先完成数据位宽转换 54 wire pixel_clk; 55 reg [1:0] vc_reset; 56 reg dv_enable; 57 reg [9:0] count_lines; 58 reg cmd_en; 59 reg cmd_wrreq; 60 reg cmd_wrreq_r; 61 reg rst_cmd_fifo; 62 wire [DW_CMD-1:0] cmd_din; 63 reg [DW_CMD-1:0] cmd_dat; 64 65 assign pixel_clk = DVCLK; 66 67 always@(posedge pixel_clk or negedge reset_l)begin 68 if(((~(reset_l))) == 1'b1) 69 begin 70 vc_reset <= 2'b00; 71 dv_enable <= 1'b0; 72 end 73 else 74 begin 75 dv_enable <= #1 1'b1; 76 if((~(DVSYN)) == 1'b1 & dv_enable == 1'b1) 77 vc_reset <= #1 ({vc_reset[0],1'b1}); 78 end 79 end 80 81 reg [DW_DVD-1:0] vd_r[0:DVD_CHN-1]; 82 reg [DVD_CHN*DW_DVD-1:0] data_merge; 83 84 reg vsync; 85 reg [DVD_CHN:0] hsync_r; 86 reg mux; 87 reg mux_r; 88 89 //缓存场同步和行同步信号 90 always@(posedge pixel_clk or negedge reset_l)begin 91 if(((~(reset_l))) == 1'b1) 92 begin 93 vsync <= 1'b0; 94 hsync_r <= {DVD_CHN+1{1'b0}}; 95 end 96 else 97 begin 98 vsync <= #1 DVSYN; 99 hsync_r <= #1 {hsync_r[DVD_CHN-1:0],DHSYN}; 100 end 101 end 102 103 //像素通道计算,指示当前像素属于RGB那个通道 104 reg [DVD_CHN:0] pixel_cnt; 105 106 always@(posedge pixel_clk or negedge reset_l)begin 107 if(((~(reset_l))) == 1'b1) 108 begin 109 pixel_cnt <= {DVD_CHN+1{1'b1}}; 110 end 111 else 112 begin 113 if(hsync_r[1] == 1'b0) 114 pixel_cnt <= #1 {DVD_CHN+1{1'b1}}; 115 else 116 if(pixel_cnt == DVD_CHN -1) 117 pixel_cnt <= #1 {DVD_CHN+1{1'b0}}; 118 else 119 pixel_cnt <= #1 pixel_cnt + 1'b1; 120 end 121 end 122 123 integer i; 124 integer j; 125 126 //缓存输入DV,获得3个RGB通道值 127 128 always@(posedge pixel_clk or negedge reset_l)begin 129 if(((~(reset_l)))==1'b1) 130 for(i=0;i<DVD_CHN;i=i+1) 131 vd_r[i] <= {DW_DVD{1'b0}}; 132 else 133 begin 134 vd_r[0] <= #1 DVD; 135 for(j=1;j<DVD_CHN;j=j+1) 136 vd_r[j] <= vd_r[j-1]; 137 end 138 end 139 140 141 //RGB 合并有效信号 142 wire mux_valid; 143 144 always@(posedge pixel_clk or negedge reset_l)begin 145 if(((~(reset_l))) == 1'b1) 146 mux <= 1'b0; 147 else begin 148 if(hsync_r[DVD_CHN-2] == 1'b0) 149 mux <= #1 1'b1; 150 else 151 if(mux_valid == 1'b1) 152 mux <= #1 1'b1; 153 else 154 mux <= #1 1'b0; 155 end 156 end 157 158 always@(posedge pixel_clk) 159 mux_r <= mux; 160 161 162 wire [DVD_CHN*DW_DVD-1:0] dvd_temp; 163 wire mux_1st; 164 165 assign mux_1st = (~hsync_r[DVD_CHN]) & (hsync_r[DVD_CHN-1]); 166 167 //一个颜色通道 168 generate 169 if(DVD_CHN == 1) 170 begin: xhdl1 171 assign mux_valid = hsync_r[0]; 172 assign dvd_temp = vd_r[0]; 173 end 174 endgenerate 175 176 177 //两个颜色通道 178 generate 179 if(DVD_CHN == 2) 180 begin: xhdl2 181 assign mux_valid = mux_1st | (pixel_cnt == DVD_CHN - 1); 182 assign dvd_temp = {vd_r[0],vd_r[1]}; 183 end 184 endgenerate 185 186 //三个颜色通道,将三路RBG数据合并到dvd_temp信号中 187 generate 188 if(DVD_CHN == 3) 189 begin: xhdl3 190 assign mux_valid = mux_1st | (pixel_cnt == 0); 191 assign dvd_temp = {vd_r[0],vd_r[1],vd_r[2]}; 192 end 193 endgenerate 194 195 //四个颜色通道 196 generate 197 if(DVD_CHN == 4) 198 begin: xhdl4 199 assign mux_valid = mux_1st | (pixel_cnt == 1); 200 assign dvd_temp = {vd_r[0],vd_r[1],vd_r[2],vd_r[3]}; 201 end 202 endgenerate 203 204 //将合并后的数据存入寄存器 205 always@(posedge pixel_clk or negedge reset_l)begin 206 if(((~(reset_l))) == 1'b1) 207 data_merge <= {DVD_CHN*DW_DVD{1'b0}}; 208 else 209 begin 210 if(hsync_r[DVD_CHN] == 1'b1 & mux == 1'b1) 211 data_merge <= #1 dvd_temp; 212 end 213 end 214 215 //将合并后的数据打入异步fifo 216 wire [DW_DVD*DVD_CHN-1:0] fifo_din; 217 wire [DW_DVD*DVD_CHN-1:0] fifo_dout; 218 219 wire [IMG_FIFO_DW_DEPTH-1:0] rdusedw; 220 reg [9:0] trig_cnt; 221 wire fifo_empty; 222 reg fifo_wrreq; 223 reg fifo_wrreq_r; 224 //wire fifo_wrreq; 225 226 //assign fifo_wrreq = mux & hsync_r[DVD_CHN]; 227 228 reg fifo_rdreq; 229 reg fifo_rdreq_r1; 230 reg rst_fifo; 231 232 //实例化异步fifo 233 cross_clock_fifo img_fifo( 234 .data(fifo_din), 235 .rdclk(cap_clk), 236 .rdreq(fifo_rdreq), 237 .wrclk(pixel_clk), 238 .wrreq(fifo_wrreq), 239 .q(fifo_dout), 240 .rdempty(fifo_empty), 241 .rdusedw(rdusedw), 242 .aclr(rst_fifo) 243 ); 244 245 /* 246 defparam img_fifo.DW = DW_DVD*DVD_CHN; 247 defparam img_fifo.DEPTH = IMG_FIFO_DEPTH; 248 defparam img_fifo.DW_DEPTH = IMG_FIFO_DW_DEPTH; 249 */ 250 251 assign fifo_din = data_merge; 252 253 254 //RGB合并时写入fifo 255 always@(posedge pixel_clk or negedge reset_l)begin 256 if(reset_l == 1'b0)begin 257 fifo_wrreq <= #1 1'b0; 258 fifo_wrreq_r <= #1 1'b0; 259 end 260 else begin 261 fifo_wrreq <= hsync_r[DVD_CHN] & mux_r; 262 fifo_wrreq_r <= fifo_wrreq; 263 end 264 end 265 266 //fifo中数据大于触发值时开始读,读完一行停止 267 always@(posedge cap_clk or negedge reset_l)begin 268 if(reset_l == 1'b0) 269 fifo_rdreq <= #1 1'b0; 270 else 271 begin 272 if((rdusedw >= TRIG_VALUE) & (fifo_empty == 1'b0)) 273 fifo_rdreq <= #1 1'b1; 274 else if(trig_cnt == (IW - 1)) 275 fifo_rdreq <= #1 1'b0; 276 end 277 end 278 279 //读计数 280 always@(posedge cap_clk or negedge reset_l)begin 281 if(reset_l == 1'b0) 282 trig_cnt <= #1 {10{1'b0}}; 283 else 284 begin 285 if(fifo_rdreq == 1'b0) 286 trig_cnt <= #1 {10{1'b0}}; 287 else 288 if(trig_cnt == (IW - 1)) 289 trig_cnt <= #1 {10{1'b0}}; 290 else 291 trig_cnt <= #1 trig_cnt + 10'b0000000001; 292 end 293 end 294 295 wire [DW_LOCAL-1:0] img_din; 296 297 assign img_din = ((cmd_en == 1'b0)) ? fifo_dout[DW_LOCAL-1:0] : {DW_LOCAL{1'b0}}; 298 299 assign cmd_din = ((cmd_en == 1'b1)) ? fifo_dout[DW_CMD-1:0] : {DW_CMD{1'b0}}; 300 301 //生成场同步信号、数据有效信号及像素数据输出 302 reg vsync_async; 303 reg vsync_async_r1; 304 reg [VSYNC_WIDTH:0] vsync_async_r; 305 reg cap_vsync_tmp; 306 307 always@(posedge cap_clk or negedge reset_l)begin 308 if(reset_l == 1'b0) 309 begin 310 vsync_async <= #1 1'b0; 311 vsync_async_r1 <= #1 1'b0; 312 vsync_async_r <= {VSYNC_WIDTH+1{1'b0}}; 313 cap_vsync_tmp <= #1 1'b0; 314 end 315 else 316 begin 317 vsync_async <= #1 (~vsync); 318 vsync_async_r1 <= #1 vsync_async; 319 vsync_async_r <= {vsync_async_r[VSYNC_WIDTH-1:0], vsync_async_r1}; 320 if(vsync_async_r[1] == 1'b1 & vsync_async_r[0] == 1'b0) 321 cap_vsync_tmp <= #1 1'b1; 322 else if(vsync_async_r[VSYNC_WIDTH] == 1'b0 & vsync_async_r[0] == 1'b0) 323 cap_vsync_tmp <= #1 1'b0; 324 end 325 end 326 327 assign cap_vsync = cap_vsync_tmp; 328 329 always@(posedge cap_clk or negedge reset_l)begin 330 if(reset_l==1'b0) 331 begin 332 cap_dat <= #1 {DW_LOCAL{1'b0}}; 333 fifo_rdreq_r1 <= #1 1'b0; 334 cap_dvalid <= #1 1'b0; 335 cmd_dat <= #1 {DW_CMD{1'b0}}; 336 cmd_wrreq <= #1 1'b0; 337 cmd_wrreq_r <= #1 1'b0; 338 end 339 else 340 begin 341 cap_dat <= #1 img_din; 342 fifo_rdreq_r1 <= #1 fifo_rdreq; 343 cap_dvalid <= #1 fifo_rdreq_r1 & (~(cmd_en)); 344 cmd_dat <= #1 cmd_din; 345 cmd_wrreq <= #1 fifo_rdreq_r1 & cmd_en; 346 cmd_wrreq_r <= cmd_wrreq; 347 end 348 end 349 350 //frame count and img_en signal 351 reg [1:0] fr_cnt; 352 reg img_out_en; 353 354 always@(posedge cap_clk)begin 355 if(vc_reset[1] == 1'b0) 356 begin 357 img_out_en <= 1'b0; 358 fr_cnt <= {2{1'b0}}; 359 end 360 else 361 begin 362 if(vsync_async_r1 == 1'b0 & vsync_async == 1'b1) 363 begin 364 fr_cnt <= fr_cnt + 2'b01; 365 if(fr_cnt == 2'b11) 366 img_out_en <= 1'b1; 367 end 368 end 369 end 370 371 assign img_en = img_out_en; 372 373 374 //行计数,确定cmd数据到来时刻 375 always@(posedge cap_clk)begin 376 if(cap_vsync_tmp == 1'b1) 377 begin 378 count_lines <= {10{1'b0}}; 379 cmd_en <= 1'b0; 380 cmd_rdy <= 1'b0; 381 end 382 begin 383 if(fifo_rdreq_r1 == 1'b1 & fifo_rdreq == 1'b0) 384 count_lines <= #1 count_lines + 4'h1; 385 if(count_lines == (IH - 2)) 386 rst_cmd_fifo <= 1'b1; 387 else 388 rst_cmd_fifo <= 1'b0; 389 if(count_lines >= IH) 390 cmd_en <= #1 1'b1; 391 if(cmd_wrreq_r == 1'b1 & cmd_wrreq == 1'b0) 392 cmd_rdy <= 1'b1; 393 if(cmd_wrreq_r == 1'b1 & cmd_wrreq == 1'b0) 394 rst_fifo <= 1'b1; 395 else 396 rst_fifo <= 1'b0; 397 end 398 end 399 400 401 //Instance a line buffer to store the cmd line 402 line_buffer_new 403 cmd_buf( 404 .aclr(rst_cmd_fifo), 405 .clock(cap_clk), 406 .data(cmd_dat), 407 .rdreq(cmd_rdreq), 408 .wrreq(cmd_wrreq), 409 .empty(), 410 .full(), 411 .q(cmd_rdat), 412 .usedw() 413 ); 414 415 /* 416 defparam cmd_buf.DW = DW_CMD; 417 defparam cmd_buf.DEPTH = CMD_FIFO_DEPTH; 418 defparam cmd_buf.DW_DEPTH = CMD_FIFO_DW_DEPTH; 419 defparam cmd_buf.IW = IW; 420 */ 421 endmodule
(3)仿真测试代码video_cap_tb.v如下,值得注意的是$fdisplay是自带换行的,因此代码中不需要添加换行符,加入后仿真结果不对。
1 `timescale 1ns/1ns 2 3 module video_cap_tb; 4 5 6 /*image para*/ 7 parameter iw = 640; //image width 8 parameter ih = 512; //image height 9 parameter trig_value = 400; //250 10 11 /*video parameter*/ 12 parameter h_total = 2000; 13 parameter v_total = 600; 14 parameter sync_b = 5; 15 parameter sync_e = 55; 16 parameter vld_b = 65; 17 18 parameter clk_freq = 72; 19 20 /*data width*/ 21 parameter dvd_dw = 8; //image source data width 22 parameter dvd_chn = 3; //channel of the dvd data: when 3 it's rgb or 4:4:YCbCr 23 parameter local_dw = dvd_dw * dvd_chn; //local algorithem process data width 24 parameter cmd_dw = dvd_dw * dvd_chn; //local algorithem process data width 25 26 /*test module enable*/ 27 parameter cap_en = 1; 28 29 /*signal group*/ 30 reg clk = 1'b0; 31 reg reset_l; 32 reg [3:0] src_sel; 33 34 35 /*input dv group*/ 36 wire dv_clk; 37 wire dvsyn; 38 wire dhsyn; 39 wire [dvd_dw-1:0] dvd; 40 41 /*dvd source data generated for simulation*/ 42 image_src //#(iw*dvd_chn, ih+1, dvd_dw, h_total, v_total, sync_b, sync_e, vld_b) 43 img_src_ins( 44 .clk(clk), 45 .reset_l(reset_l), 46 .src_sel(src_sel), 47 .test_data(dvd), 48 .test_dvalid(dhsyn), 49 .test_vsync(dvsyn), 50 .clk_out(dv_clk) 51 ); 52 53 defparam img_src_ins.iw = iw*dvd_chn; 54 defparam img_src_ins.ih = ih + 1; 55 defparam img_src_ins.dw = dvd_dw; 56 defparam img_src_ins.h_total = h_total; 57 defparam img_src_ins.v_total = v_total; 58 defparam img_src_ins.sync_b = sync_b; 59 defparam img_src_ins.sync_e = sync_e; 60 defparam img_src_ins.vld_b = vld_b; 61 62 /*data captured*/ 63 wire cap_dvalid; 64 wire [local_dw-1:0] cap_data; 65 wire cap_vsync; 66 67 /*command line*/ 68 wire cmd_rdy; 69 wire [cmd_dw-1:0] cmd_rdat; 70 reg cmd_rdreq; 71 72 /*local clk: also clk of all local modules*/ 73 reg cap_clk = 1'b0; 74 75 /*img enable*/ 76 wire img_en; 77 78 /*video capture: capture image src and transfer it into local timing*/ 79 video_cap //#(trig_value,iw,ih) /*default trig value 250*/ 80 video_new( 81 .reset_l(reset_l), 82 .DVD(dvd), 83 .DVSYN(dvsyn), 84 .DHSYN(dhsyn), 85 .DVCLK(dv_clk), 86 .cap_dat(cap_data), 87 .cap_dvalid(cap_dvalid), 88 .cap_vsync(cap_vsync), 89 .cap_clk(cap_clk), 90 .img_en(img_en), 91 .cmd_rdy(cmd_rdy), 92 .cmd_rdat(cmd_rdat), 93 .cmd_rdreq(cmd_rdreq) 94 ); 95 96 defparam video_new.DW_DVD = dvd_dw; 97 defparam video_new.DW_LOCAL = local_dw; 98 defparam video_new.DW_CMD = cmd_dw; 99 defparam video_new.DVD_CHN = dvd_chn; 100 defparam video_new.TRIG_VALUE = trig_value; 101 defparam video_new.IW = iw; 102 defparam video_new.IH = ih; 103 104 initial 105 begin: init 106 reset_l <= 1'b1; 107 src_sel <= 4'b0000; 108 #(100); //reset the system 109 reset_l <= 1'b0; 110 #(100); 111 reset_l <= 1'b1; 112 end 113 114 //dv_clk generate 115 always@(reset_l or clk)begin 116 if((~(reset_l)) == 1'b1) 117 clk <= 1'b0; 118 else 119 begin 120 if(clk_freq == 48) //48MHz 121 clk <= #10417 (~(clk)); 122 123 else if(clk_freq == 51.84) //51.84MHz 124 clk <= #9645 (~(clk)); 125 126 else if(clk_freq == 72) //72MHz 127 clk <= #6944 (~(clk)); 128 end 129 end 130 131 //cap_clk generate: 25MHz 132 always@(reset_l or cap_clk)begin 133 if((~(reset_l)) == 1'b1) 134 cap_clk <= 1'b0; 135 else 136 cap_clk <= #20000 (~(cap_clk)); 137 end 138 139 generate 140 if(cap_en != 0) begin :capture_operation 141 integer fp_cap, cnt_cap=0; 142 143 always@(posedge cap_clk or posedge cap_vsync)begin 144 if(((~(cap_vsync))) == 1'b0) 145 cnt_cap = 0; 146 else 147 begin 148 if(cap_dvalid == 1'b1) 149 begin 150 fp_cap = $fopen("E:/Modelsim/video_cap/sim/lena_rgb_4.txt","r+"); 151 $fseek(fp_cap,cnt_cap,0); 152 if(local_dw==24) 153 begin 154 $fdisplay(fp_cap,"%06X",cap_data); 155 $fclose(fp_cap); 156 cnt_cap<=cnt_cap+8; 157 end 158 else 159 begin 160 $fdisplay(fp_cap,"%02x ",cap_data); 161 $fclose(fp_cap); 162 cnt_cap<=cnt_cap+4; 163 end 164 end 165 end 166 end 167 end 168 endgenerate 169 170 endmodule 171
(4)rgb2txt.m用于生成测试文件的matlab程序,生成640*512*24Bits的RGB图像数据,多加一行命令行数据;
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 %% 保存三通道RGB数据 25 I_resize_2 = imresize(I_rgb,[512 640],'nearest');% [rows cols] 26 [m,n,c] = size(I_resize_2); 27 fid2 = fopen('lena_rgb_3.txt','wt'); 28 for i=1:m 29 for j = 1:n 30 for k = 1:c 31 fprintf(fid2,'%02X ',I_resize_2(i,j,k)); 32 end 33 end 34 %fprintf(fid2,' '); 35 end 36 % 37 for a = 1:n 38 for b = 1:3 39 fprintf(fid2,'%02X ',rem(a,255)); 40 end 41 end 42 fclose(fid2); 43 44 45 fid = fclose(fid); 46 I_data = load('./lena_640x512.txt');
(5)用于Modelsim仿真的.do文件的编写,video_cap.do代码如下:
1 #切换至工程目录 2 cd E:/Modelsim/video_cap/sim 3 4 #打开工程 5 project open E:/Modelsim/video_cap/sim/video_cap 6 7 #添加指定设计文件 8 project addfile E:/Modelsim/video_cap/sim/video_cap_tb.v 9 project addfile E:/Modelsim/video_cap/src/cross_clock_fifo.v 10 #project addfile E:/Modelsim/video_cap/src/cross_clock_fifo.qip 11 project addfile E:/Modelsim/video_cap/src/image_src.v 12 project addfile E:/Modelsim/video_cap/src/line_buffer_new.v 13 #project addfile E:/Modelsim/video_cap/src/line_buffer_new.qip 14 project addfile E:/Modelsim/video_cap/src/video_cap.v 15 16 #编译工程内所有文件 17 project compileall 18 19 #仿真Work库下面的video_cap_tb实例,同时调用altera_lib库,不进行任何优化 20 vsim -t 1ps -novopt -L altera_lib work.video_cap_tb 21 22 #添加输入信号InputData 23 add wave -divider InputData 24 25 add wave -position insertpoint 26 sim:/video_cap_tb/video_new/DVCLK 27 28 add wave -position insertpoint 29 sim:/video_cap_tb/video_new/DVSYN 30 31 add wave -position insertpoint 32 sim:/video_cap_tb/video_new/DHSYN 33 34 add wave -radix hex -position insertpoint 35 sim:/video_cap_tb/video_new/DVD 36 37 #添加RGB合并信号,RGB_Merge 38 add wave -divider RGB_Merge 39 add wave -radix hex -position insertpoint 40 sim:/video_cap_tb/video_new/vd_r 41 42 add wave -radix hex -position insertpoint 43 sim:/video_cap_tb/video_new/data_merge 44 45 add wave -radix hex -position insertpoint 46 sim:/video_cap_tb/video_new/vsync 47 48 add wave -radix binary -position insertpoint 49 sim:/video_cap_tb/video_new/hsync_r 50 51 add wave -radix binary -position insertpoint 52 sim:/video_cap_tb/video_new/mux 53 54 add wave -radix binary -position insertpoint 55 sim:/video_cap_tb/video_new/mux_r 56 57 add wave -radix unsigned -position insertpoint 58 sim:/video_cap_tb/video_new/pixel_cnt 59 60 add wave -radix unsigned -position insertpoint 61 sim:/video_cap_tb/video_new/i 62 63 add wave -radix unsigned -position insertpoint 64 sim:/video_cap_tb/video_new/j 65 66 add wave -radix binary -position insertpoint 67 sim:/video_cap_tb/video_new/mux_valid 68 69 add wave -radix hex -position insertpoint 70 sim:/video_cap_tb/video_new/dvd_temp 71 72 add wave -radix binary -position insertpoint 73 sim:/video_cap_tb/video_new/mux_1st 74 75 #添加Image_FIFO信号 76 add wave -divider Image_FIFO 77 78 add wave -radix hex -position insertpoint 79 sim:/video_cap_tb/video_new/fifo_din 80 81 add wave -radix hex -position insertpoint 82 sim:/video_cap_tb/video_new/fifo_dout 83 84 add wave -radix unsigned -position insertpoint 85 sim:/video_cap_tb/video_new/rdusedw 86 87 add wave -radix unsigned -position insertpoint 88 sim:/video_cap_tb/video_new/trig_cnt 89 90 add wave -radix binary -position insertpoint 91 sim:/video_cap_tb/video_new/fifo_empty 92 93 add wave -radix binary -position insertpoint 94 sim:/video_cap_tb/video_new/fifo_wrreq 95 96 add wave -radix binary -position insertpoint 97 sim:/video_cap_tb/video_new/fifo_wrreq_r 98 99 add wave -radix binary -position insertpoint 100 sim:/video_cap_tb/video_new/fifo_rdreq 101 102 add wave -radix binary -position insertpoint 103 sim:/video_cap_tb/video_new/fifo_rdreq_r1 104 105 add wave -radix binary -position insertpoint 106 sim:/video_cap_tb/video_new/rst_fifo 107 108 #添加CMD_BUF信号 109 add wave -divider CMD_BUF 110 111 add wave -radix unsigned -position insertpoint 112 sim:/video_cap_tb/video_new/count_lines 113 114 add wave -radix binary -position insertpoint 115 sim:/video_cap_tb/video_new/cmd_en 116 117 add wave -radix binary -position insertpoint 118 sim:/video_cap_tb/video_new/cmd_wrreq 119 120 add wave -radix binary -position insertpoint 121 sim:/video_cap_tb/video_new/cmd_wrreq_r 122 123 add wave -radix binary -position insertpoint 124 sim:/video_cap_tb/video_new/rst_cmd_fifo 125 126 add wave -radix hex -position insertpoint 127 sim:/video_cap_tb/video_new/cmd_din 128 129 add wave -radix hex -position insertpoint 130 sim:/video_cap_tb/video_new/cmd_dat 131 132 add wave -radix binary -position insertpoint 133 sim:/video_cap_tb/video_new/cmd_rdy 134 135 add wave -radix binary -position insertpoint 136 sim:/video_cap_tb/video_new/cmd_rdreq 137 138 add wave -radix hex -position insertpoint 139 sim:/video_cap_tb/video_new/cmd_rdat 140 141 #添加输出信号OutputData 142 add wave -divider OutputData 143 144 add wave -radix hex -position insertpoint 145 sim:/video_cap_tb/video_new/img_din 146 147 add wave -radix binary -position insertpoint 148 sim:/video_cap_tb/video_new/vsync_async 149 150 add wave -radix binary -position insertpoint 151 sim:/video_cap_tb/video_new/vsync_async_r1 152 153 add wave -radix binary -position insertpoint 154 sim:/video_cap_tb/video_new/vsync_async_r 155 156 add wave -radix binary -position insertpoint 157 sim:/video_cap_tb/video_new/cap_vsync_tmp 158 159 add wave -radix unsigned -position insertpoint 160 sim:/video_cap_tb/video_new/fr_cnt 161 162 add wave -radix binary -position insertpoint 163 sim:/video_cap_tb/video_new/img_out_en 164 165 add wave -radix binary -position insertpoint 166 sim:/video_cap_tb/video_new/cap_clk 167 168 add wave -radix binary -position insertpoint 169 sim:/video_cap_tb/video_new/cap_vsync 170 171 add wave -radix binary -position insertpoint 172 sim:/video_cap_tb/video_new/cap_dvalid 173 174 add wave -radix hex -position insertpoint 175 sim:/video_cap_tb/video_new/cap_dat 176 177 #复位 178 restart 179 180 #取消警告 181 set StdArithNoWarnings 1 182 183 #开始 184 run 17 ms
四、仿真结果
(1)整体输入/输出仿真结果,输入/输出的频率都为60Hz。
(2)输出cap_dat延时输入一段时间,由于本地带宽略大,行消隐时间也长一些。
(3)输出成功地将输入连续3个像素合并为24位位宽数据输出。
(4)cmd_rdreq置1后,cmd数据输出。