// Copyright lowRISC contributors (OpenTitan project).
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// This is a simple data diffusion primitive that is constructed in a similar fashion
// as the PRESENT cipher (i.e. it uses a substitution/permutation network). Note however
// that this is **not** cryptographically secure. The main purpose of this primitive is to
// provide a cheap diffusion mechanism for arbitrarily sized vectors.
//
// See also: prim_prince, prim_present, prim_cipher_pkg

module prim_subst_perm #(
  parameter int DataWidth = 64,
  parameter int NumRounds = 31,
  parameter bit Decrypt   = 0    // 0: encrypt, 1: decrypt
) (
  input        [DataWidth-1:0] data_i,
  input        [DataWidth-1:0] key_i,
  output logic [DataWidth-1:0] data_o
);

  //////////////
  // datapath //
  //////////////

  // The "split_var" hint that we pass to verilator here tells it to schedule the different parts of
  // data_state separately. This avoids an UNOPTFLAT error where it would otherwise see a dependency
  // chain
  //
  //    data_state -> data_state_sbox -> data_state
  //
  logic [NumRounds:0][DataWidth-1:0] data_state /* verilator split_var */;

  // initialize
  assign data_state[0] = data_i;

  for (genvar r = 0; r < NumRounds; r++) begin : gen_round
    logic [DataWidth-1:0] data_state_sbox, data_state_flipped;
    ////////////////////////////////
    // decryption pass, performs inverse permutation and sbox
    if (Decrypt) begin : gen_dec
      always_comb begin : p_dec
        data_state_sbox = data_state[r] ^ key_i;
        // Reverse odd/even grouping
        data_state_flipped = data_state_sbox;
        for (int k = 0; k < DataWidth/2; k++) begin
          data_state_flipped[k * 2]     = data_state_sbox[k];
          data_state_flipped[k * 2 + 1] = data_state_sbox[k + DataWidth/2];
        end
        // Flip vector
        for (int k = 0; k < DataWidth; k++) begin
          data_state_sbox[DataWidth - 1 - k] = data_state_flipped[k];
        end
        // Inverse SBox layer
        for (int k = 0; k < DataWidth/4; k++) begin
          data_state_sbox[k*4 +: 4] = prim_cipher_pkg::PRESENT_SBOX4_INV[data_state_sbox[k*4 +: 4]];
        end
        data_state[r + 1] = data_state_sbox;
      end
    ////////////////////////////////
    // encryption pass
    end else begin : gen_enc
      always_comb begin : p_enc
        data_state_sbox = data_state[r] ^ key_i;
        // This SBox layer is aligned to nibbles, so the uppermost bits may not be affected by this.
        // However, the permutation below ensures that these bits get shuffled to a different
        // position when performing multiple rounds.
        for (int k = 0; k < DataWidth/4; k++) begin
          data_state_sbox[k*4 +: 4] = prim_cipher_pkg::PRESENT_SBOX4[data_state_sbox[k*4 +: 4]];
        end
        // Flip the vector to move the MSB positions into the LSB positions
        for (int k = 0; k < DataWidth; k++) begin
          data_state_flipped[DataWidth - 1 - k] = data_state_sbox[k];
        end
        // Regroup bits such that all even indices are stacked up first, followed by all odd
        // indices. Note that if the Width is odd, this is still ok, since
        // the uppermost bit just stays in place in that case.
        data_state_sbox = data_state_flipped;
        for (int k = 0; k < DataWidth/2; k++) begin
          data_state_sbox[k]               = data_state_flipped[k * 2];
          data_state_sbox[k + DataWidth/2] = data_state_flipped[k * 2 + 1];
        end
        data_state[r + 1] = data_state_sbox;
      end
    end // gen_enc
    ////////////////////////////////
  end // gen_round

  // finalize
  assign data_o = data_state[NumRounds] ^ key_i;

endmodule : prim_subst_perm
