8.2.2 整体设计与模块划分
在本节中,我们将详细介绍并行全比较排序算法的FPGA实现方 法,本书中主要针对中值输出的排序进行介绍。 首先是模块的整体设计与模块划分。我们在前面也提到,全比较 排序法以面积换速度的设计原则,使排序运算非常适合流水运算。由 于排序运算在图像的行列方向是同性的,因此,同样考虑首先进行一 维图像行方向上的排序,再对列方向上的行排序结果进行排序,即可 得到一个窗口内的排序结果。同样的,行方向的对齐采用行缓存来实 现,如图8-4所示。
由图8-4可以看出,设计的重点在于一维方向上的向量排序。因 此,考虑将一维方向上的运算封装成单独的模块。 实际上,我们在介绍求和模块时也提到,列方向上的算子不能直 接例化一维算子,这是由于列方向的输出结果是“非流水”的,而一 维算子往往都是基于流水设计的。因此,一维列方向上的排序同样需 要根据排序的原理依次取出不同行的排序结果进行再一次排序。 行缓存负责对不同行的排序结果进行对齐。 设计重点在于一维排序算法的实现,一维方向上的全并行排序要 同时实现所有数据的除自身的相互比较,并将结果相加后输出。一维方向上的设计框图如图8-5所示。
8.2.3 子模块设计
由上节所述,我们需要设计一维方向的排序运算模块,记为 sort_1d。同样,对于最终的二维排序运算模块,记为sort_2d。 1.sort_1d模块设计 我们首先来整理一下全并行排序的计算步骤: (1)首先得到待排序的n个数据:这可以通过将数据流打n-1拍来 实现。 (2)进行全比较:当前数据与其他所有数据依次比较,并记录比 较结果,比较的过程中需考虑输入次序问题。 (3)将(2)中的记录结果进行相加:根据不同的比较宽度,相 加工作可以通过多个时钟完成。 (4)查找(3)中相加结果按指定次序输出:如果要实现中值滤 波,中间索引号输出,对于其他次序的滤波可以采用取其他编号的数 据输出。以1×3的排序单元为例,需要至少6个比较器、3个加法器和6个寄 存器,其设计框电路如图8-6所示。 请读者注意图8-6中的大于符号与大于等于符号。图中的等于比较 器和mux实际上是3路。 我们注意到上述电路的资源消耗有3个大于比较器、3个大于等于 比较器、3个等于比较器、3个mux、3个加法器及6个reg。一个时钟即 可实现数据的输出,满足流水运算需求。 同时,我们注意到,输出mux需要一个OUT_ID的输入信号来进行判 决。这个OUT_ID既是决定排序的输出ID。对于中值滤波,我们当然会 选择中间结果进行输出,例如,设定我们的处理核为KSZ,则有 OUT_ID=KSZ>>1 中值滤波器 OUT_ID=0 最大值滤波器 OUT_ID=KSZ-1 最小值滤波器 2.sort_2d模块设计 对于二维运算,我们可以采用和均值滤波同样的思路来处理,同 样对于3×3的二维排序运算,整个计算步骤如下: (1)计算一维行方向的排序结果输出。 (2)将第(1)步的结果接入第一个行缓存,第一个行缓存的输 出接入第二个行缓存,得到共3行的一维输出。 (3)对第(2)步的输出的三个数据进行排序,得到结果输出。 (4)完成时序对齐。 我们已经有了一维运算的经验,因此,计算二维运算也是非常简 单的,比较麻烦的问题是时序对齐。二维运算的电路设计如图8-7所 示。
8.2.4 Verilog代码设计
1.sort_1d模块设计我们以1×3的排序为例来设计。一维排序模块的代码设计上需要 对以下参数作为入口参数: (1)数据位宽,记为DW。 (2)待排序的数据个数,记为KSZ。 (3)待输出的排序ID,记为OUT_ID。 模块定义如下:
module sort_1d(
rst_n,
clk,
din, //输入数据
din_valid, //输入数据有效
dout_valid, //输出数据有效
dout //输出数据
);
parameter DW = 14; //数据位宽
parameter KSZ = 3; //得排序数据个数
parameter OUT_ID = (KSZ >> 1);//待输出的排序ID
parameter DW_MAX_NUM = 8;
//输入数据个数最大值位宽,为8则不超过256个
reg [KSZ+3:0] din_valid_r; //输入有效寄存器
reg [DW-1:0] din_r[0:KSZ+2]; //输入数据寄存器
wire cmp_result[0:KSZ-1][0:KSZ-1];//比较中间结果信号
reg [7:0] cmp_sum[0:KSZ-1]; //3个比较和寄存器
reg [7:0] cmp_sum_r[0:KSZ-1];
reg [7:0] cmp_sum_r2[0:KSZ-1];
reg [DW-1:0] dout_temp; //输出寄存器
//首先缓存输入数据和输入有效数据,同时得到待排序的数据//缓存输入数据
always @(posedge clk or negedge rst_n)
if (((~(rst_n))) == 1'b1)
din_r[0]<= #1 {DW{1'b0}};
else
begin
if (din_valid == 1'b1)
din_r[0]<= #1 din;
end
generate
begin : xhdl0
genvar i;
for (i = 1; i <= KSZ + 2; i = i + 1)
begin : DATA_REG1
always @(posedge clk or negedge rst_n)
if (((~(rst_n))) == 1'b1)
din_r[i]<= #1 {DW{1'b0}};
else
din_r[i]<= #1 din_r[i - 1];
end
end
endgenerate
//缓存输入有效信号
always @(posedge clk or negedge rst_n)
if (((~(rst_n))) == 1'b1)
din_valid_r <= #1 {KSZ+4{1'b0}};
elsedin_valid_r <= #1 ({din_valid_r[KSZ +
2:0],din_valid});
//关键比较代码如下:
//将第一个数据与其他数据做比较 结果存放在cmp_result[0]中
generate
begin : xhdl1
genvar i;
for (i = 1; i <= KSZ - 1; i = i + 1)
begin : CMP_1st
assign cmp_result[0][i]=((din_r[0]>=din_r[i]))?
1'b1:1'b0;
end
end
endgenerate
//与自身的比较结果置0
assign cmp_result[0][0]= 1'b0;
//其他数据的比较电路
generate
begin : xhdl4
genvar i;
for (i = 2; i <= KSZ; i = i + 1)//除了第一个数据的
总共KSZ-1个数据
begin : CMP_Others
begin : xhdl2
genvar j;
for (j = 1; j <= i - 1; j = j + 1)begin : CMP_Previous //与本数据之前的数据
作比较
assign cmp_result[i - 1][j - 1]=
((din_r[i - 1]>
din_r[j - 1])) ? 1'b1 : 1'b0;
end
end
assign cmp_result[i - 1][i - 1]= 1'b0;//与自身
的比较结果置0
begin : xhdl3
genvar j;
for (j = i + 1; j <= KSZ; j = j + 1)
begin : CMP_After与本数据之后的数据作
比较
assign cmp_result[i - 1][j - 1]=
((din_r[i - 1]>=
din_r[j - 1])) ? 1'b1 : 1'b0;
end
end
end
end
endgenerate
//将比较结果相加
if (KSZ == 3)
begin : sum_ksz_3
always @(posedge clk or negedge rst_n)
if (((~(rst_n))) == 1'b1)begin
cmp_sum[i] <= 8'b0;
cmp_sum_r[i] <= 8'b0;
cmp_sum_r2[i]<= 8'b0;
end
else
begin
if (din_valid_r[KSZ - 1]== 1'b1)
begin
cmp_sum_r[i]<=#1(cmp_result[i][0])+
(cmp_result[i][2]);
cmp_sum_r2[i]<= #1 (cmp_result[i][1]);
end
if (din_valid_r[KSZ]== 1'b1)
cmp_sum[i]<= #1 cmp_sum_r[i]+
cmp_sum_r2[i];
end
end
//查找目标值
generate
if (KSZ == 3)
begin : dout_ksz_3
always @(posedge clk or negedge rst_n)
if (((~(rst_n))) == 1'b1)
dout_temp <= {DW{1'b0}};
else
beginif (din_valid_r[KSZ + 1]== 1'b1)
begin
if (cmp_sum[0]== OUT_ID)
dout_temp <= din_r[2];
else
if (cmp_sum[1]== OUT_ID)
dout_temp <= din_r[3];
else
if (cmp_sum[2]== OUT_ID)
dout_temp <= din_r[4];
end
end
end
endgenerate
//数据及数据有效输出
assign dout_valid = din_valid_r[KSZ + 2];
assign dout = dout_temp;
上述代码是基于1×3的窗口来设计的,我们注意到,代码的设计 非常灵活,因为全比较电路这一块是对任何尺寸通用的。如果要实现 不同尺寸的排序,那么需要考虑的仅是查找电路、加法电路和输出对 齐。以下是1×5的相关代码:
generate
begin : xhdl5
genvar i;
for (i = 0; i <= KSZ - 1; i = i + 1)
begin : CMP_r_sum
if (KSZ == 5)begin : sum_ksz_5
reg [DW_MAX_NUM-1:0]cmp_sum_r3[0:KSZ-1];
reg [DW_MAX_NUM-1:0]cmp_sum_r4[0:KSZ-1];
reg [DW_MAX_NUM-1:0]cmp_sum_r5[0:KSZ-1];
always @(posedge clk or negedge rst_n)
if (((~(rst_n))) == 1'b1)
begin
cmp_sum[i] <= {DW_MAX_NUM{1'b0}};
cmp_sum_r[i] <= {DW_MAX_NUM{1'b0}};
cmp_sum_r2[i]<= {DW_MAX_NUM{1'b0}};
cmp_sum_r3[i]<= {DW_MAX_NUM{1'b0}};
cmp_sum_r4[i]<= {DW_MAX_NUM{1'b0}};
cmp_sum_r5[i]<= {DW_MAX_NUM{1'b0}};
end
else
begin
if (din_valid_r[KSZ - 1]== 1'b1)
begin
cmp_sum_r[i] <= #1 (cmp_result[i][0])
+
(cmp_result[i][4]);
cmp_sum_r2[i]<= #1 (cmp_result[i][1])
+
(cmp_result[i][3]);
cmp_sum_r3[i]<= #1 (cmp_result[i][2]);
end
if (din_valid_r[KSZ]== 1'b1)begin
cmp_sum_r4[i]<= #1 cmp_sum_r[i]+
cmp_sum_r2[i];
cmp_sum_r5[i]<= #1 cmp_sum_r3[i];
end
if (din_valid_r[KSZ + 1]== 1'b1)
cmp_sum[i]<= #1 cmp_sum_r4[i]+
cmp_sum_r5[i];
end
assign dout_valid = din_valid_r[KSZ +
3];
assign dout = dout_temp;
end
generate
if (KSZ == 5)
begin : dout_ksz_5
always @(posedge clk or negedge rst_n)
if (((~(rst_n))) == 1'b1)
dout_temp <= {DW{1'b0}};
else
begin
if (din_valid_r[KSZ + 2]== 1'b1)
begin
if (cmp_sum[0]== OUT_ID)
dout_temp <= din_r[3];
else
if (cmp_sum[1]== OUT_ID)dout_temp <= din_r[4];
else
if (cmp_sum[2]== OUT_ID)
dout_temp <= din_r[5];
else
if (cmp_sum[3]== OUT_ID)
dout_temp <= din_r[6];
else
if (cmp_sum[4]== OUT_ID)
dout_temp <= din_r[7];
end
end
end
endgenerate
2.sort_2d模块设计 二维代码的设计除了要例化一维的参数,还需要图像的宽度和高 度作为参数信息。 模块定义如下:
module sort_2d(
rst_n,
clk,
din_valid,
din,
dout,
vsync,
vsync_out,
dout_valid);
parameter DW = 14;
parameter KSZ = 3;
parameter IH = 512;
parameter IW = 640;
parameter OUT_ID = KSZ>>1;
parameter DW_MAX_NUM = 8;
根据图8-7的电路所示,二维运算模块电路设计步骤如下:
(1)例化一个一维的排序模块sort_1d。
(2)将(1)的输出接入两个行缓存。
(3)对(1)和(2)的结果进行排序。
(4)完成时序对齐。
关键代码如下:
//首先需例化一个一维的排序模块
sort_1d #(DW,KSZ,OUT_ID,DW_MAX_NUM)
sort_row(
.rst_n(rst_all_low),
.clk(clk),
.din(din),
.din_valid(valid),
.dout_valid(din_valid_r),
.dout(sort_row) //行排序结果输出
);
//将一维排序的输出打入行缓存
assign sort_row_temp[0]= sort_row;
assign sort_row_temp[KSZ - 1]= line_dout[KSZ - 2];
generatebegin : xhdl4
genvar i;
for (i = 0; i <= KSZ - 2; i = i + 1)
begin : buf_cmp_inst
if (i == 0)
begin : MAP12
always @(*) line_din[i]<=
sort_row_temp[i];
always @(din_valid_r)
line_wren[i]<= din_valid_r;
end
if ((~(i == 0)))
begin : MAP13
assign sort_row_temp[i]= line_dout[i - 1];
always @(posedge clk)
begin
if (rst_all == 1'b1)
begin
line_wren[i]<= 1'b0;
line_din[i]<= {DW{1'b0}};
end
else
begin
line_wren[i]<= #1 line_rden[i - 1];
//接成菊花链方式
line_din[i]<= sort_row_temp[i];
//换缓存内为一维运算数据end
end
end
//行缓存读出
assign line_rden[i]= buf_pop_en[i]&
din_valid_r;
always @(posedge clk)
begin
if (rst_all == 1'b1)
buf_pop_en[i]<= #1 1'b0;//行缓存存满后再打
出
else if (line_count[i]== IW)
buf_pop_en[i]<= #1 1'b1;
end
//例化行缓存
line_buffer #(DW,IW)
line_buf_inst(
.rst(rst_all),
.clk(clk),
.din(line_din[i]),
.dout(line_dout[i]),
.wr_en(line_wren[i]),
.rd_en(line_rden[i]),
.empty(line_empty[i]),
.full(line_full[i]),
.count(line_count[i])
);end
end
endgenerate
//接着进行列方向上的排序运算
//第一个数与其他数作比较
generate
begin : xhdl1
genvar i;
for (i = 1; i <= KSZ - 1; i = i + 1)
begin : CMP_1st
assign cmp_result[0][i]= ((sort_row_temp[0]>=
sort_row_temp
[i])) ? 1'b1: 1'b0;
end
end
endgenerate
//自身比较结果置0
assign cmp_result[0][0]= 1'b0;
//其他数的全比较电路
generate
begin : xhdl2
genvar i,j;
for (i = 2; i <= KSZ; i = i + 1)
begin : CMP_Others
for (j = 1; j <= i - 1; j = j + 1)
begin : CMP_Previous //与当前数的前面的数做比较assign cmp_result[i - 1][j - 1]=
((sort_row_temp[i -
1]> sort_row_temp[j - 1])) ? 1'b1 : 1'b0;
end
assign cmp_result[i - 1][i - 1]= 1'b0;//自身比较
结果清零
for (j = i + 1; j <= KSZ; j = j + 1) //与当前数
的后面的数做比较
begin : CMP_After
assign cmp_result[i - 1][j - 1]=
((sort_row_temp[i -
1]>= sort_row_temp[j - 1])) ? 1'b1 : 1'b0;
end
end
end
endgenerate
//将比较结果求和 得到排序序列
generate
begin : xhdl3
genvar i;
for (i = 0; i <= KSZ - 1; i = i + 1)
begin : CMP_r_sum
if (KSZ == 3)
begin : SUM_ksz_3
always @(posedge clk or negedge rst_n)
if (((~(rst_n))) == 1'b1)
begincmp_sum[i] <= {DW_MAX_NUM{1'b0}};
cmp_sum_r[i]<= {DW_MAX_NUM{1'b0}};
end
else
begin
cmp_sum_r[i]<= #1 (cmp_result[i][0]) +
(cmp_result[i][2]);
cmp_sum_temp[i]<= #1 (cmp_result[i]
[1]);
cmp_sum[i]<= #1 cmp_sum_r[i]+
cmp_sum_temp[i];
end
end
end
end
endgenerate
//查找待输出的排序序列ID
generate
if (KSZ == 3)
begin : dout_ksz_3
always @(posedge clk or negedge rst_n)
if (((~(rst_n))) == 1'b1)
dout_temp <= {DW{1'b0}};
else
begin
if (cmp_sum[0]== OUT_ID)
dout_temp <= sort_r2[0];else if (cmp_sum[1]== OUT_ID)
dout_temp <= sort_r2[1];
else if (cmp_sum[2]== OUT_ID)
dout_temp <= sort_r2[2];
end
end
endgenerate