ℹ️ Select 'Choose Exercise', or randomize 'Next Random Exercise' in selected language.

Choose Exercise:
Timer 00:00
WPM --
Score --
Acc --
Correct chars --

VHDL Bitonic Sort Network Generator

VHDL

Goal -- WPM

Ready
Exercise Algorithm Area
1library ieee;
2use ieee.std_logic_1164.all;
3use ieee.numeric_std.all;
4
5package bitonic_sort_pkg is
6-- Generic for the number of elements to sort (must be a power of 2).
7-- For example, N=8 means sorting 8 elements.
8constant N : integer := 8;
9
10-- Type for the data elements being sorted.
11-- Assuming unsigned integers for simplicity.
12subtype data_t is unsigned(31 downto 0);
13
14-- Type for the array of data elements.
15subtype data_array_t is data_t array(0 to N-1) of data_t;
16
17-- Component for a single compare-and-swap unit.
18-- Inputs a and b are compared. If dir = '1', a is swapped with b if a > b.
19-- If dir = '0', a is swapped with b if a < b.
20component compare_swap
21generic (
22DATA_WIDTH : integer
23);
24port (
25a : in unsigned(DATA_WIDTH-1 downto 0);
26b : in unsigned(DATA_WIDTH-1 downto 0);
27dir : in std_logic;
28y1 : out unsigned(DATA_WIDTH-1 downto 0);
29y2 : out unsigned(DATA_WIDTH-1 downto 0)
30);
31end component;
32
33-- Generates a bitonic sort network for N elements.
34-- The network sorts the input array in ascending order if dir = '1'.
35-- If dir = '0', it sorts in descending order.
36procedure generate_bitonic_sort_network (
37signal input_data : in data_array_t;
38signal output_data : out data_array_t;
39input dir : std_logic -- '1' for ascending, '0' for descending
40);
41
42-- Helper procedure to generate a bitonic merge stage.
43-- Merges two bitonic sequences of size k into a single bitonic sequence of size 2k.
44procedure generate_bitonic_merge (
45signal input_data : in data_array_t;
46signal output_data : out data_array_t;
47input k : integer; -- Size of sub-sequences
48input dir : std_logic -- Sort direction
49);
50
51-- Helper procedure to generate a bitonic sequence.
52-- Creates a bitonic sequence from an unsorted array of size k.
53procedure generate_bitonic_sequence (
54signal input_data : in data_array_t;
55signal output_data : out data_array_t;
56input k : integer; -- Size of the sequence to create
57input dir : std_logic -- Sort direction
58);
59
60end package bitonic_sort_pkg;
61
62package body bitonic_sort_pkg is
63
64-- Implementation of the compare_swap component
65component compare_swap
66generic (
67DATA_WIDTH : integer
68);
69port (
70a : in unsigned(DATA_WIDTH-1 downto 0);
71b : in unsigned(DATA_WIDTH-1 downto 0);
72dir : in std_logic;
73y1 : out unsigned(DATA_WIDTH-1 downto 0);
74y2 : out unsigned(DATA_WIDTH-1 downto 0)
75);
76end component;
77
78-- Procedure to generate a single compare-and-swap unit.
79procedure compare_swap_unit (
80signal a : in unsigned(DATA_WIDTH-1 downto 0);
81signal b : in unsigned(DATA_WIDTH-1 downto 0);
82input dir : std_logic;
83signal y1 : out unsigned(DATA_WIDTH-1 downto 0);
84signal y2 : out unsigned(DATA_WIDTH-1 downto 0)
85)
86is
87begin
88if dir = '1' then -- Ascending sort
89if a > b then
90y1 <= b;
91y2 <= a;
92else
93y1 <= a;
94y2 <= b;
95end if;
96else -- Descending sort
97if a < b then
98y1 <= b;
99y2 <= a;
100else
101y1 <= a;
102y2 <= b;
103end if;
104end if;
105end procedure;
106
107-- Instantiate the compare_swap component
108component compare_swap
109generic (
110DATA_WIDTH : integer := 32
111);
112port (
113a : in unsigned(DATA_WIDTH-1 downto 0);
114b : in unsigned(DATA_WIDTH-1 downto 0);
115dir : in std_logic;
116y1 : out unsigned(DATA_WIDTH-1 downto 0);
117y2 : out unsigned(DATA_WIDTH-1 downto 0)
118);
119end component;
120
121-- Helper procedure to generate a bitonic merge stage.
122-- Merges two bitonic sequences of size k into a single bitonic sequence of size 2k.
123procedure generate_bitonic_merge (
124signal input_data : in data_array_t;
125signal output_data : out data_array_t;
126input k : integer; -- Size of sub-sequences
127input dir : std_logic -- Sort direction
128)
129is
130-- Temporary array to hold intermediate results for this merge stage.
131variable temp_merge_array : data_array_t;
132-- Number of elements in the current merge stage.
133constant current_size : integer := 2 * k;
134begin
135-- Initialize temp array with input data for this stage.
136-- The input_data to this procedure is already structured for merging.
137for i in 0 to current_size - 1 loop
138temp_merge_array(i) := input_data(i);
139end loop;
140
141-- Perform compare-and-swap operations.
142-- For each element i, compare it with element i+k (or i-k depending on direction).
143-- The distance between compared elements decreases as we move through the stage.
144for j in 0 to k - 1 loop
145-- The distance between elements to compare is k.
146-- The indices are i and i+k for ascending, and i and i-k for descending.
147-- We need to be careful with index calculations to ensure we cover all pairs.
148-- The logic for bitonic merge is that element i is compared with element i+k.
149-- The indices for comparison are (i) and (i+k) for the first half, and
150-- (i+k) and (i) for the second half, but this is handled by the loop structure.
151-- The core idea is that for each pair (x, y) where y is k positions away from x,
152-- they are compared and swapped based on the direction.
153-- For a bitonic merge of size 2k, we have two bitonic sequences of size k.
154-- The first sequence is indices 0 to k-1, the second is k to 2k-1.
155-- We compare element i with element i+k.
156-- The loop for j iterates through the first half of the elements.
157-- The compare_swap operation is applied to pairs (temp_merge_array(j), temp_merge_array(j+k)).
158declare
159signal cs_y1, cs_y2 : data_t;
160begin
161compare_swap_unit(
162a => temp_merge_array(j),
163b => temp_merge_array(j+k),
164dir => dir,
165y1 => cs_y1,
166y2 => cs_y2
167);
168temp_merge_array(j) <= cs_y1;
169temp_merge_array(j+k) <= cs_y2;
170end;
171end loop;
172
173-- The result of the merge stage is now in temp_merge_array.
174-- Assign it to the output_data.
175for i in 0 to current_size - 1 loop
176output_data(i) <= temp_merge_array(i);
177end loop;
178
179end procedure generate_bitonic_merge;
180
181-- Helper procedure to generate a bitonic sequence.
182-- Creates a bitonic sequence from an unsorted array of size k.
183procedure generate_bitonic_sequence (
184signal input_data : in data_array_t;
185signal output_data : out data_array_t;
186input k : integer; -- Size of the sequence to create
187input dir : std_logic -- Sort direction
188)
189is
190-- Temporary array to hold intermediate results.
191variable temp_seq_array : data_array_t;
192-- Half the size of the current sequence.
193constant half_k : integer := k / 2;
194begin
195-- Base case: If k is 2, we just need to compare and swap the two elements.
196if k = 2 then
197declare
198signal cs_y1, cs_y2 : data_t;
199begin
200compare_swap_unit(
201a => input_data(0),
202b => input_data(1),
203dir => dir,
204y1 => cs_y1,
205y2 => cs_y2
206);
207output_data(0) <= cs_y1;
208output_data(1) <= cs_y2;
209end;
210else
211-- Recursive step: Create two bitonic sequences of size k/2.
212-- The first sequence is sorted in the same direction 'dir'.
213-- The second sequence is sorted in the opposite direction.
214-- This creates a bitonic sequence when concatenated.
215
216-- Initialize temp_seq_array with the first k elements of input_data.
217for i in 0 to k-1 loop
218temp_seq_array(i) := input_data(i);
219end loop;
220
221-- Recursively generate the first bitonic sequence (ascending).
222generate_bitonic_sequence(
223input_data => temp_seq_array,
224output_data => temp_seq_array, -- Output to same temp array for now
225k => half_k,
226dir => dir
227);
228
229-- Recursively generate the second bitonic sequence (descending).
230-- The second sequence starts from index half_k.
231-- We need to pass a view or slice of the array, or manage indices carefully.
232-- For simplicity, we'll create a temporary sub-array for the second sequence.
233variable second_half_input : data_array_t;
234for i in 0 to half_k - 1 loop
235second_half_input(i) := temp_seq_array(i + half_k);
236end loop;
237
238variable second_half_output : data_array_t;
239generate_bitonic_sequence(
240input_data => second_half_input,
241output_data => second_half_output,
242k => half_k,
243dir => not dir -- Opposite direction
244);
245
246-- Combine the two generated sequences back into temp_seq_array.
247for i in 0 to half_k - 1 loop
248temp_seq_array(i) := second_half_output(i);
249end loop;
250
251-- Now, temp_seq_array contains a bitonic sequence of size k.
252-- We need to merge these two halves.
253-- The generate_bitonic_merge procedure takes the first 2*half_k elements
254-- and merges them. Since temp_seq_array now holds the bitonic sequence,
255-- we can pass it to merge.
256generate_bitonic_merge(
257input_data => temp_seq_array,
258output_data => temp_seq_array, -- Merge into the same temp array
259k => half_k,
260dir => dir
261);
262
263-- Finally, copy the result to the output_data.
264for i in 0 to k - 1 loop
265output_data(i) := temp_seq_array(i);
266end loop;
267end if;
268end procedure generate_bitonic_sequence;
269
270-- Main procedure to generate the complete bitonic sort network.
271procedure generate_bitonic_sort_network (
272signal input_data : in data_array_t;
273signal output_data : out data_array_t;
274input dir : std_logic -- '1' for ascending, '0' for descending
275)
276is
277-- Temporary array to hold intermediate sorted stages.
278variable temp_sort_array : data_array_t;
279begin
280-- The overall sorting process involves creating a bitonic sequence first,
281-- and then repeatedly merging stages until the entire array is sorted.
282-- The generate_bitonic_sequence procedure effectively does the first part.
283-- The subsequent merges are handled by calling generate_bitonic_merge iteratively.
284
285-- Step 1: Create a bitonic sequence from the input data.
286generate_bitonic_sequence(
287input_data => input_data,
288output_data => temp_sort_array,
289k => N,
290dir => dir
291);
292
293-- Step 2: Perform subsequent merge stages to sort the entire array.
294-- The size of the sequences being merged doubles in each stage.
295-- We start with sequences of size 2, then 4, 8, ..., up to N.
296variable current_merge_size : integer := 2;
297while current_merge_size <= N loop
298-- For each merge size, we perform multiple merges across the array.
299-- The number of merges is N / current_merge_size.
300-- Each merge operation combines two bitonic sequences of size current_merge_size/2.
301-- The direction of merge alternates based on the overall sort direction.
302-- The logic here is that we are merging pairs of bitonic sequences.
303-- The distance between elements being compared in a merge stage is current_merge_size/2.
304-- The overall sort direction 'dir' determines the final sort order.
305-- Within each merge stage, the direction of comparison for pairs depends on their position.
306-- For a bitonic sort, the direction of merge alternates.
307-- The outer loop iterates through the merge stage size (2, 4, 8, ... N).
308-- The inner loop iterates through the starting index of each merge operation.
309-- The distance for comparison is half the current merge size.
310for i in 0 to N - 1 loop
311-- This loop structure is a bit tricky. It's not a direct loop over merge operations.
312-- Instead, it's about applying the compare-swap logic across the entire array
313-- for a given stage size.
314-- The bitonic sort network structure implies that for a given stage size 's',
315-- elements at index 'j' and 'j+s' are compared and swapped.
316-- The direction of swap depends on 'dir' and the position within the stage.
317-- The 'dir' parameter to generate_bitonic_merge dictates the sort order for that specific merge.
318-- For the overall network, the direction of merge alternates.
319-- The 'dir' parameter passed to generate_bitonic_merge should be 'dir' for the first half
320-- of the elements and 'not dir' for the second half, or vice versa.
321-- The standard bitonic sort network structure implies that for a merge stage of size 's',
322-- we compare element 'i' with element 'i+s'.
323-- The direction of comparison for element 'i' depends on whether 'i' is in the first or second half
324-- of the overall bitonic sequence being formed.
325-- A simpler way to think about it is that we are applying the merge logic.
326-- The generate_bitonic_merge procedure takes a bitonic sequence and merges it.
327-- Here, we are applying it iteratively.
328-- The 'dir' parameter for generate_bitonic_merge should be 'dir' if the element pair
329-- is part of an ascending sub-sequence, and 'not dir' if it's part of a descending sub-sequence.
330-- This is implicitly handled by the structure of the bitonic sort network.
331-- The key is that the 'generate_bitonic_merge' procedure is called with the correct 'k' and 'dir'.
332-- The 'k' parameter is half the size of the current merge stage.
333-- The 'dir' parameter for the merge depends on the overall sort direction 'dir'.
334-- For a bitonic sort network, the direction of comparison alternates.
335-- The loop for 'i' here is not directly for calling generate_bitonic_merge.
336-- It's more about how the compare-swap operations are distributed.
337-- Let's rethink the structure of the merge stages.
338-- After creating the initial bitonic sequence, we have N/2 merge operations of size 2.
339-- Then N/4 merge operations of size 4, and so on.
340-- The procedure generate_bitonic_merge merges two bitonic sequences of size k.
341-- So, to sort N elements, we call generate_bitonic_merge with k = N/2, then k = N/4, etc.
342-- The 'dir' parameter for generate_bitonic_merge needs to be correctly determined.
343-- For the first merge stage (k=N/2), we merge two bitonic sequences of size N/2.
344-- The direction of the merge for each pair depends on its position.
345-- The overall sort direction is 'dir'.
346-- The standard bitonic sort network structure implies that for a merge stage of size 's',
347-- the direction of comparison for element 'i' and 'i+s' depends on the bit pattern of 'i' and 's'.
348-- A common way to implement this is to have the 'dir' parameter for merge alternate.
349-- However, the 'generate_bitonic_merge' procedure itself takes a 'dir' parameter.
350-- The 'dir' parameter passed to generate_bitonic_merge should be the overall sort direction.
351-- The internal logic of generate_bitonic_merge handles the pairing correctly.
352-- The loop structure for 'i' should be over the number of merge operations at each stage.
353-- For a merge stage of size 'current_merge_size', there are N / current_merge_size merge operations.
354-- Each merge operation operates on a block of size 'current_merge_size'.
355-- The 'k' for generate_bitonic_merge is 'current_merge_size / 2'.
356-- The 'dir' for generate_bitonic_merge is 'dir'.
357-- The input to generate_bitonic_merge is the current state of temp_sort_array.
358-- The output is also to temp_sort_array.
359-- The loop for 'i' should iterate through the starting indices of these merge blocks.
360-- For a merge stage of size 'current_merge_size', the starting indices are 0, current_merge_size, 2*current_merge_size, etc.
361-- Let's restructure this loop.
362null;
363end loop;
364
365-- Corrected loop for merge stages:
366-- Iterate through the size of the sub-sequences being merged.
367-- This size doubles in each iteration of the outer loop.
368-- The 'k' for generate_bitonic_merge is half the current_merge_size.
369-- The 'dir' parameter for generate_bitonic_merge is the overall sort direction.
370-- The number of merge operations at this stage is N / current_merge_size.
371-- The starting index of each merge operation is 'm * current_merge_size'.
372for m in 0 to (N / current_merge_size) - 1 loop
373-- We need to pass a view or slice of temp_sort_array to generate_bitonic_merge.
374-- VHDL doesn't directly support array slicing for procedure arguments in this way.
375-- We need to copy the relevant portion to a temporary array.
376variable merge_input_block : data_array_t;
377variable merge_output_block : data_array_t;
378constant block_start_index : integer := m * current_merge_size;
379
380-- Copy the block to be merged into merge_input_block.
381for i in 0 to current_merge_size - 1 loop
382merge_input_block(i) := temp_sort_array(block_start_index + i);
383end loop;
384
385-- Perform the merge operation on this block.
386generate_bitonic_merge(
387input_data => merge_input_block,
388output_data => merge_output_block,
389k => current_merge_size / 2,
390dir => dir
391);
392
393-- Copy the merged block back into temp_sort_array.
394for i in 0 to current_merge_size - 1 loop
395temp_sort_array(block_start_index + i) := merge_output_block(i);
396end loop;
397end loop;
398
399current_merge_size := current_merge_size * 2;
400end loop;
401
402-- After all merge stages, temp_sort_array contains the sorted data.
403output_data <= temp_sort_array;
404
405end procedure generate_bitonic_sort_network;
406
407end package body bitonic_sort_pkg;
Algorithm description viewbox

VHDL Bitonic Sort Network Generator

Algorithm description:

This VHDL package defines a parameterized bitonic sort network generator. A bitonic sort network is a parallel sorting algorithm that uses a fixed number of comparators arranged in stages to sort data. It's particularly well-suited for hardware implementation due to its regular structure and lack of conditional branching in the data path. The package allows users to specify the number of elements to sort (N), which must be a power of 2, and the data width. It generates the necessary logic to sort an array of data elements either in ascending or descending order.

Algorithm explanation:

The bitonic sort algorithm works by first creating a bitonic sequence from the input data and then repeatedly merging these bitonic sequences until the entire array is sorted. A bitonic sequence is a sequence that is either monotonically increasing or decreasing, or it can be split into two halves, one bitonic increasing and the other bitonic decreasing, such that the elements in the first half are all less than or equal to the elements in the second half. The `generate_bitonic_sequence` procedure recursively builds such a sequence. It splits the input into two halves, recursively sorts them into bitonic sequences (one ascending, one descending), and then merges them. The `generate_bitonic_merge` procedure takes two bitonic sequences and merges them into a single sorted sequence by applying compare-and-swap operations. The `generate_bitonic_sort_network` procedure orchestrates the entire process, first calling `generate_bitonic_sequence` and then iteratively calling `generate_bitonic_merge` for increasing merge sizes until the entire array is sorted. The time complexity of a bitonic sort network for N elements is O(N log^2 N), and its space complexity is O(N log N) for the network structure itself. Edge cases include the base case of N=2, which is handled directly by a compare-swap. The recursive structure ensures correctness by maintaining the bitonic property at each step and progressively reducing the disorder until full sorting is achieved.

Pseudocode:

PACKAGE bitonic_sort_pkg IS
    CONSTANT N : integer -- Number of elements (power of 2)
    TYPE data_t IS UNSIGNED(31 DOWNTO 0)
    TYPE data_array_t IS ARRAY(0 TO N-1) OF data_t

    PROCEDURE generate_bitonic_sort_network(input_data : IN data_array_t, output_data : OUT data_array_t, dir : IN std_logic)
    PROCEDURE generate_bitonic_merge(input_data : IN data_array_t, output_data : OUT data_array_t, k : IN integer, dir : IN std_logic)
    PROCEDURE generate_bitonic_sequence(input_data : IN data_array_t, output_data : OUT data_array_t, k : IN integer, dir : IN std_logic)
END PACKAGE

PACKAGE BODY bitonic_sort_pkg IS
    PROCEDURE compare_swap_unit(a, b : IN data_t; dir : IN std_logic; y1, y2 : OUT data_t)
        -- Compares a and b and outputs them in sorted order based on dir
    END PROCEDURE

    PROCEDURE generate_bitonic_merge(input_data, output_data, k, dir)
        DECLARE temp_merge_array : data_array_t
        FOR j FROM 0 TO k-1 DO
            compare_swap_unit(temp_merge_array(j), temp_merge_array(j+k), dir, y1, y2)
            temp_merge_array(j) <= y1
            temp_merge_array(j+k) <= y2
        END FOR
        output_data <= temp_merge_array
    END PROCEDURE

    PROCEDURE generate_bitonic_sequence(input_data, output_data, k, dir)
        DECLARE temp_seq_array : data_array_t
        IF k = 2 THEN
            compare_swap_unit(input_data(0), input_data(1), dir, y1, y2)
            output_data(0) <= y1
            output_data(1) <= y2
        ELSE
            half_k = k / 2
            -- Recursively generate first half (same dir)
            generate_bitonic_sequence(input_data, temp_seq_array, half_k, dir)
            -- Recursively generate second half (opposite dir)
            generate_bitonic_sequence(input_data[half_k to k-1], temp_seq_array[half_k to k-1], half_k, NOT dir)
            -- Merge the two halves
            generate_bitonic_merge(temp_seq_array, temp_seq_array, half_k, dir)
            output_data <= temp_seq_array
        END IF
    END PROCEDURE

    PROCEDURE generate_bitonic_sort_network(input_data, output_data, dir)
        DECLARE temp_sort_array : data_array_t
        generate_bitonic_sequence(input_data, temp_sort_array, N, dir)
        current_merge_size = 2
        WHILE current_merge_size <= N DO
            FOR m FROM 0 TO (N / current_merge_size) - 1 DO
                -- Copy block to temp, merge, copy back
                generate_bitonic_merge(block, block, current_merge_size/2, dir)
            END FOR
            current_merge_size = current_merge_size * 2
        END WHILE
        output_data <= temp_sort_array
    END PROCEDURE
END PACKAGE BODY