FPGA图像处理的仿真测试激励该如何写?

https://github.com/becomequantum/ 代码在这

FPGA图像处理的仿真测试激励该如何写?_哔哩哔哩_bilibili直接读位图文件生成仿真输入波形,再把仿真结果存入输出位图中。icon-default.png?t=N7T8https://www.bilibili.com/video/BV1ZS4y1H7oQ之前的这些视频讲了一些基本的FPGA图像处理Verilog代码该怎么写。但我想大家都知道,你只写了Verilog模块代码还是不够的,它不像软件编程代码那样写了就能运行看到结果。Verilog模块代码你想看到输出结果还得编写测试激励给这个模块提供数据输入信号,然后用仿真器跑仿真才能看到模块的输出波形。对于FPGA图像处理模块来说,写测试激励还会遇到两个问题:一是图像输入数据是比较多的,一般都存在bmp位图文件里,我们不可能把这些数据手写到测试激励代码里。这就涉及到该如何把位图文件里的图像数据读到测试激励里,然后形成仿真输入波形。第二个问题是仿真结果一般是以波形的形式出现的,但图像处理的结果一般也是图像,要从一维的波形数据里看出图像也不大现实。所以我们也需要把仿真结果写入到一个输出位图文件中,这样等仿真跑完我们打开输出位图文件就可以看到仿真结果了。

也就是说我们的测试激励代码需要解决两个问题:一是读入位图文件中的图像数据形成仿真波形。二是把仿真结果数据也写入到一个输出位图文件中。在讲这个代码之前我们需要了解一下bmp位图文件的格式。位图文件存的是未压缩的图像数据,文件前面是54个字节的文件头,后面就是图像数据。位图文件常见的有24位和32位两种,下面要讲的代码对这两种格式都适用。54个字节的文件头里存了图片的相关信息,测试激励代码里会用到的信息除了图像的宽度和高度之外,就是它是24位还是32位图这个。

下面直接看代码。最先要说的是,在Verilog测试激励代码里,integer这个变量类型指的是32位整型,但它也可以用作指代一个打开的文件,就相当于一个文件指针或句柄吧。所以上面的代码中In Bmp File这个变量指代的就是我们要读入的测试输入位图文件。再来看深度为54的Bmp Head Mem这个寄存器数组,它就是用来存放要读入的位图文件前54个字节文件头的。把文件头读入存起来有两个原因:一是要从中获取图片的宽高信息,以及是24位还是32位。另一个原因是仿真输出要写入一个新建的输出位图文件中,这个位图文件也需要写入一个文件头啊,一般输出图像和输入是一样大的,那正好把输入文件的文件头写过去就可以了。

那为啥只存文件头呢?为啥不把所有文件数据都读进来存着呢?因为这时候我们还并不知道图片的宽高等信息啊,所以不知道需要多大的存储空间,再说也没有这个必要,仿真的过程中是可以边从文件里读数据边输出的。

接下来看initial块里面的代码,第一件事情就是打开输入位图文件, 并把文件头读入到Bmp Head Mem里。代码里写的文件名是input点bmp。在仿真时你把这个文件放到仿真器的工作目录里就可以了。ISE里就放到工程文件夹里就可以了,因为ISE里啥工作目录都在这里。Vivado里把文件夹分类管理了一下,所以你得找到仿真工作目录把它放进去。仿真输出的位图文件在仿真结束后也会出现在这个目录里。

第二件事情就是获取图片的宽高这两个参数,把记录它们的四个字节拼起来就行了,具体是哪几个字节已经写在上面了,大家就不用去查了。当然更简单的写法是,把输入图片宽高参数就写在测试激励里,这样就不用去读它了。这样写的代码通用性就不好,输入图片尺寸变了就得改代码。像我这样写啥情况都能适用。

第三件事情就是获取一个像素是有3个字节的数据还是4个字节的数据,对应的就是24位图和32位图。文件头里存的数是24或32,而代码里需要的是字节数3或4,所以把数据右移了3位。这样写是为了让代码能适用于这两种文件类型,这样就不用去管输入的位图文件是24还是32位的了。有了宽高和字节数信息之后就可以算出Stride是多少,这个Stride的意思是一行图像数据所占用的实际字节数是多少。32位图的情况下比较简单,实际字节数就是图像宽度乘以4。24位图时由于需要四字节对齐,当图像宽度乘以3的结果不等于4的倍数时,它实际占用的字节数就要多一点,会加成稍大一点的4的倍数。算出了Stride,我们才知道换行时字节地址要加多少。

图去视频中看

测试激励代码在仿真一开始需要做的事情就是这些了,只是获取和保存了一些信息。接下来继续看读入图像数据并产生测试输入的代码,如上图所示。代码是写在一个task里面的,需要再initial块里调用。既然是要读图像数据,那肯定少不了for语句双循环。for循环在Verilog里算是不可综合语句,一般都用在写测试激励中。模块代码中其实也用不上for语句的,如果你觉得用的上,那肯定是思维还没从写C代码中转过来。模块代码中没有索引变量这样的概念,但是有Ram地址。Ram地址一般就是个计数器,计数器根据图像的行有效信号是否升起来计数。上面的代码中RGB E n就是行有效信号。

第一重遍历图像行数的循环变量h是从图像高度值减1开始的,那是因为在位图文件中,第一行、也就是最上面那行的数据居然存在了最后面,这也是我写这个代码时才发现的。C#代码中用指针去遍历图像数据时,第一行数据就在最前面。第二重遍历每个像素数据的循环中,先要把一个像素点的数据从文件中读出来,用的是f read这个函数。这个函数可以有四个参数,第一个参数是一个寄存器数组,数据会被读进这里。第二个参数写的是位图文件句柄In Bmp File,它指代的就是前面打开的input点bmp。读进来之后就把数据赋值给R、G、B这三个信号即可。这个循环里最关键的是下一句:@(pos edge clock),要是这句没有,这段代码就跑不正确。会不会写这句基本上就代表着你会不会写测试激励。这么重要的一句是干啥的就不说了,留给大家自己琢磨,可以把这句删掉,或者写两遍再去跑下仿真,看看结果会有啥不同就知道了。

图去视频中看

有了产生输入波形的代码还没完,还需要采样仿真输出并把它保存到输出位图文件中。这段代码可以写在另外一个initial块中,如上图所示,它也需要用到两重循环,只不过第二重循环用的是while循环,其实用for循环好像也可以,但用while更简单一些。这段代码里的一个关键点是,可能需要跳过几行才开始采样输出数据。因为输出的数据相比输入数据,可能在横竖这两个方向上都是有延时的。大家在得到仿真输出后一定要放大并和输入仔细对比一下,看看输出和输入图像有没有精确对齐,是否发生了横竖向的错位。要是有横向错位,那就是输出使能信号没有和数据对齐。要是有上下的错位,那就是这里的延时行数没有弄对。

好了,主要代码就这些了。其实测试激励的代码并不难写,只是初学者没见过真正工程中实用的测试激励代码,所以不知道该怎么写。有些测试激励里才会出现的写法初学者可能没见过。上面这些代码就包含了各种测试激励里的常用操作,包括如何生成、采样信号,文件读写等等。会了这些基本写法,各种各样的测试激励就都能写出来的。因为Verilog模块的输入一般都是线性的数据流,格式都比较简单,图像数据要用到双循环可能都算复杂的了。

上面这个代码有一个小问题,那就是在ISE里仿真时,如果输入图像的宽度是某个特定值时,仿真读入数据会莫名出错。具体原因不知道,在Vivado和别的仿真器里会不会出现这个问题也不知道。如果出现了就去画图里把图像宽度改一下,绕过这个问题就行。Verilog代码的调试比上位机代码的调试的确要复杂一些。首先Verilog的编辑器并不能很好的帮你检查语法错误,不知道现在有没有好用一点的编辑器或者插件了。这样就会导致初学者还会被一些简单的低级错误卡住。在就是Verilog可能出问题的地方更多,除了模块代码出问题之外,还有可能是测试激励代码出问题,甚至有可能是仿真器出现了莫名其妙的问题导致结果不对。要是涉及到硬件调试,那各种莫名问题就更多...顺便吹个牛,俺直觉比较好,调代码的时候从来不会被bug卡很久。之前还专门做了上面个视频讲该如何训练直觉。有兴趣的朋友可以看看。

  • 9
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值