blob: c030089b769f20041a7e4e7cac4225885b973a92 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// AES KeyExpand
`include "prim_assert.sv"
module aes_key_expand #(
parameter bit AES192Enable = 1,
parameter SBoxImpl = "lut"
) (
input logic clk_i,
input logic rst_ni,
input aes_pkg::ciph_op_e op_i,
input logic step_i,
input logic clear_i,
input logic [3:0] round_i,
input aes_pkg::key_len_e key_len_i,
input logic [7:0][31:0] key_i,
output logic [7:0][31:0] key_o
);
import aes_pkg::*;
logic [7:0] rcon_d, rcon_q;
logic rcon_we;
logic use_rcon;
logic [3:0] rnd;
logic [3:0] rnd_type;
logic [31:0] spec_in_128, spec_in_192;
logic [31:0] rot_word_in, rot_word_out;
logic use_rot_word;
logic [31:0] sub_word_in, sub_word_out;
logic [7:0] rcon_add_in, rcon_add_out;
logic [31:0] rcon_added;
logic [31:0] irregular;
logic [7:0][31:0] regular;
assign rnd = round_i;
// For AES-192, there are four different types of rounds.
always_comb begin : get_rnd_type
if (AES192Enable) begin
rnd_type[0] = (rnd == 0);
rnd_type[1] = (rnd == 1 || rnd == 4 || rnd == 7 || rnd == 10);
rnd_type[2] = (rnd == 2 || rnd == 5 || rnd == 8 || rnd == 11);
rnd_type[3] = (rnd == 3 || rnd == 6 || rnd == 9 || rnd == 12);
end else begin
rnd_type = '0;
end
end
//////////////////////////////////////////////////////
// Irregular part involving Rcon, RotWord & SubWord //
//////////////////////////////////////////////////////
// Depending on key length and round, RotWord may not be used.
assign use_rot_word = (key_len_i == AES_256 && rnd[0] == 1'b0) ? 1'b0 : 1'b1;
// Depending on operation, key length and round, Rcon may not be used thus must not be updated.
always_comb begin : rcon_usage
use_rcon = 1'b1;
if (AES192Enable) begin
if (key_len_i == AES_192 &&
((op_i == CIPH_FWD && rnd_type[1]) ||
(op_i == CIPH_INV && (rnd_type[0] || rnd_type[3])))) begin
use_rcon = 1'b0;
end
end
if (key_len_i == AES_256 && rnd[0] == 1'b0) begin
use_rcon = 1'b0;
end
end
// Generate Rcon
always_comb begin : rcon_update
rcon_d = rcon_q;
if (clear_i) begin
rcon_d = (op_i == CIPH_FWD) ? 8'h01 :
((op_i == CIPH_INV) && (key_len_i == AES_128)) ? 8'h36 :
((op_i == CIPH_INV) && (key_len_i == AES_192)) ? 8'h80 :
((op_i == CIPH_INV) && (key_len_i == AES_256)) ? 8'h40 : 8'h01;
end else begin
rcon_d = (op_i == CIPH_FWD) ? aes_mul2(rcon_q) :
(op_i == CIPH_INV) ? aes_div2(rcon_q) : 8'h01;
end
end
assign rcon_we = clear_i | (step_i & use_rcon);
// Rcon register
always_ff @(posedge clk_i or negedge rst_ni) begin : reg_rcon
if (!rst_ni) begin
rcon_q <= '0;
end else if (rcon_we) begin
rcon_q <= rcon_d;
end
end
// Special input, equivalent to key_o[3] in the used cases
assign spec_in_128 = key_i[3] ^ key_i[2];
assign spec_in_192 = AES192Enable ? key_i[5] ^ key_i[1] ^ key_i[0] : '0;
// Select input
always_comb begin : rot_word_in_mux
unique case (key_len_i)
/////////////
// AES-128 //
/////////////
AES_128: begin
unique case (op_i)
CIPH_FWD: rot_word_in = key_i[3];
CIPH_INV: rot_word_in = spec_in_128;
default: rot_word_in = key_i[3];
endcase
end
/////////////
// AES-192 //
/////////////
AES_192: begin
if (AES192Enable) begin
unique case (op_i)
CIPH_FWD: begin
rot_word_in = rnd_type[0] ? key_i[5] :
rnd_type[2] ? key_i[5] :
rnd_type[3] ? spec_in_192 : key_i[3];
end
CIPH_INV: begin
rot_word_in = rnd_type[1] ? key_i[3] :
rnd_type[2] ? key_i[1] : key_i[3];
end
default: rot_word_in = key_i[3];
endcase
end else begin
rot_word_in = key_i[3];
end
end
/////////////
// AES-256 //
/////////////
AES_256: begin
unique case (op_i)
CIPH_FWD: rot_word_in = key_i[7];
CIPH_INV: rot_word_in = key_i[3];
default: rot_word_in = key_i[7];
endcase
end
default: rot_word_in = key_i[3];
endcase
end
// RotWord: cyclic byte shift
assign rot_word_out = aes_circ_byte_shift(rot_word_in, 2'h3);
// Mux input for SubWord
assign sub_word_in = use_rot_word ? rot_word_out : rot_word_in;
// SubWord - individually substitute bytes
for (genvar i = 0; i < 4; i++) begin : gen_sbox
aes_sbox #(
.SBoxImpl ( SBoxImpl )
) aes_sbox_i (
.op_i ( CIPH_FWD ),
.data_i ( sub_word_in[8*i +: 8] ),
.data_o ( sub_word_out[8*i +: 8] )
);
end
// Add Rcon
assign rcon_add_in = sub_word_out[7:0];
assign rcon_add_out = rcon_add_in ^ rcon_q;
assign rcon_added = {sub_word_out[31:8], rcon_add_out};
// Mux output coming from Rcon & SubWord
assign irregular = use_rcon ? rcon_added : sub_word_out;
///////////////////////////
// The more regular part //
///////////////////////////
// To reduce muxing resources, we re-use existing
// connections for unused words and default cases.
always_comb begin : drive_regular
unique case (key_len_i)
/////////////
// AES-128 //
/////////////
AES_128: begin
// key_o[7:4] not used
regular[7:4] = key_i[3:0];
regular[0] = irregular ^ key_i[0];
unique case (op_i)
CIPH_FWD: begin
for (int i=1; i<4; i++) begin
regular[i] = regular[i-1] ^ key_i[i];
end
end
CIPH_INV: begin
for (int i=1; i<4; i++) begin
regular[i] = key_i[i-1] ^ key_i[i];
end
end
default: regular = {key_i[3:0], key_i[7:4]};
endcase
end
/////////////
// AES-192 //
/////////////
AES_192: begin
// key_o[7:6] not used
regular[7:6] = key_i[3:2];
if (AES192Enable) begin
unique case (op_i)
CIPH_FWD: begin
if (rnd_type[0]) begin
// Shift down four upper most words
regular[3:0] = key_i[5:2];
// Generate Words 6 and 7
regular[4] = irregular ^ key_i[0];
regular[5] = regular[4] ^ key_i[1];
end else begin
// Shift down two upper most words
regular[1:0] = key_i[5:4];
// Generate new upper four words
for (int i=0; i<4; i++) begin
if ((i == 0 && rnd_type[2]) ||
(i == 2 && rnd_type[3])) begin
regular[i+2] = irregular ^ key_i[i];
end else begin
regular[i+2] = regular[i+1] ^ key_i[i];
end
end
end // rnd_type[0]
end
CIPH_INV: begin
if (rnd_type[0]) begin
// Shift up four lowest words
regular[5:2] = key_i[3:0];
// Generate Word 44 and 45
for (int i=0; i<2; i++) begin
regular[i] = key_i[3+i] ^ key_i[3+i+1];
end
end else begin
// Shift up two lowest words
regular[5:4] = key_i[1:0];
// Generate new lower four words
for (int i=0; i<4; i++) begin
if ((i == 2 && rnd_type[1]) ||
(i == 0 && rnd_type[2])) begin
regular[i] = irregular ^ key_i[i+2];
end else begin
regular[i] = key_i[i+1] ^ key_i[i+2];
end
end
end // rnd_type[0]
end
default: regular = {key_i[3:0], key_i[7:4]};
endcase
end else begin
regular = {key_i[3:0], key_i[7:4]};
end // AES192Enable
end
/////////////
// AES-256 //
/////////////
AES_256: begin
unique case (op_i)
CIPH_FWD: begin
if (rnd == 0) begin
// Round 0: Nothing to be done
// The Full Key registers are not updated
regular = {key_i[3:0], key_i[7:4]};
end else begin
// Shift down old upper half
regular[3:0] = key_i[7:4];
// Generate new upper half
regular[4] = irregular ^ key_i[0];
for (int i=1; i<4; i++) begin
regular[i+4] = regular[i+4-1] ^ key_i[i];
end
end // rnd == 0
end
CIPH_INV: begin
if (rnd == 0) begin
// Round 0: Nothing to be done
// The Full Key registers are not updated
regular = {key_i[3:0], key_i[7:4]};
end else begin
// Shift up old lower half
regular[7:4] = key_i[3:0];
// Generate new lower half
regular[0] = irregular ^ key_i[4];
for (int i=0; i<3; i++) begin
regular[i+1] = key_i[4+i] ^ key_i[4+i+1];
end
end // rnd == 0
end
default: regular = {key_i[3:0], key_i[7:4]};
endcase
end
default: regular = {key_i[3:0], key_i[7:4]};
endcase // key_len_i
end
// Drive output
assign key_o = regular;
// Selectors must be known/valid
`ASSERT_KNOWN(AesCiphOpKnown, op_i)
`ASSERT(AesKeyLenValid, key_len_i inside {
AES_128,
AES_192,
AES_256
})
endmodule