5.1.4 其他加速仿真的方法
针对图像处理的仿真速度会非常之慢,特别是针对一些比较复杂的算法或是较大分辨率的图像。
除了采用SE版本的Modelsim和使用do文件,常用的提高仿真速度的方法还有以下几种。
(1)降分辨率处理:通常情况下,可以对低分辨率的图像进行仿真验证,这也是为什么一定要将图像高度和图像宽度参数化的原因。
(2)提高timescale:尽可能地提高timescale来提高仿真速度。
(3)降频仿真:在不影响处理结果的情况下降频处理。
5.2 视频图像处理仿真测试系统
5.2.1 仿真测试系统框架
一个完善稳定的仿真测试系统对于图像处理算法的设计至关重要。这个测试系统至少要完成以下功能:
(1)模拟可配置的视频流(单帧的视频即为一幅图像)。
(2)模拟视频捕获,生成视频数据。
(3)测试系统与testbench及视频流的数据共享。
(4)可视化的图像及视频操作。
(5)对FPGA的处理结果进行验证。
通常情况下会在VC平台或是Matlab平台下开发图像处理算法。这些软件也提供了良好的gui界面及图像处理可视化工具包。因此,可以考虑将测试系统搭建在上述两个平台的基础上,在本书中所采用的平台是基于VS2015的MFC平台。
视频流的模拟是测试平台的差异性部分:这是由于视频流的时序是由具体的视频输入接口所决定的,不同的接口输入就决定了我们要采取不同的视频流时序模拟。但是,在某些情况下,视频流的时序非常复杂,例如USB接口和千兆以太网接口,而这些接口在实际的相机中是十分常见的。我们没必要花精力在这个时序模拟上面,实际上,这基本上是不可能完成的任务(这些工作往往会交给专用芯片来做)。
在测试系统中往往会屏蔽这个差异,也就是会采用统一的视频模拟时序。第二个考虑是测试系统,即VS平台与FPGA模拟视频源及处理结果的数据共享问题,这个自然使用文件来实现共享。Verilog也提供了基本的文件读写操作,实际情况下,会采用文本形式的文件,这样也可以直接打开文件查看结果。
可视化的图像及视频操作用微软的MFC平台实现是很简单的,而对于处理结果的验证则会负责把用C++的软件算法处理结果与FPGA的处理结果进行逐像素比对,并输出比对结果。整个测试系统的框架如图5-20所示。
5.2.2 视频时序模拟
1)基本视频时序
从根本上来说,视频信号其实就是一个二维数组,数组的内容是按照一定的帧率刷新的亮度和色度数据。一个典型的CMOS传感器输出视频流(并行LVDS接口)时序如图5-21所示。
以下为上述时序的信号说明:
(1)HSYNC。HSYNC是水平同步信号,它规定了一个视频帧中每一行视频的有效起始点。HSYNC为高电平说明数据线上的像素数据为有效的显示像素。
(2)FILED。FIELD是场信号,常用各行扫描,用来表示当前的场是奇数场还是偶数场。
(3)VSYNC。VSYNC是垂直同步信号,它规定了一幅新视频图像(也称为一帧图像)的起始点。
(4)CLK。CLK信号即像素时钟,数据在时钟上升沿进行采样。
(5)DATA。有效像素数据,仅在行同步有效时才有效。上述时序模型的建立主要是基于传统的CRT模拟显示器。模拟电视信号会控制一个电子束按照从上到下、从左到右的顺序点亮屏幕上的
荧光粉,HSYNC的低电平信号也称为水平消隐时间,即电子枪从屏幕的右侧返回到下一行的左侧所用的时间。同理,VSYNC的低电平时间即电子枪从屏幕的右下角返回到左上角所用的时间,称为垂直消隐时间(图中时序图并未按实际比例绘制)。
以下为上述视频时序的主要参数。
(1)视频分辨率。视频分辨率又可以分为水平分辨率和垂直分辨率。水平分辨率即图像中每一行的像素个数,记为IW,垂直分辨率即每一帧图像中有多少个水平的像素行,记为IH。
(2)扫描频率。扫描频率也即每一场的频率,常见的扫描频率有30Hz,50Hz,60Hz,100Hz,120Hz等。一般情况下,扫描频率在50Hz/60Hz以上时,人眼才不会感觉到闪烁。扫描频率主要由图像分辨和像素时钟决定,其余的时序参数影响不大。
(3)其他时序参数
① Horizontal Total:总行时间,记为h_total。
② Vertical Total:总列时间,记为v_total。
③ 行消隐脉冲:即各行之间的低电平时间sync_h。
④ 场同步脉冲:即每帧场同步信号的低电平时间,记为sync_v。
⑤ 场前肩:从场计数开始到场同步的时间torch_f。
⑥ 场后肩:从场同步结束到行同步开始的时间torch_b。
上述几个时序参数定义如图5-22所示。
在进行时序模拟的时序上述几个参数最好是可手动配置的,我们注意到sync_h记为h_total-IW,因此,这个参数是冗余参数。同时,我们在模拟时往往会忽略场信号。
2.Verilog示例代码
以下的代码实现了上述视频流的模拟时序(像素数据从文件中得到)。
/*
*********************************************************
**********
** Input file : None
** Component name : image_src.v
** Author : ZhengXiaoliang
** Company : WHUT
** Description : to simulate dvd stream
*********************************************************
**********
*/
'timescale 1 ns/1 ns
'define SEEK_SET 0
'define SEEK_CUR 1
'define SEEK_END 2
module image_src(
reset_l, //全局复位
clk, //同步时钟
src_sel, //数据源通道选择
test_vsync, //场同步输出
test_dvalid, //像素有效输出
test_data, //像素数据输出
clk_out //像素时钟输出
);
parameter iw = 640; //默认视频宽度parameter ih = 512; //默认视频高度
parameter dw = 8; //默认像素数据位宽
parameter h_total = 1440; //行总数
parameter v_total = 600; //垂直总数
parameter sync_b = 5; //场前肩
parameter sync_e = 55; //场同步脉冲
parameter vld_b = 65; //场后肩
input reset_l,clk;
input [3:0]src_sel; //to select the input file
output test_vsync,test_dvalid,clk_out;
output [dw-1:0]test_data;
reg [dw-1:0]test_data_reg;
reg test_vsync_temp;
reg test_dvalid_tmp;
reg [1:0]test_dvalid_r;
reg [10:0] h_cnt;
reg [10:0] v_cnt;
integer fp_r;
integer cnt=0;
//输出像素时钟
assign clk_out = clk; //output the dv clk
//输出像素数据
assign test_data = test_data_reg; //test data output
//当行同步有效时,从文件读取像素数据输出到数据线上
always @(posedge clk or posedge test_vsync_temp )
if (((~(test_vsync_temp))) == 1'b0) //场同步清零文件指针
cnt<=0;//clear file pointer when a new frame comeselse
begin
if (test_dvalid_tmp == 1'b1) //行同步有效,说明当前时钟
数据有效
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,cnt,0);//查找当前需要读取的文件位置
$fscanf(fp_r,"%02x\n",test_data_reg);
//将数据按指定格式读入test_data_reg寄存
器
cnt <= cnt + 4 ; //移动文件指针到下一个数据
$fclose(fp_r); //关闭文件
//$display("%02x",test_data_reg); //for debug use
end
end
//水平计数器,每来一个时钟就+1 加到h_total置零重新计数
always @(posedge clk or posedge reset_l)
if (((~(reset_l))) == 1'b1)h_cnt <= #1 {11{1'b0}};
else
begin
if (h_cnt == ((h_total - 1)))
h_cnt <= #1 {11{1'b0}};
else
h_cnt <= #1 h_cnt + 11'b00000000001;
end
//垂直计数器:水平计数器计满后+1 ,计满后清零
always @(posedge clk or posedge reset_l)
if (((~(reset_l))) == 1'b1)
v_cnt <= #1 {11{1'b0}};
else
begin
if (h_cnt == ((h_total - 1)))
begin
if (v_cnt == ((v_total - 1)))
v_cnt <= #1 {11{1'b0}};
else
v_cnt <= #1 v_cnt + 11'b00000000001;
end
end
//场同步信号生成
always @(posedge clk or posedge reset_l)
if (((~(reset_l))) == 1'b1)
test_vsync_temp <= #1 1'b1;
elsebegin
if (v_cnt >= sync_b & v_cnt <= sync_e)
test_vsync_temp <= #1 1'b1;
else
test_vsync_temp <= #1 1'b0;
end
assign test_vsync = (~test_vsync_temp);
//水平同步信号生成
always @(posedge clk or posedge reset_l)
if (((~(reset_l))) == 1'b1)
test_dvalid_tmp <= #1 1'b0;
else
begin
if (v_cnt >= vld_b & v_cnt < ((vld_b + ih)))
begin
if (h_cnt == 10'b0000000000)
test_dvalid_tmp <= #1 1'b1;
else if (h_cnt == iw)
test_dvalid_tmp <= #1 1'b0;
end
else
test_dvalid_tmp <= #1 1'b0;
end
//水平同步信号输出
assign test_dvalid = test_dvalid_tmp;
always @(posedge clk or posedge reset_l)
if (((~(reset_l))) == 1'b1)test_dvalid_r <= #1 2'b00;
else
test_dvalid_r <= #1
({test_dvalid_r[0],test_dvalid_tmp});
endmodule
3.视频流仿真
以一个分辨率为640×512、扫描频率为60Hz、数据位宽为8位的模拟时序来对上述设计的模块进行仿真。主要时序参数如下:
parameter iw = 640; //图像宽度
parameter ih = 512; //图像高度
parameter dvd_dw = 8 ; //数据位宽
//视频时序参数
parameter h_total = 1440;
parameter v_total = 600;
parameter sync_b = 5;
parameter sync_e = 55;
parameter vld_b = 65;
首要的问题是我们需计算出视频像素时钟。根据我们的时序参数,单场所需时钟总数为
pixel_total=h_total*v_total=86400
扫描频率为60Hz,也即1s之内的数据总量为
pixel_total=60*h_total*v_total=51 840 000
也就是我们需要的时钟频率最少为5184000,也就是51.84MHz才能完成分辨率为640×512,扫描频率为60Hz的视频扫描。
为此,我们设计testbench如下:
reg clk;//时钟产生:≈ 51.84MHz
always @(reset_l or clk)
begin
if ((~(reset_l)) == 1'b1)
clk <= 1'b0;
else
clk <= #9645 (~(clk));
end
wire dv_clk;
wire dvsyn;
wire dhsyn;
wire [dvd_dw-1:0]dvd;
/*根据时序参数例化一个视频源 */
image_src #
(iw,ih,dvd_dw,h_total,v_total,sync_b,sync_e,vld_b)
img_src_ins(
.clk(clk),
.reset_l(reset_l),
.src_sel(src_sel),
.test_data(dvd),
.test_dvalid(dhsyn),
.test_vsync(dvsyn),
.clk_out(dv_clk)
);
仿真结果如图5-23和图5-24所示。