Verilog/C++实现排序算法
1、冒泡排序算法
冒泡排序是一种简单的交换类排序。
冒泡排序算法的原理如下:
- 1、比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 2、对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
- 3、针对所有的元素重复以上的步骤,除了最后一个。
- 4、持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,是不会再交换的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。
1.1、C++实现代码如下:
//交换 a 和 b 的位置
void swap(int *a, int *b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
//冒泡排序实现函数,从输出结果,可以看到冒泡的具体实现流程
void BubSort_test(int *a, int N)
{
for (int i = 0; i < N; i++)
{
//对待排序序列进行冒泡排序
for (int j = 0; j + 1 < N - i; j++)
{
//相邻元素进行比较,当顺序不正确时,交换位置
if (a[j] > a[j + 1])
{
swap(&a[j], &a[j + 1]);
}
}
/*///-------输出本轮冒泡排序之后的序列----------
printf("第%d轮冒泡排序:", i + 1);
for (int i = 0; i < N; i++)
{
printf("%d ", a[i]);
}
printf("\n");
-------输出本轮冒泡排序之后的序列----------///*/
}
}
1.2、Verilog实现代码如下:
1.2.1、冒泡排序代码:
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2023/06/19 21:28:28
// Design Name:
// Module Name: bubbling
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
//冒泡排序
module bubbling #(
parameter ROW_WIDTH = 8, //行
parameter COL_WIDTH = 16 //列
)
(
input wire clk,
input wire rst,
input [15:0] comp_data_i, //数据
input valid_i //数据有效
);
//---------------------------------------------------
bubbling_sort u_bubbling_sort(
.clk ( clk ),
.rst ( rst ),
.comp_data_i ( comp_data_i ), //输入数据
.valid_i ( valid_i ), //数据有效
.sort_done ( sort_done ) //排序完成
);
endmodule
1.2.2、冒泡排序核心代码:
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2023/06/19 21:32:23
// Design Name:
// Module Name: bubbling_sort
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module bubbling_sort #(
parameter ROW_WIDTH = 8, //行
parameter COL_WIDTH = 16 //列
)
(
input wire clk,
input wire rst,
input wire [15:0] comp_data_i, //数据
input wire valid_i, //数据有效
output reg sort_done //排序完成
);
//reg define
reg comp_valid_f;
reg [15:0] comp_data [COL_WIDTH:1];
reg [7:0] count;
reg [7:0] count_i;
reg [7:0] count_j;
//***********************************************
//** 读取数据
//***********************************************
always@(posedge clk or posedge rst) begin
if(rst) begin
comp_valid_f <= 0;
count <= 1;
end
else if(valid_i) begin
comp_data[count] <= comp_data_i;
count <= count + 1;
if( count==COL_WIDTH )
comp_valid_f <= 1;
else
comp_valid_f <= 0;
end
else begin
comp_valid_f <= 0;
count <= 1;
end
end
//-----------------------------------------------
always@(posedge clk or posedge rst) begin
if(rst) begin
count_i <= COL_WIDTH + 1;
count_j <= COL_WIDTH + 1;
end
else if(count_i < COL_WIDTH) begin
if(count_j < COL_WIDTH - count_i + 1) begin
count_j <= count_j + 1;
if(comp_data[count_j] < comp_data[count_j+1]) begin
comp_data[count_j] <= comp_data[count_j+1];
comp_data[count_j+1] <= comp_data[count_j];
end
else begin
;
end
end
else begin
count_i <= count_i + 1;
count_j <= 1;
end
end
else if(comp_valid_f) begin
count_i <= 1;
count_j <= 1;
end
else begin
count_i <= count_i;
count_j <= count_j;
end
end
//----------------------------------------------------
always@(posedge clk or posedge rst) begin
if(rst)
sort_done <= 0;
else if((count_i== (COL_WIDTH-1))&& (count_j== 2))
sort_done <= 1;
else
sort_done <= 0;
end
endmodule
1.2.3、冒泡排序顶层代码:
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2023/06/14 21:10:41
// Design Name:
// Module Name: main
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
//FPGA 排序算法
module main(
input clk,
input rst,
input enable
);
//parameter define
parameter ROW_WIDTH = 8; //行
parameter COL_WIDTH = 16; //列
reg [15:0] comp_data_i; //数据
reg valid_i; //数据有效
reg [15:0] data [COL_WIDTH:1];
reg [7:0] count;
//初始化数据---输入需要排序的数据
initial begin
data[1] = 16'd20;
//data[2] = 16'd723;
data[2] = 16'd20;
data[3] = 16'd12;
data[4] = 16'd456;
data[5] = 16'd278;
data[6] = 16'd9756;
data[7] = 16'd433;
data[8] = 16'd10000;
data[9] = 16'd21;
data[10] = 16'd724;
data[11] = 16'd15;
data[12] = 16'd458;
data[13] = 16'd279;
data[14] = 16'd9758;
data[15] = 16'd439;
data[16] = 16'd30;
end
//***********************************************
//** 读取数据
//***********************************************
always@(posedge clk or posedge rst) begin
if(rst) begin
count <= COL_WIDTH + 1;
valid_i <= 0;
comp_data_i <= 0;
end
else if(enable) begin
count <= 1;
end
else if(count <= COL_WIDTH) begin
comp_data_i <= data[count];
count <= count + 1;
valid_i <= 1;
end
else begin
valid_i <= 0;
count <= count;
end
end
//***********************************************
//** 冒泡排序算法
//***********************************************
bubbling u2_bubbling(
.clk (clk ),
.rst (rst ),
.comp_data_i (comp_data_i ),
.valid_i (valid_i )
);
endmodule
1.2.4、冒泡排序仿真代码:
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2023/06/14 21:15:30
// Design Name:
// Module Name: tb_main
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module tb_main();
// Inputs
reg clk;
reg rst;
reg enable;
//parameter CLK_PERIOD= 20; //50MHz系统时钟(一个周期是20ns:1/50MHz=0.02us=20ns)
//parameter CLK_PERIOD = 10; //100MHz系统时钟(一个周期是10ns:1/100MHz=0.01us=10ns)
parameter CLK_PERIOD = 5; //200MHz系统时钟(一个周期是5ns:1/200MHz=0.005us=5ns)
// Instantiate the Unit Under Test (UUT)
main u_main(
.clk(clk),
.enable(enable),
.rst(rst)
);
always #(CLK_PERIOD/2) clk = ~clk;
initial begin
// Initialize Inputs
clk = 0;
rst = 1;
enable = 0;
// Wait 100 ns for global reset to finish
#100;
rst = 0;
// Add stimulus here
enable = 1;
#10;
enable = 0;
#5000;
enable = 1;
#10;
enable = 0;
end
endmodule
仿真结果如下:
从仿真波形图中可以看出,当 sort_done 为高电平时,表示排序完成。
2、选择排序算法
选择排序法是一种不稳定的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到全部待排序的数据元素排完。
选择排序法是在要排序的一组数中,选出最小(或最大)的一个数与第一个位置的数交换;在剩下的数当中找最小的与第二个位置的数交换,即顺序放在已排好序的数列的最后,如此循环,直到全部数据元素排完为止。
2.1、C++实现代码如下:
方式一:
//交换两个数据
//交换 a 和 b 的位置
void swap(int *a, int *b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
//选择排序
void SelectSort(int* arr, int size)
{
int i = 0;
for (i = 0; i < size-1; i++)
{
int min = i;
int j = 0;
for (j = i+1; j < size; j++)
{
if (arr[j] < arr[min])
{
min = j;
}
}
swap(&arr[i], &arr[min]);
}
}
方式二:
//选择排序
void select_sort(int *p, int n)
{
int i,j;
int min = 0;
for(i = 0; i<n-1; i++)//排序次数
{
min = i;
for(j=i+1; j<n; j++)
{
if(p[j] < p[min])
{
min = j;//记录交换的元素下标值
}
}
if(i != min)
{
int temp = p[i];
p[i] = p[min];
p[min] = temp;
}
}
}
2.2、Verilog实现代码如下:
选择排序法和冒泡法属于传统的两两比较的算法,但是消耗的周期比较长,在一些对实时性要求较高的情况下无法满足要求。
3、并行全比较排序法
传统的排序方式是以两两之间顺序比较为基础,而并行全比较实时排序算法是基于序列中任意两个数并行比较实现。由于从原来的串行比较变成了并行比较,所以需要消耗比以前多的比较器,诠释了FPGA中 “用面积换速度” 的思想。
并行全比较算法就是一种以FPGA的资源换取排序时间的算法。
- 1、第一个时钟周期:将其中一个数据和其他数据在一个周期中一一比较,比较器分三种情况:
- 1.1、这个数据大于其他数据,则它的得分为0;
- 1.2、这个数据等于其他数据,若它在这个序列中比和它相等的其他数据靠前,则它的得分为0,反之为1;
- 1.3、这个数据小于其他数据,则它的得分为1;
- 2、第二个时钟周期:将每个数据和其他数据比较后的得分数据累加;
- 3、第三个时钟周期:将每个数据根据自己的得分高低分别赋值给新的数组(若得分为1的就赋值给数组中的第一个数,2就赋值给新的数组中第二个数);
- 4、第四个时钟周期:将新数组输出;
经过以上四个步骤,即可将算法完成。
3.1、C++实现代码如下:
3.2、Verilog实现代码如下:
1、第一个时钟周期:将其中一个数据和其他数据在一个周期中一一比较
2、第二个时钟周期:将每个数据和其他数据比较后的得分数据累加;
3、第三个时钟周期:将每个数据根据自己的得分高低分别赋值给新的数组(若得分为1的就赋值给数组中的第一个数,2就赋值给新的数组中第二个数);
4、第四个时钟周期:将新数组输出;
- 优点:并行比较排序方式在实时性上有明显的优势,只需要四个时钟周期就可以排序完成;
- 缺点:
- 1.由于是并行比较消耗了较多的资源,而且在第二个时钟周期(得分累加)需要大量的加法器级联,考虑到路径延迟、建立保持时间和时钟抖动,一个时钟周期许多个加法器级联会有问题;
- 2.在代码可移植性方面也有欠缺,比如若序列大小改变,在第二个和第三个时钟周期的时候就需要人为修改多处代码;
4、串行全比较排序法
串行全比较排序法在并行全比较排序法做了一些改进,将原来并行全比较排序法的前三个周期由并行转变为串行,但是可以在比较的同时将得分累加,所以串行全比较排序法排序需要的周期是2*m(m个序列)个周期。
4.1、C++实现代码如下:
4.2、Verilog实现代码如下:
串行全比较算法和并行全比较算法比较:
- 优点:
- 1、资源消耗的比较少;
- 2、代码可移植性好,序列变化只需要改变几个参数,不需要大规模修改代码;
- 缺点:串行全比较算法所消耗的时间比并行全比较算法长。
总结
- 代码可移植性:传统串行排序算法>串行全比较排序法>并行全比较排序法
- 资源使用:传统串行排序算法<串行全比较排序法<并行全比较排序法
- l排序时间:并行全比较排序法<串行全比较排序法<传统串行排序算法