blob: d5101f2bf65d851fb78d0b79d071a40c8a120454 [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 import aes_pkg::*;
#(
parameter bit AES192Enable = 1,
parameter bit SecMasking = 0,
parameter sbox_impl_e SecSBoxImpl = SBoxImplLut,
localparam int NumShares = SecMasking ? 2 : 1 // derived parameter
) (
input logic clk_i,
input logic rst_ni,
input logic cfg_valid_i,
input ciph_op_e op_i,
input sp2v_e en_i,
output sp2v_e out_req_o,
input sp2v_e out_ack_i,
input logic clear_i,
input logic [3:0] round_i,
input key_len_e key_len_i,
input logic [7:0][31:0] key_i [NumShares],
output logic [7:0][31:0] key_o [NumShares],
input logic [WidthPRDKey-1:0] prd_i,
output logic err_o
);
sp2v_e en;
logic en_err;
sp2v_e out_ack;
logic out_ack_err;
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 [NumShares];
logic [31:0] spec_in_192 [NumShares];
logic [31:0] rot_word_in [NumShares];
logic [31:0] rot_word_out [NumShares];
logic use_rot_word;
logic [31:0] sub_word_in, sub_word_out;
logic [3:0] sub_word_out_req;
logic [31:0] sw_in_mask, sw_out_mask;
logic [7:0] rcon_add_in, rcon_add_out;
logic [31:0] rcon_added;
logic [31:0] irregular [NumShares];
logic [7:0][31:0] regular [NumShares];
// cfg_valid_i is used for gating assertions only.
logic unused_cfg_valid;
assign unused_cfg_valid = cfg_valid_i;
// Get a shorter reference.
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
// Advance.
assign rcon_we = clear_i | use_rcon &
(en == SP2V_HIGH) & (out_req_o == SP2V_HIGH) & (out_ack == SP2V_HIGH);
// 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
for (genvar s = 0; s < NumShares; s++) begin : gen_shares_rot_word_out
// Special input, equivalent to key_o[3] in the used cases
assign spec_in_128[s] = key_i[s][3] ^ key_i[s][2];
assign spec_in_192[s] = AES192Enable ? key_i[s][5] ^ key_i[s][1] ^ key_i[s][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[s] = key_i[s][3];
CIPH_INV: rot_word_in[s] = spec_in_128[s];
default: rot_word_in[s] = key_i[s][3];
endcase
end
/////////////
// AES-192 //
/////////////
AES_192: begin
if (AES192Enable) begin
unique case (op_i)
CIPH_FWD: begin
rot_word_in[s] = rnd_type[0] ? key_i[s][5] :
rnd_type[2] ? key_i[s][5] :
rnd_type[3] ? spec_in_192[s] : key_i[s][3];
end
CIPH_INV: begin
rot_word_in[s] = rnd_type[1] ? key_i[s][3] :
rnd_type[2] ? key_i[s][1] : key_i[s][3];
end
default: rot_word_in[s] = key_i[s][3];
endcase
end else begin
rot_word_in[s] = key_i[s][3];
end
end
/////////////
// AES-256 //
/////////////
AES_256: begin
unique case (op_i)
CIPH_FWD: rot_word_in[s] = key_i[s][7];
CIPH_INV: rot_word_in[s] = key_i[s][3];
default: rot_word_in[s] = key_i[s][7];
endcase
end
default: rot_word_in[s] = key_i[s][3];
endcase
end
// RotWord: cyclic byte shift
assign rot_word_out[s] = aes_circ_byte_shift(rot_word_in[s], 2'h3);
end
// Mux input for SubWord
assign sub_word_in = use_rot_word ? rot_word_out[0] : rot_word_in[0];
// Masking
if (!SecMasking) begin : gen_no_sw_in_mask
// The mask share is ignored anyway, it can be 0.
assign sw_in_mask = '0;
// Tie-off unused signals.
logic [31:0] unused_sw_out_mask;
assign unused_sw_out_mask = sw_out_mask;
end else begin : gen_sw_in_mask
// The input mask is the mask share of rot_word_in/out.
assign sw_in_mask = use_rot_word ? rot_word_out[1] : rot_word_in[1];
end
// SubWord - individually substitute bytes.
// Every DOM S-Box instance consumes 28 bits of randomness but itself produces 20 bits for use in
// another S-Box instance. For other S-Box implementations, only the bits corresponding to prd_i
// are used. Other bits are ignored and tied to 0.
logic [3:0][WidthPRDSBox+19:0] in_prd;
logic [3:0] [19:0] out_prd;
for (genvar i = 0; i < 4; i++) begin : gen_sbox
// Rotate the randomness produced by the S-Boxes. The LSBs are taken from the masking PRNG
// (prd_i) whereas the MSBs are produced by the other S-Box instances.
assign in_prd[i] = {out_prd[aes_rot_int(i,4)], prd_i[WidthPRDSBox*i +: WidthPRDSBox]};
aes_sbox #(
.SecSBoxImpl ( SecSBoxImpl )
) u_aes_sbox_i (
.clk_i ( clk_i ),
.rst_ni ( rst_ni ),
.en_i ( en == SP2V_HIGH ),
.out_req_o ( sub_word_out_req[i] ),
.out_ack_i ( out_ack == SP2V_HIGH ),
.op_i ( CIPH_FWD ),
.data_i ( sub_word_in[8*i +: 8] ),
.mask_i ( sw_in_mask[8*i +: 8] ),
.prd_i ( in_prd[i] ),
.data_o ( sub_word_out[8*i +: 8] ),
.mask_o ( sw_out_mask[8*i +: 8] ),
.prd_o ( out_prd[i] )
);
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
for (genvar s = 0; s < NumShares; s++) begin : gen_shares_irregular
if (s == 0) begin : gen_irregular_rcon
// The (masked) key share
assign irregular[s] = use_rcon ? rcon_added : sub_word_out;
end else begin : gen_irregular_no_rcon
// The mask share
assign irregular[s] = sw_out_mask;
end
end
///////////////////////////
// The more regular part //
///////////////////////////
// To reduce muxing resources, we re-use existing
// connections for unused words and default cases.
for (genvar s = 0; s < NumShares; s++) begin : gen_shares_regular
always_comb begin : drive_regular
unique case (key_len_i)
/////////////
// AES-128 //
/////////////
AES_128: begin
// key_o[7:4] not used
regular[s][7:4] = key_i[s][3:0];
regular[s][0] = irregular[s] ^ key_i[s][0];
unique case (op_i)
CIPH_FWD: begin
for (int i = 1; i < 4; i++) begin
regular[s][i] = regular[s][i-1] ^ key_i[s][i];
end
end
CIPH_INV: begin
for (int i = 1; i < 4; i++) begin
regular[s][i] = key_i[s][i-1] ^ key_i[s][i];
end
end
default: regular[s] = {key_i[s][3:0], key_i[s][7:4]};
endcase
end
/////////////
// AES-192 //
/////////////
AES_192: begin
// key_o[7:6] not used
regular[s][7:6] = key_i[s][3:2];
if (AES192Enable) begin
unique case (op_i)
CIPH_FWD: begin
if (rnd_type[0]) begin
// Shift down four upper most words
regular[s][3:0] = key_i[s][5:2];
// Generate Words 6 and 7
regular[s][4] = irregular[s] ^ key_i[s][0];
regular[s][5] = regular[s][4] ^ key_i[s][1];
end else begin
// Shift down two upper most words
regular[s][1:0] = key_i[s][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[s][i+2] = irregular[s] ^ key_i[s][i];
end else begin
regular[s][i+2] = regular[s][i+1] ^ key_i[s][i];
end
end
end // rnd_type[0]
end
CIPH_INV: begin
if (rnd_type[0]) begin
// Shift up four lowest words
regular[s][5:2] = key_i[s][3:0];
// Generate Word 44 and 45
for (int i = 0; i < 2; i++) begin
regular[s][i] = key_i[s][3+i] ^ key_i[s][3+i+1];
end
end else begin
// Shift up two lowest words
regular[s][5:4] = key_i[s][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[s][i] = irregular[s] ^ key_i[s][i+2];
end else begin
regular[s][i] = key_i[s][i+1] ^ key_i[s][i+2];
end
end
end // rnd_type[0]
end
default: regular[s] = {key_i[s][3:0], key_i[s][7:4]};
endcase
end else begin
regular[s] = {key_i[s][3:0], key_i[s][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[s] = {key_i[s][3:0], key_i[s][7:4]};
end else begin
// Shift down old upper half
regular[s][3:0] = key_i[s][7:4];
// Generate new upper half
regular[s][4] = irregular[s] ^ key_i[s][0];
for (int i = 1; i < 4; i++) begin
regular[s][i+4] = regular[s][i+4-1] ^ key_i[s][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[s] = {key_i[s][3:0], key_i[s][7:4]};
end else begin
// Shift up old lower half
regular[s][7:4] = key_i[s][3:0];
// Generate new lower half
regular[s][0] = irregular[s] ^ key_i[s][4];
for (int i = 0; i < 3; i++) begin
regular[s][i+1] = key_i[s][4+i] ^ key_i[s][4+i+1];
end
end // rnd == 0
end
default: regular[s] = {key_i[s][3:0], key_i[s][7:4]};
endcase
end
default: regular[s] = {key_i[s][3:0], key_i[s][7:4]};
endcase // key_len_i
end // drive_regular
end // gen_shares_regular
// Drive output
assign key_o = regular;
assign out_req_o = &sub_word_out_req ? SP2V_HIGH : SP2V_LOW;
//////////////////////////////
// Sparsely Encoded Signals //
//////////////////////////////
logic [Sp2VWidth-1:0] en_raw;
aes_sel_buf_chk #(
.Num ( Sp2VNum ),
.Width ( Sp2VWidth ),
.EnSecBuf ( 1'b1 )
) u_aes_key_expand_en_buf_chk (
.clk_i ( clk_i ),
.rst_ni ( rst_ni ),
.sel_i ( en_i ),
.sel_o ( en_raw ),
.err_o ( en_err )
);
assign en = sp2v_e'(en_raw);
logic [Sp2VWidth-1:0] out_ack_raw;
aes_sel_buf_chk #(
.Num ( Sp2VNum ),
.Width ( Sp2VWidth ),
.EnSecBuf ( 1'b1 )
) u_aes_key_expand_out_ack_buf_chk (
.clk_i ( clk_i ),
.rst_ni ( rst_ni ),
.sel_i ( out_ack_i ),
.sel_o ( out_ack_raw ),
.err_o ( out_ack_err )
);
assign out_ack = sp2v_e'(out_ack_raw);
// Collect encoding errors.
assign err_o = en_err | out_ack_err;
////////////////
// Assertions //
////////////////
// Create a lint error to reduce the risk of accidentally disabling the masking.
`ASSERT_STATIC_LINT_ERROR(AesKeyExpandSecMaskingNonDefault, SecMasking == 1)
// Cipher core masking requires a masked SBox and vice versa.
`ASSERT_INIT(AesMaskedCoreAndSBox,
(SecMasking &&
(SecSBoxImpl == SBoxImplCanrightMasked ||
SecSBoxImpl == SBoxImplCanrightMaskedNoreuse ||
SecSBoxImpl == SBoxImplDom)) ||
(!SecMasking &&
(SecSBoxImpl == SBoxImplLut ||
SecSBoxImpl == SBoxImplCanright)))
// Selectors must be known/valid
`ASSERT(AesCiphOpValid, cfg_valid_i |-> op_i inside {
CIPH_FWD,
CIPH_INV
})
`ASSERT(AesKeyLenValid, cfg_valid_i |-> key_len_i inside {
AES_128,
AES_192,
AES_256
})
endmodule