FPGA开发过程中“行为功能仿真”是非常必要的一个过程,如果仿真都没通过,则上板测试必定失败。
FPGA图像处理需要读写大量的图像数据,单看这些图像数据实际是没有规则的,如果直接上板测试,调试起来非常困难,数量量大,ILA只能抓一段时间的数据,不易确定问题原因。
所以FPGA图像处理开发中,创建一个正确的仿真测试激励非常关键,基本上只要满足时序要求,上板就大概率就能过,如果上板遇到问题,还可以拿到这张图片数据,仿真测试一下看看能否复现,如果能复现问题,再通过仿真调试解决这个问题就容易很多了。
今天,我们就来搭建一个图像处理仿真测试工程,实现读写bmp文件的功能。
一、SystemVerilog/Verilog读写文件函数
仿真测试激励(Testbench)中经常需要读写文件,这里介绍一下SystemVerilog/Verilog常用的操作文件的函数,写测试激励推荐使用SystemVerilog。
1、打开文件函数
函数定义:integer fd = $fopen(file_name,type)
函数返回值: fd不等于0,表示文件打开成功;fd等于0,表示文件打开失败。
函数参数:
(1)file_name是一个字符串。
(2)type有如下类型:
“r” or “rb”:只读 或 按二进制文件读取
“w” or “wb”:只写 或 按二进制文件写入
“a” or “ab”:打开文件追加从文件末尾(EOF)写或创建文件写
“r+” or “rb+”:打开文件,可读可写
“w+” or "wb+": 打开或创建文件写
"a+" "a+b" or "ab+" :追加,在文件末尾打开
示例:
integer fd; fd = $fopen("./1280_720.bmp","rb");
2、关闭文件函数
养成一个良好的习惯,打开文件处理完后,记得关闭文件。
函数定义:$fclose(fd)
函数说明:fd就是调用$fopen返回的值。
3、读写文件函数
(1)writemem[b|h]/readmem[b|h]
writemem 表示写,readmem表示读。b 表示读写二进制文件,h表示读写十六进制文件。
所以对应的全名有:writememb/writememh/readmemb/readmemh。
读写文件函数调用形式如下:
(1)$readmemb("<数据文件名>",<存储器名>);
(2)$readmemb("<数据文件名>",<存储器名>,<起始地址>);
(3)$readmemb("<数据文件名>",<存储器名>,<起始地址>,<终止地址>);
其中存储器名就是数组型变量,起始地址和终止地址就是数组型变量的起始范围。
示例:
reg [31:0] mem[63:0] ;
initial begin
//读文件数据
$readmemb("./data.hex", mem);
$display("Read memory1: %h", mem[0]) ;
//写文件数据
$writememb("./data_bak.hex", mem);
end
注意:
readmem[b|h]是将数据放在存储器数组中,所以存储器数组应该定义为reg型,而不是wire。
readmem[b|h]是不能操作二维数组,只能操作一维数组。
(2)$fscanf 和 $fwrite
$fscanf 按照指定格式从文件中读取数据。
函数定义:integer flag=$fscanf(fd,format,args);
函数参数:fd表示文件句柄;format表示数据格式,%d表示十进制整数,%c表示一字节8bit字符,%x表示十六进制整数;args表示存储器数组。
$fwrite 按照指定格式从文件中读取数据。
函数定义:integer flag=$fwrite(fd,format,args);
函数参数定义:与$fscanf一样。
示例:
parameter LEN = 1920;
integer i;
reg [7:0] data[LEN-1:0];
integer fd;
initial begin
//读文件
fd = $fopen("./in.txt","rb");
for( i=0; i<LEN; i=i+1 ) begin
$fscanf(fd, "%c", data[i]);
$display("Read data is: %c", data[i]);
end
$fcolse(fd);
//写文件
fd = $fopen("./out.txt","rb");
for( i=0; i<LEN; i=i+1 ) begin
$fwrite(fd, "%c", data[i]);
end
$fcolse(fd);
end
4、文件定位函数
(1)获取文件位置函数 $ftell
integer pos = $ftell( fd ) ; 返回文件当前位置距离文件首部的偏移量,初始地址为 0,偏移量按照字节为一单位(8bits),配合 $fseek 使用。
(2)重定位函数$fseek
integer code = $fseek(fd, offset, type) ; 设置文件下一个输入或输出的位置
函数参数 :offset 为设置的偏移量,type 为偏移量的参考位置,具体如下:
--- 0: 以文件起始位置为基准
--- 1: 以文件当前位置为基准
--- 2: 以文件末尾为基准
二、BMP文件介绍
BMP(Bitmap)文件格式是一种图像文件格式,与常见的图像格式如 JPEG、PNG 等不同,它属于典型的位图格式。BMP 采用位映射存储格式,除了图像深度可选以外,不使用其他任何压缩。
1、BMP文件头
BMP文件头长度可变,但一般都是 54 字节,其中包括 14 字节的 Bitmap 文件头以及 40 字节的 DIB (Device Independent Bitmap) 数据头,或称位图信息数据头(BItmap Information Header)。
2、视频数据 Raw Bitmap Data
常见的数据格式是24bitRGB,具体到每一个像素是24bit数据,分别是B、G、R的形式排列。
三、图像处理仿真测试工程
1、图像处理仿真测试工程结构
2、参数定义
设定图像文件位置、图像大小、横向消影区、纵向消影区大小。
//`define pix_1920_1080
`define pix_1280_720
`ifdef pix_1920_1080
`define INPUT_FILE "../../../../test_img/in/1920_1080.bmp" //input image
`define IMG_WIDTH 1920 //Image width
`define IMG_HEIGHT 1080 //Image height
`define H_BLANK 720 //横向消影区,仿真可自由设定
`define V_BLANK 45 //纵向消影区,仿真可自由设定
`endif
`ifdef pix_1280_720
`define INPUT_FILE "../../../../test_img/in/1280_720.bmp" //input image
`define IMG_WIDTH 1280 //Image width
`define IMG_HEIGHT 720 //Image height
`define H_BLANK 480 //横向消影区,仿真可自由设定
`define V_BLANK 30 //纵向消影区,仿真可自由设定
`endif
`define OUTPUT_FILE "../../../../test_img/out/result.bmp" //result image
`define SEEK_SET 0
`define SEEK_CUR 1
`define SEEK_END 2
3、读图像文件
读取BMP文件数据,先读取BMP文件头,BMP的数据时按倒序存储,即从下到上,从左到右,读取出来存储时,需要存储到对应的位置。
fdI = $fopen(`INPUT_FILE,"rb");
if (fdI == `NULL) begin
$display("> OPEN FAIL: The file not exist !!!");
end else begin
$display("> OPEN file SUCCESS !");
//读取bmp文件头
ret = $fread(bmp_head_r, fdI, 0, `LEN_HEADER);
//读取图像RGB分量值
//BMP倒序存储数据时,从下到上,从左到右
for(i=`IMG_HEIGHT - 1;i >= 0;i=i-1)
for(j=0;j <`IMG_WIDTH;j=j+1) begin
idx = i*`IMG_WIDTH + j;
imgB_r[idx] = $fgetc(fdI);//b
imgG_r[idx] = $fgetc(fdI);//g
imgR_r[idx] = $fgetc(fdI);//r
end
$display("> Read b,g,r Successful !");
end
4、写图像文件
写图像文件,先写入文件头信息,再按照倒序存储BGR数据,需要移动文件内的偏移量。
//写入文件头
for(i=0;i < `LEN_HEADER;i=i+1) begin
$fwrite(fdO, "%c", bmp_head_r[i]);
end
//移动到图片数据最后一行起始位置
file_end_offset = `IMG_ALL + `LEN_HEADER - `IMG_WIDTH*3;
$fseek(fdO, file_end_offset, `SEEK_SET);
assign {R_o_w, G_o_w, B_o_w} = img_data_o;
always @(posedge clk or posedge reset) begin
if (reset) begin
out_data_cnt_r <= 'b0;
valid_o_r <= 'b0;
end else begin
valid_o_r <= valid_o;
if(valid_o) begin
$fwrite(fdO, "%c", B_o_w);
$fwrite(fdO, "%c", G_o_w);
$fwrite(fdO, "%c", R_o_w);
out_data_cnt_r <= out_data_cnt_r + 1'b1;
end else if(valid_o_r) begin//行结束
file_end_offset = file_end_offset - `IMG_WIDTH*3;
$fseek(fdO, file_end_offset, `SEEK_SET);
end
end
end
注意:
BMP位图的每一行像素所占字节数必须被4整除。若不能倍4整除,则每一行的末尾需要“补”1至3个字节的“00”。
整个文件大小也需要是4字节的整数倍,不足需要补零,当然不补零也不影响正常显示。
四、仿真测试结果
仿真图:
效果图:
五、源码下载
https://pan.quark.cn/s/f0e7a72caa26