FPGA设计篇之并行全排序
写在前面
在FPGA设计的过程中,有时候需要对一些数据进行排序,那么常见的排序算法有冒泡排序、并行全排序、双调排序等,它们之间的区别在这里就不进行过多的介绍,本文主要介绍并行全排序算法的原理以及RTL代码实现。
并行全排序算法原理
并行全排序算法,之所以叫并行全排序算法,是指其各个数据之间的比较的并行执行的。
并行全排序对于多少个数值的排序,都只需要2个时钟周期,但是随之需要排序数据个数的增加,其资源消耗呈指数上升。并行全排序算法排序的过程包括两个步骤:
(1)将数据中的每个数据与所有数据进行对比,如果对比结果为若为大于(a
>
\gt
>b),则对比结果为1,否则,为0;
(2)将步骤一中每个数据与其他数据对比的结果相加,得到值即为对应的大小顺序;
比如:对四个值1、7、5、2进行排序,那么其结果如下图所示。
可见,这里是符合我们预期要求的。但是还有个疑问,如果这组数值中存在两个相等的数值,那么结果会怎么样?比如:对四个值1、7、5、2进行排序,那么其结果如下。
这时候,由于用于排序的四个值中有两个都为7,相等,也就导致了这两个值对应的2排序值都为2,这在我们设计的过程中往往是不允许的,因为在多数情况下,我们需要将这个排序值作为其他模块中作为位索引的参数,那么我们对这两个相等的值做出区分,给他们不同的排序值(给第1个7的排序值赋为2,第2个7的排序值赋为3或者是给第1个7的排序值赋为3,第2个7的排序值赋为2)。
那么,我们通过这样一个办法将其区分:假设第用于对比的值为第i个(i = 0 ~ 3),用于被对比的值为第j个(j = 0 ~ 3),当i
>
\gt
>j时,对比采用大于等于(
⩾
\geqslant
⩾)符号进行对比,如果a
⩾
\geqslant
⩾b,则对比结果为1,否则,为0;而当i
⩽
\leqslant
⩽j时,对比采用大于(
>
\gt
>)符号进行对比,如果a
>
\gt
>b,则对比结果为1,否则,为0。按照这个方法对四个值:1、7、5、7再进行对比,得到排序值如下:
那么,这样的作用其实就是:如果用于排序的N个数值中,出现多个相等的值,则将原本序号更大的数值赋予更大的排序值。
并行全排序算法RTL实现
首先可以设计一个状态机,用于控制排序的进程,主要包括三个状态:初始化、排序、反转。
always @(posedge clk or negedge rst_n)
if(!rst_n)
FSM_state_sort <= Initial;
else
case(FSM_state_sort)
Initial:
begin
if(sort_sig)
FSM_state_sort <= Sort;
else
FSM_state_sort <= Initial;
end
Sort:
begin
if(cnt_sort == 1'd1)
FSM_state_sort <= Convert;
else
FSM_state_sort <= Sort;
end
Convert:
begin
FSM_state_sort <= Initial;
end
default: FSM_state_sort <= Initial;
endcase
在这里,首先状态机处于初始化状态,如果排序信号sort_sig为高电平,则进入排序状态,而在排序状态下,我们需要进行两个操作:数据两两相比较和对某个数据与其他数据的比较结果进行求和,那么这里需要两个时钟周期,于是在这里设置一个计数器cnt_sort,如果计数器计数到1,则进入反转状态下,将原本每个数据的下标根据排序结果进行调换。
那么,对数值进行两两并行比较的RTL代码如下:
always @(posedge clk or negedge rst_n)
if(!rst_n) begin //复位信号
for(i=0;i<DN;i=i+1) begin
temp[i] = 0;
end
end
else if(sort_sig) begin //排序开始信号
for(i=0;i<DN;i=i+1) begin
for(j=0;j<DN;j=j+1) begin
if(i>j) begin
if(data_unsort[i*DW+:DW]>=data_unsort[j*DW+:DW])
temp[i][j] <= 1;
else
temp[i][j] <= 0;
end
else begin
if(data_unsort[i*DW+:DW]>data_unsort[j*DW+:DW])
temp[i][j] <= 1;
else
temp[i][j] <= 0;
end
end
end
end
对比较结果求和代码如下:
always @(posedge clk or negedge rst_n)
if(!rst_n) begin //复位信号
sequence_sorted_temp <= 0;
end
else if((FSM_state_sort == Sort) && (cnt_sort == 1'd0)) begin //序列初始化
for(i=0;i<DN;i=i+1) begin
sequence_sorted_temp[i*DW_sequence+:DW_sequence] <= i;
end
end
else if(cnt_sort == 1'd1) begin //更新序列
for(i=0;i<DN;i=i+1) begin
sequence_sorted_temp[i*DW_sequence+:DW_sequence] <= temp[i][0]+temp[i][1]+temp[i][2]+temp[i][3]+temp[i][4]+temp[i][5]+temp[i][6]+temp[i][7];
end
end
else
sequence_sorted_temp <= sequence_sorted_temp;
根据排序结果对原本每个数据对应的下标进行调换代码如下:
always @(posedge clk or negedge rst_n)
if(!rst_n)
sequence_sorted <= 0;
else if(FSM_state_sort == Convert)
for(i=0;i<DN;i=i+1) begin
sequence_sorted[sequence_sorted_temp[i*DW_sequence+:DW_sequence]*DW_sequence+:DW_sequence] <= i;
end
else
sequence_sorted <= sequence_sorted;
Test_Bench
仿真代码如下。
`timescale 1ns/1ns
module tb_parallel_sort();
//--------------------------------------------
//参数定义
//--------------------------------------------
parameter DW = 8; //数据位宽
parameter DN = 8; //数据个数
parameter DW_sequence = $clog2(DN); //序号位宽
//--------------------------------------------
//端口定义
//--------------------------------------------
reg clk,rst_n;
reg sort_sig;
reg [DW*DN-1:0] data_unsort;
wire [DW_sequence*DN-1:0] sequence_sorted;
wire sort_finish;
wire [DW_sequence-1:0] sequence_sorted_0;
wire [DW_sequence-1:0] sequence_sorted_1;
wire [DW_sequence-1:0] sequence_sorted_2;
wire [DW_sequence-1:0] sequence_sorted_3;
wire [DW_sequence-1:0] sequence_sorted_4;
wire [DW_sequence-1:0] sequence_sorted_5;
wire [DW_sequence-1:0] sequence_sorted_6;
wire [DW_sequence-1:0] sequence_sorted_7;
assign sequence_sorted_0 = sequence_sorted[0*DW_sequence+:DW_sequence];
assign sequence_sorted_1 = sequence_sorted[1*DW_sequence+:DW_sequence];
assign sequence_sorted_2 = sequence_sorted[2*DW_sequence+:DW_sequence];
assign sequence_sorted_3 = sequence_sorted[3*DW_sequence+:DW_sequence];
assign sequence_sorted_4 = sequence_sorted[4*DW_sequence+:DW_sequence];
assign sequence_sorted_5 = sequence_sorted[5*DW_sequence+:DW_sequence];
assign sequence_sorted_6 = sequence_sorted[6*DW_sequence+:DW_sequence];
assign sequence_sorted_7 = sequence_sorted[7*DW_sequence+:DW_sequence];
//--------------------------------------------
//初始信号值
//--------------------------------------------
initial
begin
clk = 0;
rst_n = 0;
sort_sig = 1'b0;
#5
#10 rst_n = 1;
#10 sort_sig = 1'b1;
data_unsort[DW*0+:DW] = 'd29; //6
data_unsort[DW*1+:DW] = 'd19; //2
data_unsort[DW*2+:DW] = 'd9 ; //0
data_unsort[DW*3+:DW] = 'd22; //3
data_unsort[DW*4+:DW] = 'd27; //4
data_unsort[DW*5+:DW] = 'd28; //5
data_unsort[DW*6+:DW] = 'd92; //7
data_unsort[DW*7+:DW] = 'd9 ; //1
#10 sort_sig = 1'b0;
#1600
$stop;
end
always #5 clk <= ~clk;
//--------------------------------------------
//模块例化
//--------------------------------------------
parallel_sort
#(
.DN (DN ),
.DW (DW )
)
parallel_sort_inst
(
.clk(clk),
.rst_n(rst_n),
.sort_sig(sort_sig),
.data_unsort(data_unsort),
.sequence_sorted(sequence_sorted),
.sort_finish(sort_finish)
);
endmodule
仿真结果
在这里我们以8个数为例,这8个数分别为: a 0 a_0 a0=29, a 1 a_1 a1=19, a 2 a_2 a2=9, a 3 a_3 a3=22, a 4 a_4 a4=27, a 5 a_5 a5=28, a 6 a_6 a6=92, a 7 a_7 a7=9。那么,对这组数据进行升序排列后的结果应该是 a 2 a_2 a2( a 7 a_7 a7)< a 1 a_1 a1< a 3 a_3 a3< a 4 a_4 a4< a 5 a_5 a5< a 0 a_0 a0< a 6 a_6 a6,运行Test_Bench,得到以下结果,可见与我们理论上排序的结果一致,且只需要3个时钟周期,仿真通过。
写在最后
并行全排序是一种使用面积换速度的排序算法,无论对于多大的数据集,其排序时间仅需3个时钟周期,但是对于数据集中数据个数的上升,其电路面积呈指数上升,所以对于含有少量数据的数据集的排序,并行全排序是一种可选的方案。
好了,上面就是关于FPGA中实现并行全排序的一些学习笔记,如果有疑义的地方欢迎评论区友好探讨学习!!!!!(RTL代码)