一、功能描述
mid3x3模块的功能首先是根据当前中心像素点坐标生成地址,获取RAM_picture模块中的数据,接着生成我们需要的3X3矩阵模板,然后输出给后面的模块去处理,当后面的模块处理完后,再通过模块生成下一个3x3矩阵,直到把所有的像素点都遍历完。
二、端口描述和设计
上图,先看一下我们需要哪些I/O端口?
结合图具体解析一下,
输入信号:模块的输入信号有4个,时钟clk和复位rst_n都是全局信号,接着是flag信号,前面都有讲到代表的是一个3x3模板处理完之后的标志信号,由midfilter模块产生,输入到当前模块,然后是输入的data_in[23:0]是来自于RAM_picture的数据也就是提前存储好的图片数据;
输出信号:address_out[31:0]控制RAM读出的数据地址,以便我们能够取到想要坐标的地址,具体的地址选取方式我们下面再讲。mid3x3_done也是标志信号,代表着一个3x3模板生成了,后面模块可以拿去用了。而c1~c9就是我们生成的3x3矩阵模板,排列方式如下。
这个排序是需要记住的,这关乎到我们后面地址的选取,还有就是我们数据是从图层的左上角为第一个点,从左往右,从上往下开始存储在RAM当中,右下角是最后一个像素点。
通过Verilog设计的端口和常量如下:
module mid3x3#(
parameter picture_size =71200,
picture_row=200,
picture_col=356,
data_width =24
)(
input clk,rst_n,
input flag,
input [data_width-1:0]data_in,
output reg [data_width-1:0] c1,c2,c3,c4,c5,c6,c7,c8,c9,模板矩阵
output[31:0] address_out,
output reg mid3x3_done
);
三、逻辑功能设计
逻辑功能部分的设计又可以分为两部分:坐标计数,矩阵像素点获取。
1)坐标计数
要遍历所有的像素点,生成以当前像素点为中心的3x3矩阵模板,我们首先得知道当前像素点的坐标,怎么求这个坐标呢,很简单就是计数!这里需要产生三个寄存器:像素点计数寄存器、行计数寄存器、列计数寄存器。
reg [31:0] address_out_n;
reg [31:0] pixel_cnt;//像素点计数
reg row_last;///一行结束信号
reg [31:0] row_cnt;//行像素点计数
reg [31:0] col_cnt;//列像素点计数
其中的address_out_n代表的是像素地址,row_last标识一行的最后一个像素点(其实可用可不用),就是在最后一个像素点的时候拉高,其他的时候全为低电平0。
像素点计数寄存器pixel_cnt:为了知道像素点处理的进程,就是明确当前是第几个像素点了,所以需要对像素点计数,且当前像素点的计数就是当前像素点的地址address_out。解释一下,比如我们要生成的第一个3x3矩阵模板,它的中心点像素地址我们是可以直接知道的就是0(为什么是0呢?因为我们存在RAM中的地址是从0开始的),第二个我们要生成的3x3矩阵模板,它的中心点像素地址是1,以此类推,第N的矩阵模板的地址就是N-1。
可以看到中心点还是比较好找的,地址都是跟我们的矩阵模板处理的进度或者说数量是一致的,但是除开中心点,其他点的寻找就需要坐标了。
列计数寄存器row_cnt:代表当前模板中心像素点的横坐标,也就是在一行中处理到哪一列像素了。
行计数寄存器col_cnt:代表当前模板中心像素点的纵坐标,也就是处理到哪一行像素了。
例如下面这图:
以黑色这个点为中心点像素,那它的坐标就是row_cnt=6、col_cnt=3。对应这里还可以简单的推一下它的地址为pixel_cnt=row_cnt+row_cnt*picture_col=6+3*10=36。其中picture_col代表的一行有多少个像素点。
对于坐标计数部分的具体Verilog设计如下:
///判断像素点处理进度/
always@(posedge clk)
if(!rst_n)
pixel_cnt<=0;
else if(flag&(pixel_cnt<=picture_size-1))
pixel_cnt<=pixel_cnt+1;
else
pixel_cnt<=pixel_cnt;
///判断在一行中处理进度/
always@(posedge clk)
if(!rst_n)
begin
row_cnt<=0;
row_last<=0;
end
else if(flag&(row_cnt<picture_col-1))
begin
row_cnt<=row_cnt+1;
row_last<=0;
end
else if(flag&(row_cnt==picture_col-1))
begin
row_cnt<=0;
row_last<=1;
end
else
begin
row_cnt<=row_cnt;
row_last<=0;
end
///判断行处理进度/
always@(posedge clk)
if(!rst_n)
col_cnt<=0;
else if((col_cnt<=picture_row)&(row_cnt==picture_col-1)&flag)
col_cnt<=col_cnt+1;
else
col_cnt<=col_cnt;
这里涉及到了三个always功能描述块的使用,我们挨着挨着看:
第一个always块:主要是对pixel_cnt计数器寄存器进行累加计数,+1的条件就是当flag信号拉高且计数的值pixel_cnt小于总的像素值71200时,条件不满足时就不变。实现的逻辑是一个矩阵模板滤波处理完成之后就对计数+1,开始下一个像素处理。
第二个always块:主要是对横坐标row_cnt进行计数累加,要稍微复杂一点。实现的逻辑是当flag信号拉高且计数的值row_cnt小于一行总的像素值picture_col-1(我这里是常数356-1)时,row_cnt+1;当等于picture_col-1时就清零。简单来说就是一行的计数从零开始累加到一行的总数过后又从零开始循环。这里还对row_last进行了赋值,就是当row_cnt计数到一行的最后一个数的时候拉高,其他的时候都拉低为0。
第三个always块:主要是对纵坐标col_cnt进行计数累加,实现的逻辑是当flag信号拉高且计数的值col_cnt小于总的行数picture_row(200)时并且row_cnt等于一行总的像素值picture_col-1时,col_cnt可以+1,其他的时候都为不变。
2)矩阵像素点获取
要获取到矩阵模板9个点的数据我们得先获取到每个数据的地址,在前面部分基础上,我们成功的获得了模板中心点的地址pixel_cnt和横纵坐标row_cnt、col_cnt,有了中心点这两个计数器坐标,我们就可以推导出以它为中心点的3x3矩阵模板中,每一个像素点的坐标以及对应的存储地址:
c1的坐标为(row_cnt-1,col_cnt-1),对应地址:c1_address=row_cnt-1+(col_cnt-1)*picture_col;
c2的坐标为(row_cnt,col_cnt-1),对应地址:c2_address=row_cnt+(col_cnt-1)*picture_col;
c3的坐标为(row_cnt+1,col_cnt-1),对应地址:c3_address=row_cnt+1+(col_cnt-1)*picture_col;
c4的坐标为(row_cnt-1,col_cnt),对应地址:c4_address=row_cnt-1+col_cnt*picture_col;
c5的坐标为(row_cnt,col_cnt),对应地址:c5_address=pixel_cnt;or=row_cnt+col_cnt*picture_col;
c6的坐标为(row_cnt+1,col_cnt),对应地址:c6_address=row_cnt+1+col_cnt*picture_col;
c7的坐标为(row_cnt-1,col_cnt+1),对应地址:c7_address=row_cnt-1+(col_cnt+1)*picture_col;
c8的坐标为(row_cnt,col_cnt+1),对应地址:c8_address=row_cnt+(col_cnt+1)*picture_col;
c9的坐标为(row_cnt+1,col_cnt+1),对应地址:c9_address=row_cnt+(col_cnt+1)*picture_col;
大家可以一一推导一下加深印象,对应地址就是对应在RAM_picture中的存储地址,其中picture_col代表的一行有多少个像素点。
对于矩阵模板像素点获取的设计代码如下:
reg [3:0]state;
reg [1:0] data_cnt;/等待地址稳定然后取数
always@(posedge clk)
begin
if(!rst_n)
begin
state<=0;
pixel_cnt<=0;
row_cnt<=0;
col_cnt<=0;
mid3x3_done<=0;
address_out_n<=0;
data_cnt<=0;
end
else case(state)
0:begin state<=1;
pixel_cnt<=0;
row_cnt<=0;
col_cnt<=0;
address_out_n<=0;
mid3x3_done<=0;
data_cnt<=0;
end
1:begin
mid3x3_done<=0;
if(data_cnt==2)begin state<=2;data_cnt<=0;end else
begin state<=1;data_cnt<=data_cnt+1;end
if((col_cnt==0)||(row_cnt==0))
c1<=24'b0;
else
begin
address_out_n<=row_cnt-1+(col_cnt-1)*picture_col;//取第1个点
c1<=data_in;
end
end
2:begin
if(data_cnt==2)begin state<=3;data_cnt<=0;end else
begin state<=2;data_cnt<=data_cnt+1;end
if(col_cnt==0)
c2<=24'b0;
else
begin
address_out_n<=row_cnt+(col_cnt-1)*picture_col;//取第2个点
c2<=data_in;
end
end
3:begin
if(data_cnt==2)begin state<=4;data_cnt<=0;end else
begin state<=3;data_cnt<=data_cnt+1;end
if((col_cnt==0)||(row_cnt==picture_col-1))
c3<=24'b0;
else
begin
address_out_n<=row_cnt+1+(col_cnt-1)*picture_col;//取第3个点
c3<=data_in;
end
end
4:begin
if(data_cnt==2)begin state<=5;data_cnt<=0;end else
begin state<=4;data_cnt<=data_cnt+1;end
if(row_cnt==0)
c4<=24'b0;
else
begin
address_out_n<=row_cnt-1+col_cnt*picture_col;//取第4个点
c4<=data_in;
end
end
5:begin
if(data_cnt==2)begin state<=6;data_cnt<=0;end else
begin state<=5;data_cnt<=data_cnt+1;end
address_out_n<=pixel_cnt;//取第5个点中心点
c5<=data_in;
end
6:begin
if(data_cnt==2)begin state<=7;data_cnt<=0;end else
begin state<=6;data_cnt<=data_cnt+1;end
if(row_cnt==picture_col-1)
c6<=24'b0;
else
begin
address_out_n<=row_cnt+1+col_cnt*picture_col;//取第6个点
c6<=data_in;
end
end
7:begin
if(data_cnt==2)begin state<=8;data_cnt<=0;end else
begin state<=7;data_cnt<=data_cnt+1;end
if((col_cnt==picture_row-1)||(row_cnt==0))
c7<=24'b0;
else
begin
address_out_n<=row_cnt-1+(col_cnt+1)*picture_col;//取第7个点
c7<=data_in;
end
end
8:begin
if(data_cnt==2)begin state<=9;data_cnt<=0;end else
begin state<=8;data_cnt<=data_cnt+1;end
if((col_cnt==picture_row-1))
c8<=24'b0;
else
begin
address_out_n<=row_cnt+(col_cnt+1)*picture_col;//取第8个点
c8<=data_in;
end
end
9:begin
if(data_cnt==2)begin state<=10;data_cnt<=0;end else
begin state<=9;data_cnt<=data_cnt+1;end
if((col_cnt==picture_row-1)||(row_cnt==picture_col-1))
c9<=24'b0;
else
begin
address_out_n<=row_cnt+1+(col_cnt+1)*picture_col;//取第9个点
c9<=data_in;
end
end
10:begin
mid3x3_done<=1;
if(flag)
begin
state<=1;
mid3x3_done<=0;
end
else
state<=state;
end default:state<=0;
endcase
end
这部分的代码十分的冗长,但逻辑其实也十分的简单,整体的结构是通过状态机的形式完成的,我详细的介绍一个状态大家就能看明白其他的状态了,因为我们取9个像素点数据的逻辑手段是一样的具有可重复性。
直接来看取第一个数的状态,当state=1
1:begin
mid3x3_done<=0;
if(data_cnt==2)
begin state<=2;data_cnt<=0;end
else
begin state<=1;data_cnt<=data_cnt+1;end
if((col_cnt==0)||(row_cnt==0))
c1<=24'b0;
else
begin
address_out_n<=row_cnt-1+(col_cnt-1)*picture_col;//取第1个点
c1<=data_in;
end
end
进入这个状态首先需要把标志信号mid3x3_done清零,代表矩阵还在生成当中。
接着我们先看第二个if-else,注意有个初始条件就是当col_cnt和row_cnt等于0的时候,直接将我们的c1等于0,这里怎么理解呢,其实就是实现对我们的图像边缘进行零值填充。大家想一下,当我们生成图像第一个矩阵模板时,是不是要以图层左上角第一个点为中心点,所以取出来的矩阵如下,c1=0、c2=0、c3=0、c4=0、c7=0。
同样以此类推,每当我们生成的矩阵在以图像边缘像素点为中心的时候,都需要利用这种方式进行零值填充 ,当不是以边缘点为中心时,可以利用我们前面的推导得出每个矩阵点c1~c9的地址然后取对应的像素值,比如这里的地址address_out_n<=row_cnt-1+(col_cnt-1)*picture_col。
然后是第一个if-else,这个if-else主要的作用是利用计数寄存器data_cnt生成延时,同时进行state状态的跳转,为什么要这么设置呢?主要的原因还是在取像素点时,我们的地址需要先产生,然后过一个周期才能将像数数据读出,最后再利用c1进行锁存,换句话说就是地址address_out_n的产生到c1数据的锁存有一个时钟的间隔,保证取到了我们想要的数之后,再进行状态的跳转。
从state=1到state=9,每个状态的设计都是按照这样的思路进行的。
state=0状态代表着初始化和复位的效果是一样的。当进入state=10这个状态,主要的功能就是拉高标志信号mid3x3_done表示3x3的矩阵模板生成好了,接着就是等待flag信号等待后面模块滤波完成就将状态跳转到state=0,进行新的一轮的矩阵模板生成。
还有最后一段就是将地址寄存器赋值给地址端口输出:
assign address_out=address_out_n;
四、总结
这一章设计的内容比较的多,难度比上一章明显的提高,同时还增加了很多的细节设计。设计的点包括了,判断计数、零值填充以及状态机的使用(很简单的状态机)。希望大家能够完完全全的吃透这里面的设计,还有想说的一点逻辑思维很重要,本文大部分的语言都是在描述设计的逻辑思维,而Verilog就相当于是实现我们逻辑思维的一种工具,语法不算高深,高深的往往是逻辑思维,这是对我自己说的也是对大家说的!
顺便补充一下完整的工程代码比较多,我会在最后一章给出,所以这里就不贴出。文章来源:https://www.toymoban.com/news/detail-662203.html
作为一名优秀的FPGA开发工程师应该知其然,亦知其所以然!文章来源地址https://www.toymoban.com/news/detail-662203.html
到了这里,关于基于FPGA的中值滤波设计————(3)矩阵模板生成模块设计的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!