// Copyright lowRISC contributors (OpenTitan project).
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// This module is an implementation of the encryption pass of the 64bit PRESENT
// block cipher. It is a fully unrolled combinational implementation that
// supports both key sizes specified in the paper (80bit and 128bit). Further,
// the number of rounds is fully configurable, and the primitive supports a
// 32bit block cipher flavor which is not specified in the original paper. It
// should be noted, however, that the 32bit version is **not** secure and must
// not be used in a setting where cryptographic cipher strength is required. The
// 32bit variant is only intended to be used as a lightweight data scrambling
// device.
//
// See also: prim_prince, prim_cipher_pkg
//
// References: - https://en.wikipedia.org/wiki/PRESENT
//             - https://en.wikipedia.org/wiki/Prince_(cipher)
//             - http://www.lightweightcrypto.org/present/present_ches2007.pdf
//             - https://eprint.iacr.org/2012/529.pdf
//             - https://csrc.nist.gov/csrc/media/events/lightweight-cryptography-workshop-2015/
//               documents/papers/session7-maene-paper.pdf

`include "prim_assert.sv"
module prim_present #(
  parameter int DataWidth = 64,  // {32, 64}
  parameter int KeyWidth  = 128, // {64, 80, 128}
  // Number of rounds to perform in total (>0)
  parameter int NumRounds = 31,
  // Number of physically instantiated PRESENT rounds.
  // This can be used to construct e.g. an iterative
  // full-round implementation that only has one physical
  // round instance by setting NumRounds = 31 and NumPhysRounds = 1.
  // Note that NumPhysRounds needs to divide NumRounds.
  parameter int NumPhysRounds = NumRounds,
  // Note that the decryption pass needs a modified key,
  // to be calculated by performing NumRounds key updates
  parameter bit Decrypt   = 0    // 0: encrypt, 1: decrypt
) (
  input        [DataWidth-1:0] data_i,
  input        [KeyWidth-1:0]  key_i,
  // Starting round index for keyschedule [1 ... 31].
  // Set this to 5'd1 for a fully unrolled encryption, and 5'd31 for a fully unrolled decryption.
  input        [4:0]           idx_i,
  output logic [DataWidth-1:0] data_o,
  output logic [KeyWidth-1:0]  key_o,
  // Next round index for keyschedule
  // (Enc: idx_i + NumPhysRounds, Dec: idx_i - NumPhysRounds)
  // Can be ignored for a fully unrolled implementation.
  output logic [4:0]           idx_o
);

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

  logic [NumPhysRounds:0][DataWidth-1:0] data_state;
  logic [NumPhysRounds:0][KeyWidth-1:0]  round_key;
  logic [NumPhysRounds:0][4:0]           round_idx;

  // initialize
  assign data_state[0] = data_i;
  assign round_key[0]  = key_i;
  assign round_idx[0]  = idx_i;

  for (genvar k = 0; k < NumPhysRounds; k++) begin : gen_round
    logic [DataWidth-1:0] data_state_xor, data_state_sbox;
    // cipher layers
    assign data_state_xor  = data_state[k] ^ round_key[k][KeyWidth-1 : KeyWidth-DataWidth];
    ////////////////////////////////
    // decryption pass, performs inverse permutation, sbox and keyschedule
    if (Decrypt) begin : gen_dec
      // Decrement round count.
      assign round_idx[k+1] = round_idx[k] - 1'b1;
      // original 64bit variant
      if (DataWidth == 64) begin : gen_d64
        assign data_state_sbox = prim_cipher_pkg::perm_64bit(data_state_xor,
                                                             prim_cipher_pkg::PRESENT_PERM64_INV);
        assign data_state[k+1] = prim_cipher_pkg::sbox4_64bit(data_state_sbox,
                                                              prim_cipher_pkg::PRESENT_SBOX4_INV);
      // reduced 32bit variant
      end else begin : gen_d32
        assign data_state_sbox = prim_cipher_pkg::perm_32bit(data_state_xor,
                                                             prim_cipher_pkg::PRESENT_PERM32_INV);
        assign data_state[k+1] = prim_cipher_pkg::sbox4_32bit(data_state_sbox,
                                                              prim_cipher_pkg::PRESENT_SBOX4_INV);
      end
      // update round key, count goes from 1 to 31 (max)
      // original 128bit key variant
      if (KeyWidth == 128) begin : gen_k128
        assign round_key[k+1]  = prim_cipher_pkg::present_inv_update_key128(round_key[k],
                                                                            round_idx[k]);
      // original 80bit key variant
      end else if (KeyWidth == 80) begin : gen_k80
        assign round_key[k+1]  = prim_cipher_pkg::present_inv_update_key80(round_key[k],
                                                                           round_idx[k]);
      // reduced 64bit key variant
      end else begin : gen_k64
        assign round_key[k+1]  = prim_cipher_pkg::present_inv_update_key64(round_key[k],
                                                                           round_idx[k]);
      end
    ////////////////////////////////
    // encryption pass
    end else begin : gen_enc
      // Increment round count.
      assign round_idx[k+1] = round_idx[k] + 1'b1;
      // original 64bit variant
      if (DataWidth == 64) begin : gen_d64
        assign data_state_sbox = prim_cipher_pkg::sbox4_64bit(data_state_xor,
                                                              prim_cipher_pkg::PRESENT_SBOX4);
        assign data_state[k+1] = prim_cipher_pkg::perm_64bit(data_state_sbox,
                                                             prim_cipher_pkg::PRESENT_PERM64);
      // reduced 32bit variant
      end else begin : gen_d32
        assign data_state_sbox = prim_cipher_pkg::sbox4_32bit(data_state_xor,
                                                              prim_cipher_pkg::PRESENT_SBOX4);
        assign data_state[k+1] = prim_cipher_pkg::perm_32bit(data_state_sbox,
                                                             prim_cipher_pkg::PRESENT_PERM32);
      end
      // update round key, count goes from 1 to 31 (max)
      // original 128bit key variant
      if (KeyWidth == 128) begin : gen_k128
        assign round_key[k+1]  = prim_cipher_pkg::present_update_key128(round_key[k], round_idx[k]);
      // original 80bit key variant
      end else if (KeyWidth == 80) begin : gen_k80
        assign round_key[k+1]  = prim_cipher_pkg::present_update_key80(round_key[k], round_idx[k]);
      // reduced 64bit key variant
      end else begin : gen_k64
        assign round_key[k+1]  = prim_cipher_pkg::present_update_key64(round_key[k], round_idx[k]);
      end
    end // gen_enc
    ////////////////////////////////
  end // gen_round

  // This only needs to be applied after the last round.
  // Note that for a full-round implementation the output index
  // will be 0 for enc/dec for the last round (either due to wraparound or subtraction).
  localparam int LastRoundIdx = (Decrypt != 0 || NumRounds == 31) ? 0 : NumRounds+1;
  assign data_o = (int'(idx_o) == LastRoundIdx) ?
      data_state[NumPhysRounds] ^
      round_key[NumPhysRounds][KeyWidth-1 : KeyWidth-DataWidth] :
      data_state[NumPhysRounds];

  assign key_o  = round_key[NumPhysRounds];
  assign idx_o  = round_idx[NumPhysRounds];

  ////////////////
  // assertions //
  ////////////////

  `ASSERT_INIT(SupportedWidths_A, (DataWidth == 64 && KeyWidth inside {80, 128}) ||
                                  (DataWidth == 32 && KeyWidth == 64))
  `ASSERT_INIT(SupportedNumRounds_A, NumRounds > 0 && NumRounds <= 31)
  `ASSERT_INIT(SupportedNumPhysRounds0_A, NumPhysRounds > 0 && NumPhysRounds <= NumRounds)
  // Currently we do not support other arrangements
  `ASSERT_INIT(SupportedNumPhysRounds1_A, (NumRounds % NumPhysRounds) == 0)

endmodule : prim_present
