【FPGA/图像处理】设计与架构-仿真与发布

少女dtysky

世界Skill

时刻2015.05.11

图像处理系列文章基本都是我本科毕设论文同步发布,这个项目以LGPL许可被发布在F-I-L。由于要提供给用户使用,所以必须要提供一套完整的仿真和实测流程,这些仿真程序又有必要遵循相同的架构,以保证开发时的方便和严谨,这考验着系统架构的功底。


2 设计与架构

由于所有的图像处理模块从属于同一个系列,并且需要兼容流水线和请求响应两种模式,所以需要一个标准的接口,这个接口用来连接各个模块,接口设计的标准是要使得每个模块之间的耦合最松,同时又不需要每一个处理结果都要开一个单独的帧缓存、以造成资源的浪费。不仅如此,在还需要考虑到模块自身的可定制(重用性)和软件可控性。同时由于要提供给用户使用,必须要提供一套完整的软件和实测流程。
本章将会说明如何去设计这样的一套接口,如何实现模块的标准化,以及提供给用户的仿真、实测和使用流程。

2.4 目录结构,测试与发布

由于这个图像处理库遵循同一套规范,并且面向用户,所以需要一个规范化的设计结构,这个结构被要求提供一套完整的:
可配置的可选测试样例 -> 软件仿真 -> 功能仿真 -> PSNR计算分析 -> 板上测试。

2.4.1 目录结构

由于以上原因,一套完善的目录结构是必要的,这不仅对于用户而言,也是对于开发的便利和严谨性而言,我将目录设计成了如图2-7的形式。

图2-7 目录结构
图2-7 目录结构
每一个目录的作用如下:

  1. HDL: FPGA工程以及被打包好的IP核被放置在这个文件夹内,它们由Vivado建立。
  2. ImegForTest: 一个用于存储图片的文件夹,你可以将你想要进行测试的图像放在这里,只有"jpg"和"bmp"格式的文件被支持,不仅如此,一个名为"conf.json"的文件被用来配置仿真参数。
  3. SoftwareSim: 软件仿真的python源文件在这里,它们能够以软件的方法向你展示这个模块的功能。
    仿真结果将会被放置在"SimResCheck"文件夹内。
  4. HDLSimDataGen: 这里有一个python的源文件,它是用来创建"dat"文件的,这种文件被作为HDL功能仿真时图像数据的来源。
    dat文件将会被放置在"FunSimForHDL"文件夹内。
  5. FunSimForHDL: HDL功能仿真将在这里进行。
  6. SimResCheck: 一个名为"covert.py"的python源文件将HDL功能仿真的结果转换为图像,此外,软件仿真的结果也会被放置在这里,另一个名为"compare.py"的源文件用于将所有的软件仿真结果和HDL功能仿真结果进行比对,然后生成一份报告,用于评估IPCore的质量。
  7. DocGen: 自己编写的针对HDL的注释->文档解析器,用于快速生成一个当前项目的文档模板。

2.4.2 测试流程

测试分为软件仿真、功能仿真、PSNR计算和板上测试。

2.4.2.1 软件测试

我根据每一个模块的特性提供了一些用户可选的仿真参数,这些参数通过一个"conf.json"文件被配置,并作用于每一个张测试图像。这些参数和图像随后被用于软件仿真的程序读入,并生成软件仿真的结果,软件仿真程序遵循一个标准,其函数结构如下:

def name_format(root, name, ex, conf):
def transform(im, conf):
def debug(im, conf):

name_format函数接受的参数为所有图像的文件路径、文件名、扩展名,以及用户设定的配置文件,返回一个字符串,这个字符串将作为处理后图像的文件名。
transform函数接受一个Image对象的指针与用户设定的配置文件,返回一个Image对像的指针,这个对象即为经过这个模块处理后的图像。
debug函数接受的参数和transform函数一致,但返回的是一个字符串,这个字符串应当包含这个模块处理后的图像的像素数据,用于调试。

2.4.2.2 HDL功能仿真

功能仿真用于HDL文件的功能测试,其基本流程是:
将图像和配置转换为dat文件 -> 搭建Test bench并读入文本进行仿真 -> 输出结果到res文件 —> 转换为图像。
将图像转换为dat文件的过程是由python完成的,这个程序同样遵循一个标准:

def name_format(root, name, ex, conf):
def conf_format(im, conf):
def color_format(mode, color):
def create_dat(im, conf):

name_format函数接受的参数为所有图像的文件路径、文件名、扩展名,以及用户设定的配置文件,返回一个字符串,这个字符串将作为处理后图像的文件名。
conf_format函数接受一个Image对象的指针与用户设定的配置文件,返回写在目标dat文件起始位置的字符串,这个字符串可以视作与Test bench传递模块参数的接口,它作用于整张图像。
color_format函数接受一个图像的模式和一个像素的色彩值,返回的是一个格式化后的色彩值,格式化的格式根据模块的需求和图像模式而定,模式通常为RGB、灰度等。
create_dat函数接受一个Image对象的指针和用户设定的配置文件,它返回的是当前图像被转换后的dat文件所需要写入的所有内容。

有了数据的来源,需要考虑的便是Test bench的搭建,Test bench,即测试平台,是HDL验证领域所必须搭建的,这对于模块的功能判断和调试是十分有必要的,我使用SystemVerilogHDL来搭建测试平台,SV灵活强大,抽象能力强,几乎是业界搭建测试平台的标准,对于测试平台,虽然对于不同的模块难以完全标准化,我仍然设立了一套基本的标准:

interface TBInterface (input bit clk, input bit rst_n);
task init_file();
task init_signal();
task work_pipeline();
task work_regack();

TBInterface是一个接口,用于构造仿真需要的接口,这里使用接口并不是为了在设计的时候模糊接口完整的定义,因为它并不与verilog兼容,这样做的好处仅仅是让结构看起来更清晰,比如在实例化的时候可以这样去做:

TBInterface #(3, 8) RGBPipline(clk, rst_n);
Test Test1(RGBPipline.clk, RGBPipline.rst_n......

接口后是四个task(任务)。
init_file在每一张新的图像输入的时候被调用,一般用于将图像的宽高写入res文件,并且读取用户定义的配置参数,使其作用于整张图像。
init_signal用于在每张图像被处理前进行一些信号初始化工作,比如rst_n这个复位信号就可以在这个流程中完成模块的复位操作。
work_pipeline是工作流程,用于指定这个模块在流水线模式下如何被测试,以及将要输出怎样的数据。
work_regack同上,唯一的区别在于这个task用于请求响应模式下的测试。

仿真完成后得到的是一系列的res文件,这些文件中有处理结束后的图像数据,接下来将这些数据利用一个python脚本进行转换,便可以得到HDL功能仿真的结果。

2.4.2.3 PSNR计算分析

有了软件仿真和功能仿真的结果,便可以对模块实际运作的质量进行一个评估,这里选用PSNR进行评估,PSNR的计算公式如式2-1。
$$PSNR = 10\log10(\frac{MAX^2} {MSE})\ \ \ \ \ \ \ \ (2-1)$$

其中,MSE是原图像与处理图像之间的均方误差,MAX是图像在当前位宽下的最大值,这里用软件仿真的结果作为原图像,HDL功能仿真的结果作为处理图像。
PIL库提供了计算MSE的函数,加上math库中的log函数即可完成计算:

diffs = ImageChops.difference(Image.open(f_pair[0]), Image.open(f_pair[1]))
stat = ImageStat.Stat(diffs)
rms = sum(stat.rms) / len(stat.rms)
psnr = 20.0 * math.log10(255.0 / rms) if rms != 0 else 1000*1000

PSNR值的单位为dB,理论上,PSNR值越大失真越少,代表图像处理的质量越高,一般情况下PSNR > 30dB即为人眼可以容忍的范围。

2.4.3 板上测试

一些板上测试工程会被提供,它们由Vivado的图形化设计界面构建,使用xilinx提供的Zybo开发板,并使用Ov7670摄像头模块作为图像来源,使用VGA作为图像输出,同时建立一个AXI总线的IP核来完成简单的通过软件对模块的配置。

2.4.4 发布

项目拥有自己的发布网站,发布网站使用Pelican作为框架,用html、css和js开发,并搭建在VPS上,最终发布于fil.dtysky.moe。


参考文献


感谢

Alexis Metaireau and contributors, Pelican 3.5.0, https://github.com/getpelican

如果不是自己的创作,少女是会标识出来的,所以要告诉别人是少女写的哦。