读论文之《基于 FPGA 的并行全比较排序算法》

为什么介绍这种排序算法:最近在独立编写霍夫曼编码的verilog HDL设计程序,其中用到了排序模块,对一组数据进行排序,苦寻几天,最终找到了这个全新的适合硬件实现的排序算法,分享与此,以空间换时间的并行排序算法。十分感谢论文作者,看到这样的方法,我太激动了。

并行全比较排序算法介绍:

排序是一种重要的数据运算,传统的排序方法主要靠软件串行方式实现,包括冒泡法、选择法、计数法等,这些算法大多采用循环比较,运算费时,实时性差。不能满足工程上越来越高的实时性要求。实时性排序在工程计算中的要求越来越迫切。本文基于FPGA的硬件特点,提出了一种全新的并行全比较排序算法,又可被称为“以空间换时间”并行排序算法,可大幅提高数据处理的实时性。

并行全比较排序算法原理:

一组数据,先进行两两之间的比较,每两个数比较都会得到一个比较结果。可以根据两数的大小定义输出排序结果0或1。对这些比较结果进行累加计算,即可得到该数在序列中的排序值。由于所有数的两两之间的比较都在硬件内同时进行,只需一个时钟的时间,即可得到比较结果,再加上比较结果的和加等计算时间,几个时钟周期,就实现了数字序列的排序。

实例介绍:

假设有一数组{20,80,40,40,60,70}, 定为A0=20、A1=80、A2=40、A3=40、A4=60、A5=70,要求对该数组按从大到小的顺序排列。排序按以下过程进行:

输入比较器的选取(认真读懂):

要进行数据之间的两两比较,首先要确定比较器。考虑到数组中有相同数据,并且后续还要进行和加计算。数据相同的排序按照原数据谁在前谁优先原则,要实现这种原则下的排序,在每个数据与其它数据比较时,根据两个数字原来在数组中的顺序,比较器的类型要发生变化。比如Am和其它数比较时,在与数An比较时:

如果m>n,则选用“≥”比较器;

如果m<n,则选用“>”比较器。

采用这种方式,既为后续的两两比较结果计算提供比较基础,又避免了数组中两个数字相同时的排序问题。

注意:为什么一会采用不同的比较器类型,是因为数据相同的排序按照原数据谁在前谁优先原则,采用上述比较器规定的类型,恰好解决这个问题。事实上,个人认为用一种类型的比较器也行,数据相同的排序,谁在前在后应该是无所谓的。

进行各数之间的比较
为了后续的计算,先作统一规定,如果数Am和数An相比较,如果Am≥An(或Am>An),则比较结果Z=1;否则,则Z=0。

各数之间比较结果如下表:


比较结果累加计算
各数字两两比较后,对与一个数相比较的所有值的结果相加(COMP(X,Y)为比较器),以A0为例,其对应的比较累加和为:

由表可知计算结果,CHA0=0,在序列中从大到小的顺序为0(数列的最大值排序为第5位),也即是在序列中为最小值。同样,A1、A2、A3、A4、A5 的比较值累加和分别为CH A1=5,CH A2=1,CH A3=2;CH A4=3,CH A5=4。那么,各数字在数组中的排序位置已经确定,各值从大到小排列为:A1、A5、A4、A3、A2、A0。

排序结果输出

各数字的排序位置确定后,要把排序结果输出。

算法基于verilog HDL语言描述:

排序算法在FPGA内进行,实现过程主要有以下几步,采用verilog语言来描述:
(1)第一个时钟周期,数据全比较程序,6个数据排序,输入数据为in0~in5:

//in0和其他数据进行比较

if(in0>in1) a0<=1; else a0<=0;
if(in0>in2) a1<=1; else a1<=0;
if(in0>in3) a2<=1; else a2<=0;
⋯⋯
//in1和其他数据进行比较
if(in1>in0) b0<=1; else b0<=0;
if(in1>in2) b1<=1; else b1<=0;
if(in1>in3) b2<=1; else b2<=0;
⋯⋯
//in2和其他数据进行比较
if(in2>in0) c0<=1; else c0<=0;
if(in2>in1) c1<=1; else c1<=0;
if(in2>in3) c2<=1; else c2<=0;
⋯⋯
(2)第二个时钟周期,比较值累加,mid0、mid1、mid2等为比较累
加值
mid0<=a0+a1+a2+a3+a4;
mid1<=b0+b1+b2+b3+b4;
mid2<=c0+c1+c2+c3+c4;
⋯⋯
(3)第三个时钟周期,把输入值赋给其对应的排序空间
chn[mid0]=in0;
chn[mid1]=in1;
chn[mid2]=in2;
⋯⋯
(4)第四个时钟周期,把排序结果输出
out 0<=chn[0]; //最小值
out 1<= chn[1]; //次小值
out 2<= chn[2];
⋯⋯

经过以上几个步骤,算法完成,整个算法共需要4个时钟周期。

并行全比较排序性能分析:

空间复杂性
由于并行全比较排序算法需要全并行处理,因此占用了大量的处理空间,又可称之为“空间换时间”排序算法。经计算统计,如果需

要排序的总数字个数为n,则并行排序算法所需要的比较器为(n-1)的2次方个,每个比较器在FPGA中设计占用逻辑单元为5个左右,那么排序需要的逻辑单元为5×(n-1)的2次方个。

时间复杂性
在FPGA内实现并行全比较算法,只需4个时钟周期就可实现排序计算。时间复杂度为定值。如果FPGA的时钟周期为10ns,那么整

个排序算法时间只有40ns。而传统的排序算法比如冒泡法,时间复杂度为n(n-1)/2,按照时钟周期10ns计算,100个数的冒泡排序时间可49.5μs,远远大于并行排序算法时间,这其中还不包括相关循环计算时间。

结论:

从上述论述可知,全比较排序算法基于FPGA技术,实现数据排序并行处理,排序运算只需要几个时钟的运算时间,并且运算时间不随排序数据量变化,达到了实时性排序的效果。该算法具有通用性,可以应用到各种数据快速排序运算领域。

对10个数进行并行排序,正式给出verilog HDL设计代码:

//对10个数进行并行排序
module sort_paralell(clk, rst, in0, in1, in2, in3, in4, in5, in6, in7,
						in8, in9, out0, out1, out2, out3, out4, out5, out6,
						out7, out8, out9);
						
		input clk;
		input rst;
		input [7:0] in0, in1, in2, in3, in4, in5, in6, in7, in8, in9;
		reg [7:0] out_temp[9:0];//输出数据放入此数组中
		output [7:0] out0, out1, out2, out3, out4, out5, out6, out7, out8, out9;
		reg [7:0] out0, out1, out2, out3, out4, out5, out6, out7, out8, out9;
		//下面定义的变量用于存储比较结果,如in0 > in1,则a0 <= 1,否则a0 <= 0; 
		reg  a0, a1, a2, a3, a4, a5, a6, a7, a8;
		reg  b0, b1, b2, b3, b4, b5, b6, b7, b8;
		reg  c0, c1, c2, c3, c4, c5, c6, c7, c8;
		reg  d0, d1, d2, d3, d4, d5, d6, d7, d8;
		reg  e0, e1, e2, e3, e4, e5, e6, e7, e8;
		reg  f0, f1, f2, f3, f4, f5, f6, f7, f8;
		reg  g0, g1, g2, g3, g4, g5, g6, g7, g8;
		reg  h0, h1, h2, h3, h4, h5, h6, h7, h8;
		reg  i0, i1, i2, i3, i4, i5, i6, i7, i8;
		reg  j0, j1, j2, j3, j4, j5, j6, j7, j8;
		
		reg add_start; //该变量的作用是判断比较是否结束,比较结束后赋值为1,进入相加模块
		reg assignm_start; //该变量作用在于判断相加模块执行是否结束,结束后赋值为1,进入下一个输出模块
		//下面定义的变量用于存储上述中间变量累加结果,9个1位2进制数相加最多4位
		reg out_start;
		reg [3:0] mid0, mid1, mid2, mid3, mid4, mid5, mid6, mid7, mid8, mid9;
		
		//并行比较模块(第一个时钟)
		always @ (posedge clk)
			begin
				if(rst)
					begin
						{a0, a1, a2, a3, a4, a5, a6, a7, a8} <= 9'b0000_0000_0;
						{b0, b1, b2, b3, b4, b5, b6, b7, b8} <= 9'b0000_0000_0;
						{c0, c1, c2, c3, c4, c5, c6, c7, c8} <= 9'b0000_0000_0;
						{d0, d1, d2, d3, d4, d5, d6, d7, d8} <= 9'b0000_0000_0;
						{e0, e1, e2, e3, e4, e5, e6, e7, e8} <= 9'b0000_0000_0;
						{f0, f1, f2, f3, f4, f5, f6, f7, f8} <= 9'b0000_0000_0;
						{g0, g1, g2, g3, g4, g5, g6, g7, g8} <= 9'b0000_0000_0;
						{h0, h1, h2, h3, h4, h5, h6, h7, h8} <= 9'b0000_0000_0;
						{i0, i1, i2, i3, i4, i5, i6, i7, i8} <= 9'b0000_0000_0;
						{j0, j1, j2, j3, j4, j5, j6, j7, j8} <= 9'b0000_0000_0;
						{mid0, mid1, mid2, mid3, mid4, mid5, mid6, mid7, mid8, mid9} <= 
						40'b0000_0000_0000_0000_0000_0000_0000_0000_0000_0000;
						out0 <= 0; out1 <= 0; out2 <= 0; out3 <= 0;out4 <= 0; out5 <= 0;
						out6 <= 0; out7 <= 0;out8 <= 0; out9 <= 0;
						add_start <= 0;
						assignm_start <= 0;
						out_start <= 0;
					end
				else
					begin
						if(in0 > in1) a0 <= 1'b1; //in0和所有除自己外的其他数据比较,大于则标志置1
						else a0 <= 1'b0;
						if(in0 > in2) a1 <= 1'b1;
						else a1 <= 1'b0;
						if(in0 > in3) a2 <= 1'b1;
						else a2 <= 1'b0;
						if(in0 > in4) a3 <= 1'b1;
						else a3 <= 1'b0;
						if(in0 > in5) a4 <= 1'b1;
						else a4 <= 1'b0;
						if(in0 > in6) a5 <= 1'b1;
						else a5 <= 1'b0;
						if(in0 > in7) a6 <= 1'b1;
						else a6 <= 1'b0;
						if(in0 > in8) a7 <= 1'b1;
						else a7 <= 1'b0;
						if(in0 > in9) a8 <= 1'b1;
						else a8 <= 1'b0;
						
						if(in1 > in0) b0 <= 1'b1;//in1和所有除自己外的数据比较,大于标志位置1,否则为0
						else b0 <= 1'b0;
						if(in1 > in2) b1 <= 1'b1;
						else b1 <= 1'b0;
						if(in1 > in3) b2 <= 1'b1;
						else b2 <= 1'b0;
						if(in1 > in4) b3 <= 1'b1;
						else b3 <= 1'b0;
						if(in1 > in5) b4 <= 1'b1;
						else b4 <= 1'b0;
						if(in1 > in6) b5 <= 1'b1;
						else b5 <= 1'b0;
						if(in1 > in7) b6 <= 1'b1;
						else b6 <= 1'b0;
						if(in1 > in8) b7 <= 1'b1;
						else b7 <= 1'b0;
						if(in1 > in9) b8 <= 1'b1;
						else b8 <= 1'b0;
						
						if(in2 > in0) c0 <= 1'b1;
						else c0 <= 1'b0;
						if(in2 > in1) c1 <= 1'b1;
						else c1 <= 1'b0;
						if(in2 > in3) c2 <= 1'b1;
						else c2 <= 1'b0;
						if(in2 > in4) c3 <= 1'b1;
						else c3 <= 1'b0;
						if(in2 > in5) c4 <= 1'b1;
						else c4 <= 1'b0;
						if(in2 > in6) c5 <= 1'b1;
						else c5 <= 1'b0;
						if(in2 > in7) c6 <= 1'b1;
						else c6 <= 1'b0;
						if(in2 > in8) c7 <= 1'b1;
						else c7 <= 1'b0;
						if(in2 > in9) c8 <= 1'b1;
						else c8 <= 1'b0;
						
						if(in3 > in0) d0 <= 1'b1;
						else d0 <= 1'b0;
						if(in3 > in1) d1 <= 1'b1;
						else d1 <= 1'b0;
						if(in3 > in2) d2 <= 1'b1;
						else d2 <= 1'b0;
						if(in3 > in4) d3 <= 1'b1;
						else d3 <= 1'b0;
						if(in3 > in5) d4 <= 1'b1;
						else d4 <= 1'b0;
						if(in3 > in6) d5 <= 1'b1;
						else d5 <= 1'b0;
						if(in3 > in7) d6 <= 1'b1;
						else d6 <= 1'b0;
						if(in3 > in8) d7 <= 1'b1;
						else d7 <= 1'b0;
						if(in3 > in9) d8 <= 1'b1;
						else d8 <= 1'b0;
						
						if(in4 > in0) e0 <= 1'b1;
						else e0 <= 1'b0;
						if(in4 > in1) e1 <= 1'b1;
						else e1 <= 1'b0;
						if(in4 > in2) e2 <= 1'b1;
						else e2 <= 1'b0;
						if(in4 > in3) e3 <= 1'b1;
						else e3 <= 1'b0;
						if(in4 > in5) e4 <= 1'b1;
						else e4 <= 1'b0;
						if(in4 > in6) e5 <= 1'b1;
						else e5 <= 1'b0;
						if(in4 > in7) e6 <= 1'b1;
						else e6 <= 1'b0;
						if(in4 > in8) e7 <= 1'b1;
						else e7 <= 1'b0;
						if(in4 > in9) e8 <= 1'b1;
						else e8 <= 1'b0;
						
						if(in5 > in0) f0 <= 1'b1;
						else f0 <= 1'b0;
						if(in5 > in1) f1 <= 1'b1;
						else f1 <= 1'b0;
						if(in5 > in2) f2 <= 1'b1;
						else f2 <= 1'b0;
						if(in5 > in3) f3 <= 1'b1;
						else f3 <= 1'b0;
						if(in5 > in4) f4 <= 1'b1;
						else f4 <= 1'b0;
						if(in5 > in6) f5 <= 1'b1;
						else f5 <= 1'b0;
						if(in5 > in7) f6 <= 1'b1;
						else f6 <= 1'b0;
						if(in5 > in8) f7 <= 1'b1;
						else f7 <= 1'b0;
						if(in5 > in9) f8 <= 1'b1;
						else f8 <= 1'b0;
						
						if(in6 > in0) g0 <= 1'b1;
						else g0 <= 1'b0;
						if(in6 > in1) g1 <= 1'b1;
						else g1 <= 1'b0;
						if(in6 > in2) g2 <= 1'b1;
						else g2 <= 1'b0;
						if(in6 > in3) g3 <= 1'b1;
						else g3 <= 1'b0;
						if(in6 > in4) g4 <= 1'b1;
						else g4 <= 1'b0;
						if(in6 > in5) g5 <= 1'b1;
						else g5 <= 1'b0;
						if(in6 > in7) g6 <= 1'b1;
						else g6 <= 1'b0;
						if(in6 > in8) g7 <= 1'b1;
						else g7 <= 1'b0;
						if(in6 > in9) g8 <= 1'b1;
						else g8 <= 1'b0;
						
						if(in7 > in0) h0 <= 1'b1;
						else h0 <= 1'b0;
						if(in7 > in1) h1 <= 1'b1;
						else h1 <= 1'b0;
						if(in7 > in2) h2 <= 1'b1;
						else h2 <= 1'b0;
						if(in7 > in3) h3 <= 1'b1;
						else h3 <= 1'b0;
						if(in7 > in4) h4 <= 1'b1;
						else h4 <= 1'b0;
						if(in7 > in5) h5 <= 1'b1;
						else h5 <= 1'b0;
						if(in7 > in6) h6 <= 1'b1;
						else h6 <= 1'b0;
						if(in7 > in8) h7 <= 1'b1;
						else h7 <= 1'b0;
						if(in7 > in9) h8 <= 1'b1;
						else h8 <= 1'b0;
						
						if(in8 > in0) i0 <= 1'b1;
						else i0 <= 1'b0;
						if(in8 > in1) i1 <= 1'b1;
						else i1 <= 1'b0;
						if(in8 > in2) i2 <= 1'b1;
						else i2 <= 1'b0;
						if(in8 > in3) i3 <= 1'b1;
						else i3 <= 1'b0;
						if(in8 > in4) i4 <= 1'b1;
						else i4 <= 1'b0;
						if(in8 > in5) i5 <= 1'b1;
						else i5 <= 1'b0;
						if(in8 > in6) i6 <= 1'b1;
						else i6 <= 1'b0;
						if(in8 > in7) i7 <= 1'b1;
						else i7 <= 1'b0;
						if(in8 > in9) i8 <= 1'b1;
						else i8 <= 1'b0;
						
						if(in9 > in0) j0 <= 1'b1;
						else j0 <= 1'b0;
						if(in9 > in1) j1 <= 1'b1;
						else j1 <= 1'b0;
						if(in9 > in2) j2 <= 1'b1;
						else j2 <= 1'b0;
						if(in9 > in3) j3 <= 1'b1;
						else j3 <= 1'b0;
						if(in9 > in4) j4 <= 1'b1;
						else j4 <= 1'b0;
						if(in9 > in5) j5 <= 1'b1;
						else j5 <= 1'b0;
						if(in9 > in6) j6 <= 1'b1;
						else j6 <= 1'b0;
						if(in9 > in7) j7 <= 1'b1;
						else j7 <= 1'b0;
						if(in9 > in8) j8 <= 1'b1;
						else j8 <= 1'b0;
						
						add_start <= 1; //比较结束标志,相加开始标志
					end
			
			end
	//相加模块,mid(i)的值代表着in(i)所在输出数组中的位置,(第二个时钟)
	always @ (posedge clk)
		begin
			if(add_start == 1)
				begin
					mid0 <= a0 + a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8; //标志位相加,所得结果就是其所在位置
					mid1 <= b0 + b1 + b2 + b3 + b4 + b5 + b6 + b7 + b8;
					mid2 <= c0 + c1 + c2 + c3 + c4 + c5 + c6 + c7 + c8;
					mid3 <= d0 + d1 + d2 + d3 + d4 + d5 + d6 + d7 + d8;
					mid4 <= e0 + e1 + e2 + e3 + e4 + e5 + e6 + e7 + e8;
					mid5 <= f0 + f1 + f2 + f3 + f4 + f5 + f6 + f7 + f8;
					mid6 <= g0 + g1 + g2 + g3 + g4 + g5 + g6 + g7 + g8;
					mid7 <= h0 + h1 + h2 + h3 + h4 + h5 + h6 + h7 + h8;
					mid8 <= i0 + i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8;
					mid9 <= j0 + j1 + j2 + j3 + j4 + j5 + j6 + j7 + j8;	
				end
				assignm_start <= 1;//相加结束,赋值开始标志
		end
		
		//输出模块,将排序好的数据放入输出数组中(第三个时钟)
	always @ (posedge clk)
		begin
			if(assignm_start == 1)
				begin
					out_temp[mid0] <= in0;
					out_temp[mid1] <= in1;
					out_temp[mid2] <= in2;
					out_temp[mid3] <= in3;
					out_temp[mid4] <= in4;
					out_temp[mid5] <= in5;
					out_temp[mid6] <= in6;
					out_temp[mid7] <= in7;
					out_temp[mid8] <= in8;
					out_temp[mid9] <= in9;
					
					out_start <= 1;//赋值结束,输出开始标志位
				end
		end
	always @ (posedge clk)
		begin
			if(out_start == 1)
				begin
					out0 <= out_temp[0];
					out1 <= out_temp[1];
					out2 <= out_temp[2];
					out3 <= out_temp[3];
					out4 <= out_temp[4];
					out5 <= out_temp[5];
					out6 <= out_temp[6];
					out7 <= out_temp[7];
					out8 <= out_temp[8];
					out9 <= out_temp[9];
				end			
		end
	
	
	

endmodule

测试文件为:

`timescale 1ns/1ps;
module sort_paralell_tb;
	reg clk, rst;
	reg [7:0] din0, din1, din2, din3, din4, din5, din6, din7, din8, din9;
	wire [7:0] dout0, dout1, dout2, dout3, dout4, dout5, dout6, dout7, dout8, dout9;
	
	always
		begin
			#10 clk = ~clk;
		end
		
	initial
		begin
			clk = 1'b0;
			rst = 1'b1;
			din0 = 20; din1 = 25; din2 = 52; din3 = 32; din4 = 12;
			din5 = 98; din6 = 44; din7 = 8; din8 = 86; din9 = 22;
			#10 rst = 1'b0;
		end
		
	sort_paralell u1(.clk(clk), .rst(rst), .in0(din0), .in1(din1), .in2(din2),
					.in3(din3), .in4(din4), .in5(din5), .in6(din6), .in7(din7),
					.in8(din8), .in9(din9), .out0(dout0), .out1(dout1), .out2(dout2),
					.out3(dout3), .out4(dout4), .out5(dout5), .out6(dout6), .out7(dout7),
					.out8(dout8), .out9(dout9));


endmodule

在Modelsim中仿真所得结果为:


放大图:


完美排序,只需要4个时钟周期。

心得:互联网上的东西杂乱不堪,几乎没有一个博客能够把问题讲的清清楚楚,更很少有有贴出代码的。

把一个问题从头到尾搞的清清楚楚的,并给出自己的总结,这是我们搞研究的人最最开心的事,希望秉持这种态度,继续提升自己。

找到这个论文不容易,首先真心感激作者创新型的想法,尽管论文当中也有一些错误的地方,但本人根据自己的理解,在博客中作出了改正,并写出了verilog HDL设计代码,以及测试代码,在Modelsim中仿真的也很成功。事实上,写这个排序算法的硬件实现并不是目的,目的是接下来的哈弗曼编码的Verilog HDL实现,排序算法是其中的一个模块。

猜你喜欢

转载自blog.csdn.net/reborn_lee/article/details/80469391