module ad_perfect_shuffle #(
  parameter NUM_GROUPS = 2,
  parameter WORDS_PER_GROUP = 2,
  parameter WORD_WIDTH = 8
) (
  input [NUM_GROUPS*WORDS_PER_GROUP*WORD_WIDTH-1:0] data_in,
  output [NUM_GROUPS*WORDS_PER_GROUP*WORD_WIDTH-1:0] data_out
);
 /*
  * Performs the perfect shuffle operation.
  *
  * The perfect shuffle splits the input vector into NUM_GROUPS groups and then
  * each group in WORDS_PER_GROUP. The output vector consists of WORDS_PER_GROUP
  * groups and each group has NUM_GROUPS words. The data is remapped, so that
  * the i-th word of the j-th word in the output vector is the j-th word of the
  * i-th group of the input vector.

  * The inverse operation of the perfect shuffle is the perfect shuffle with
  * both parameters swapped.
  * I.e. [perfect_suffle B A [perfect_shuffle A B data]] == data
  *
  * Examples:
  *   NUM_GROUPS = 2, WORDS_PER_GROUP = 4
  *     [A B C D a b c d] => [A a B b C c D d]
  *   NUM_GROUPS = 4, WORDS_PER_GROUP = 2
  *     [A a B b C c D d] => [A B C D a b c d]
  *   NUM_GROUPS = 3, WORDS_PER_GROUP = 2
  *     [A B a b 1 2] => [A a 1 B b 2]
  */
  generate
    genvar i;
    genvar j;
    for (i = 0; i < NUM_GROUPS; i = i + 1) begin: shuffle_outer
      for (j = 0; j < WORDS_PER_GROUP; j = j + 1) begin: shuffle_inner
        localparam src_lsb = (j + i * WORDS_PER_GROUP) * WORD_WIDTH;
        localparam dst_lsb = (i + j * NUM_GROUPS) * WORD_WIDTH;

        assign data_out[dst_lsb+:WORD_WIDTH] = data_in[src_lsb+:WORD_WIDTH];
      end
    end
  endgenerate

endmodule