上篇博客我们遗留了一些问题,这篇博客算是对上篇博客的完善。
一、Normal(Standard)模式的FIFO
上篇博客,我们最后得到如下的波形:
1、换行数据的问题
在换行时数据比较怪,如第 4 行数据的第一个矩阵是{10,10,11,20,20,21,30,30,31},这是什么鬼?我们将数据放到正常的图片数据上来分析,可以看到,在换行时是借用了上一行最后一个矩阵的一列数据。
实验推理:图像最左边出现一个违和的竖条,隐隐约约像是图片最右边的图形。
2、换帧数据的问题
我们也按上面那种方法分析。换帧后的第一个矩阵数据为{30,30,30,40,40,40,50,50,1},这个3x3矩阵借用了上一帧图片末尾的最后一列数据。不仅如此,整个的第一排矩阵都是借用了上一帧图片末尾的最后一个矩阵的一列数据。
实验推理:图像最上边出现一个违和的横条,隐隐约约像是图片最右下角的图形。
3、实际上板效果
本次上板采用的图像处理为均值滤波,关于均值滤波的具体实现我会另开一篇博客说明。此外我将选取的图片的左上角画了一个黑点,右下角画了一个白点。如下所示:
经过处理后得到如下图片:
明显的看到最上边出现一条白线,正是右下角的白点造成的。而最左边则隐隐约约出现了最右边的图形,尤其左下角那里很容易观察到。实验现象和我们的推理一致。
二、show-ahead(first world fall through)模式的FIFO
用这种模式的话,只是矩阵数据的选取时就不需要打拍了,normal 模式的整个设计耗费 2 个时钟周期,而 show-ahead 模式的整个设计就只需要耗费 1 个时钟周期。
//矩阵数据选取 //--------------------------------------------------- assign row_1 = q_2; assign row_2 = q_1; assign row_3 = din; //打拍形成矩阵,矩阵顺序归正,1clk //--------------------------------------------------- always @(posedge clk or negedge rst_n) begin if(!rst_n) begin {matrix_11, matrix_12, matrix_13} <= {8'd0, 8'd0, 8'd0}; {matrix_21, matrix_22, matrix_23} <= {8'd0, 8'd0, 8'd0}; {matrix_31, matrix_32, matrix_33} <= {8'd0, 8'd0, 8'd0}; end else begin {matrix_11, matrix_12, matrix_13} <= {matrix_12, matrix_13, row_1}; {matrix_21, matrix_22, matrix_23} <= {matrix_22, matrix_23, row_2}; {matrix_31, matrix_32, matrix_33} <= {matrix_32, matrix_33, row_3}; end end
其仿真波形如下所示:
1、换行数据的问题
第4行数据的第一个矩阵是{11,11,11,21,21,21,30,30,31},数据主要是复制了本矩阵的最后一列,即图像数据的第一列。此外还借用了 2 次上一行最后一个矩阵的 1 个数据。
实验推理:图像最左边出现一个不怎么违和的竖条,隐隐约约像是第一列的复制。
2、换帧数据的问题
换帧后一开始的矩阵数据为{30,30,30,40,40,50,50,1},这个矩阵借用了上一帧图片末尾的最后一列数据,第5个矩阵后则开始全部借用本帧图片开头的第一个数据。
结论:最上边出现一个违和的横条,横条最左边隐隐约约像是图片最右下角的图形,横条的其他部分隐隐约约像是图片最左上角的图形。
3、实际上板效果
上板后看到最左边没有那么违和了,最上边基本是黑色,但其实最上边开头处是有一丁点的白色的,全图不好观察,我专门照一下这个角:
这样就能看到了,实验结果我们的推理也是一致的。
4、normal模式和show-ahead模式对比
(1)normal 模式的矩阵数据选取时需打拍才能对齐,打拍加生成矩阵共耗费 2 个时钟周期,而 show-ahead 模式选取矩阵数据时不需要打拍,只是生成矩阵时耗费 1 个时钟周期。
(2)normal模式转行时借用前一行最后一列像素,而show-ahead模式转行时多是复制自身,转行时 show-ahead 模式表现较好。
(3)转帧时,两种模式的表现都很垃圾。
三、FIFO法改进:边界补0
边界补 0 法即是在转行和转帧时的用0来替代这些怪怪的数据,normal 模式和 show-ahead 模式都差不多,最后的波形都一样,下面仅整理 show-ahead 模式的边界补 0 改进方法。
1、代码
边界补 0 法的要点在于矩阵数据选取时做文章,如下所示:
//矩阵数据选取 //--------------------------------------------------- assign row_1 = rd_en_2 ? q_2 : 8'd0; assign row_2 = rd_en_1 ? q_1 : 8'd0; assign row_3 = din_vld ? din : 8'd0;
2、波形
得到的波形如下所示:
拿图像数据来看的话则如下图所示:
实验推理:在像素构成上,0代表黑色,因此处理后的第一行和第一列图形偏黑,看上去就是图像的上边和左边出现了一条黑边。
3、实际上板:
上板后可以看到最左边和最上边确实出现了黑边,由于天黑了,拍照不太容易照清边界,实际上是可以看见黑边的。
四、FIFO法改进:边界复制
边界补 0 终究不是太好,最后图像的上边和左边出现了黑边。最好的方法是镜像或者复制,由于 FPGA 处理是线性的,镜像法的实现比较困难,这里只探究复制法的实现。
边界复制法用 normal 模式或 show-ahead 模式都差不多,原理都是一样的,无非是 normal 的数据和计数得再打一拍才能使用。下面仅展示 show-ahead 模式下的边界复制改进办法。
1、代码
边界复制法的要点在于生成 3x3 矩阵,如下所示:
always @(posedge clk or negedge rst_n) begin if(!rst_n) begin {matrix_11, matrix_12, matrix_13} <= {8'd0, 8'd0, 8'd0}; {matrix_21, matrix_22, matrix_23} <= {8'd0, 8'd0, 8'd0}; {matrix_31, matrix_32, matrix_33} <= {8'd0, 8'd0, 8'd0}; end //------------------------------------------------------------------------- 第1排矩阵 else if(cnt_row == 0)begin if(cnt_col == 0) begin //第1个矩阵 {matrix_11, matrix_12, matrix_13} <= {row_3, row_3, row_3}; {matrix_21, matrix_22, matrix_23} <= {row_3, row_3, row_3}; {matrix_31, matrix_32, matrix_33} <= {row_3, row_3, row_3}; end else begin //剩余矩阵 {matrix_11, matrix_12, matrix_13} <= {matrix_12, matrix_13, row_3}; {matrix_21, matrix_22, matrix_23} <= {matrix_22, matrix_23, row_3}; {matrix_31, matrix_32, matrix_33} <= {matrix_32, matrix_33, row_3}; end end //------------------------------------------------------------------------- 第2排矩阵 else if(cnt_row == 1)begin if(cnt_col == 0) begin //第1个矩阵 {matrix_11, matrix_12, matrix_13} <= {row_2, row_2, row_2}; {matrix_21, matrix_22, matrix_23} <= {row_2, row_2, row_2}; {matrix_31, matrix_32, matrix_33} <= {row_3, row_3, row_3}; end else begin //剩余矩阵 {matrix_11, matrix_12, matrix_13} <= {matrix_12, matrix_13, row_2}; {matrix_21, matrix_22, matrix_23} <= {matrix_22, matrix_23, row_2}; {matrix_31, matrix_32, matrix_33} <= {matrix_32, matrix_33, row_3}; end end //------------------------------------------------------------------------- 剩余矩阵 else begin if(cnt_col == 0) begin //第1个矩阵 {matrix_11, matrix_12, matrix_13} <= {row_1, row_1, row_1}; {matrix_21, matrix_22, matrix_23} <= {row_2, row_2, row_2}; {matrix_31, matrix_32, matrix_33} <= {row_3, row_3, row_3}; end else begin //剩余矩阵 {matrix_11, matrix_12, matrix_13} <= {matrix_12, matrix_13, row_1}; {matrix_21, matrix_22, matrix_23} <= {matrix_22, matrix_23, row_2}; {matrix_31, matrix_32, matrix_33} <= {matrix_32, matrix_33, row_3}; end end end
这代码......看起来很蠢是吧,但我实在想不到别的办法了。这段代码与其说是设计,不如说是拼凑。如果谁有更优雅的写法欢迎告知。
2、波形
仿真波形如下所示:
由于这次设计的波形太长,因此只展示第二帧的图像数据仿真波形。从波形上看数据很难看懂,我们也是一列列矩阵放到图像数据上来看,如图所示:
3、实际上板:
上板后发现图片非常完美,最上边和最左边都很和谐,完全没有违和感。直接看图片非常像边界补 0 的操作,但边界补0法上边和左边都是黑边,而边界复制法不是黑边。此次设计的实验可以通过按键切换图像效果,在边界补0法切换为边界复制法的瞬间,可以看到图片的最左边和最上边突然放大了一点点,其实就是边界补 0 法的黑边消失了。
后记:
本篇博客解决了上篇博客遗留下来的问题,比较了 FIFO 生成矩阵后的各种处理,最后得出结论: show-ahead 模式的代码最简洁,边界复制法的效果最完美。
实际上在 Quartus 、ISE、Vivado 中有专门用于生成矩阵的 shift IP 核,下篇博客我们再来介绍。