首页 > 技术文章 > 基于Modelsim的视频捕获模拟仿真

huangwei0521 2020-02-27 21:04 原文

一、前言

  针对牟新刚编著的《基于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\n",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\n",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\n',I_resize(i,j));%每个数据之间用空格分开
20     end
21     %fprintf(fid,'\n');
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\n',I_resize_2(i,j,k));
32         end
33     end
34     %fprintf(fid2,'\n');
35 end
36 % 
37 for a = 1:n
38     for b = 1:3
39        fprintf(fid2,'%02X\n',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数据输出。

 

 

 

  

 

推荐阅读