| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| // |
| // AES main control |
| // |
| // This module controls the interplay of input/output registers and the AES cipher core. |
| |
| `include "prim_assert.sv" |
| |
| module aes_control |
| import aes_pkg::*; |
| import aes_reg_pkg::*; |
| #( |
| parameter int unsigned SecStartTriggerDelay = 0 |
| ) ( |
| input logic clk_i, |
| input logic rst_ni, |
| |
| // Main control signals |
| input logic ctrl_qe_i, |
| output logic ctrl_we_o, |
| input logic ctrl_err_storage_i, |
| input aes_op_e op_i, |
| input aes_mode_e mode_i, |
| input ciph_op_e cipher_op_i, |
| input logic sideload_i, |
| input logic manual_operation_i, |
| input logic start_i, |
| input logic key_iv_data_in_clear_i, |
| input logic data_out_clear_i, |
| input logic prng_reseed_i, |
| input logic mux_sel_err_i, |
| input logic sp_enc_err_i, |
| input lc_ctrl_pkg::lc_tx_t lc_escalate_en_i, |
| input logic alert_fatal_i, |
| output logic alert_o, |
| |
| // I/O register read/write enables |
| input logic key_sideload_valid_i, |
| input logic [NumRegsKey-1:0] key_init_qe_i [NumSharesKey], |
| input logic [NumRegsIv-1:0] iv_qe_i, |
| input logic [NumRegsData-1:0] data_in_qe_i, |
| input logic [NumRegsData-1:0] data_out_re_i, |
| output logic data_in_we_o, |
| output sp2v_e data_out_we_o, |
| |
| // Previous input data register |
| output dip_sel_e data_in_prev_sel_o, |
| output sp2v_e data_in_prev_we_o, |
| |
| // Cipher I/O muxes |
| output si_sel_e state_in_sel_o, |
| output add_si_sel_e add_state_in_sel_o, |
| output add_so_sel_e add_state_out_sel_o, |
| |
| // Counter |
| output sp2v_e ctr_incr_o, |
| input sp2v_e ctr_ready_i, |
| input sp2v_e [NumSlicesCtr-1:0] ctr_we_i, |
| |
| // Cipher core control and sync |
| output sp2v_e cipher_in_valid_o, |
| input sp2v_e cipher_in_ready_i, |
| input sp2v_e cipher_out_valid_i, |
| output sp2v_e cipher_out_ready_o, |
| output sp2v_e cipher_crypt_o, |
| input sp2v_e cipher_crypt_i, |
| output sp2v_e cipher_dec_key_gen_o, |
| input sp2v_e cipher_dec_key_gen_i, |
| output logic cipher_key_clear_o, |
| input logic cipher_key_clear_i, |
| output logic cipher_data_out_clear_o, |
| input logic cipher_data_out_clear_i, |
| |
| // Initial key registers |
| output key_init_sel_e key_init_sel_o, |
| output sp2v_e [NumRegsKey-1:0] key_init_we_o [NumSharesKey], |
| |
| // IV registers |
| output iv_sel_e iv_sel_o, |
| output sp2v_e [NumSlicesCtr-1:0] iv_we_o, |
| |
| // Pseudo-random number generator interface |
| output logic prng_data_req_o, |
| input logic prng_data_ack_i, |
| output logic prng_reseed_req_o, |
| input logic prng_reseed_ack_i, |
| |
| // Trigger register |
| output logic start_o, |
| output logic start_we_o, |
| output logic key_iv_data_in_clear_o, |
| output logic key_iv_data_in_clear_we_o, |
| output logic data_out_clear_o, |
| output logic data_out_clear_we_o, |
| output logic prng_reseed_o, |
| output logic prng_reseed_we_o, |
| |
| // Status register |
| output logic idle_o, |
| output logic idle_we_o, |
| output logic stall_o, |
| output logic stall_we_o, |
| input logic output_lost_i, |
| output logic output_lost_o, |
| output logic output_lost_we_o, |
| output logic output_valid_o, |
| output logic output_valid_we_o, |
| output logic input_ready_o, |
| output logic input_ready_we_o |
| ); |
| |
| import aes_pkg::*; |
| |
| // Encoding generated with: |
| // $ ./util/design/sparse-fsm-encode.py -d 3 -m 7 -n 6 \ |
| // -s 31468618 --language=sv |
| // |
| // Hamming distance histogram: |
| // |
| // 0: -- |
| // 1: -- |
| // 2: -- |
| // 3: |||||||||||||||||||| (57.14%) |
| // 4: ||||||||||||||| (42.86%) |
| // 5: -- |
| // 6: -- |
| // |
| // Minimum Hamming distance: 3 |
| // Maximum Hamming distance: 4 |
| // Minimum Hamming weight: 1 |
| // Maximum Hamming weight: 5 |
| // |
| localparam int StateWidth = 6; |
| typedef enum logic [StateWidth-1:0] { |
| IDLE = 6'b111100, |
| LOAD = 6'b101001, |
| PRNG_UPDATE = 6'b010000, |
| PRNG_RESEED = 6'b100010, |
| FINISH = 6'b011011, |
| CLEAR = 6'b110111, |
| ERROR = 6'b001110 |
| } aes_ctrl_e; |
| |
| aes_ctrl_e aes_ctrl_ns, aes_ctrl_cs; |
| |
| // Signals |
| aes_mode_e mode; |
| logic key_init_clear; |
| sp2v_e key_init_new, key_init_new_chk; |
| logic key_init_load; |
| logic key_init_arm; |
| sp2v_e key_init_ready, key_init_ready_chk; |
| logic key_sideload; |
| |
| logic [NumSlicesCtr-1:0] iv_qe; |
| logic iv_clear; |
| logic iv_load; |
| logic iv_arm; |
| sp2v_e iv_ready, iv_ready_chk; |
| |
| logic [NumRegsData-1:0] data_in_new_d, data_in_new_q; |
| sp2v_e data_in_new, data_in_new_chk; |
| logic data_in_load; |
| |
| logic [NumRegsData-1:0] data_out_read_d, data_out_read_q; |
| sp2v_e data_out_read, data_out_read_chk; |
| sp2v_e output_valid_d, output_valid_q; |
| |
| logic start_trigger; |
| logic cfg_valid; |
| logic no_alert; |
| sp2v_e start_common, start_ecb, start_cbc, start_cfb, start_ofb, start_ctr; |
| sp2v_e start, start_chk; |
| sp2v_e finish, finish_chk; |
| sp2v_e crypt, crypt_chk; |
| sp2v_e cipher_out_done, cipher_out_done_chk; |
| logic cipher_out_done_err_d, cipher_out_done_err_q; |
| sp2v_e doing_cbc_enc, doing_cbc_enc_chk, doing_cbc_dec, doing_cbc_dec_chk; |
| sp2v_e doing_cfb_enc, doing_cfb_enc_chk, doing_cfb_dec, doing_cfb_dec_chk; |
| sp2v_e doing_ofb, doing_ofb_chk; |
| sp2v_e doing_ctr, doing_ctr_chk; |
| logic ctrl_we_q; |
| sp2v_e ctr_ready; |
| sp2v_e [NumSlicesCtr-1:0] ctr_we; |
| logic clear_in_out_status; |
| sp2v_e cipher_in_ready; |
| sp2v_e cipher_out_valid; |
| sp2v_e cipher_crypt; |
| sp2v_e cipher_dec_key_gen; |
| logic sp_enc_err; |
| |
| logic start_we; |
| logic key_iv_data_in_clear_we; |
| logic data_out_clear_we; |
| logic prng_reseed_we; |
| |
| logic idle; |
| logic idle_we; |
| logic stall; |
| logic stall_we; |
| logic output_lost; |
| logic output_lost_we; |
| logic output_valid; |
| logic output_valid_we; |
| logic input_ready; |
| logic input_ready_we; |
| |
| if (SecStartTriggerDelay > 0) begin : gen_start_delay |
| // Delay the manual start trigger input for SCA measurements. |
| localparam int unsigned WidthCounter = $clog2(SecStartTriggerDelay+1); |
| logic [WidthCounter-1:0] count_d, count_q; |
| |
| // Clear counter when input goes low. Keep value if the specified delay is reached. |
| assign count_d = !start_i ? '0 : |
| start_trigger ? count_q : count_q + 1'b1; |
| assign start_trigger = (count_q == SecStartTriggerDelay[WidthCounter-1:0]) ? 1'b1 : 1'b0; |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| count_q <= '0; |
| end else begin |
| count_q <= count_d; |
| end |
| end |
| end else begin : gen_no_start_delay |
| // Directly forward the manual start trigger input. |
| assign start_trigger = start_i; |
| end |
| |
| // Software updates IV in chunks of 32 bits, the counter updates SliceSizeCtr bits at a time. |
| // Convert word write enable to internal half-word write enable. |
| assign iv_qe = {iv_qe_i[3], iv_qe_i[3], iv_qe_i[2], iv_qe_i[2], |
| iv_qe_i[1], iv_qe_i[1], iv_qe_i[0], iv_qe_i[0]}; |
| |
| // The cipher core is only ever allowed to start or finish if the control register holds a valid |
| // configuration and if no fatal alert condition occured. |
| assign cfg_valid = ~((mode == AES_NONE) | ctrl_err_storage_i); |
| assign no_alert = ~alert_fatal_i; |
| |
| // Check common start conditions. These are needed for any mode, unless we are running in |
| // manual mode. |
| assign start_common = |
| (key_init_ready_chk == SP2V_HIGH && data_in_new_chk == SP2V_HIGH) ? |
| // If key sideload is enabled, we only start if the key is valid. |
| (sideload_i ? (key_sideload_valid_i ? SP2V_HIGH : SP2V_LOW) : SP2V_HIGH) : SP2V_LOW; |
| |
| // Check mode-specific start conditions. If the IV (and counter) is needed, we only start if |
| // also the IV (and counter) is ready. |
| assign start_ecb = (mode == AES_ECB) ? SP2V_HIGH : SP2V_LOW; |
| assign start_cbc = (mode == AES_CBC && iv_ready_chk == SP2V_HIGH) ? SP2V_HIGH : SP2V_LOW; |
| assign start_cfb = (mode == AES_CFB && iv_ready_chk == SP2V_HIGH) ? SP2V_HIGH : SP2V_LOW; |
| assign start_ofb = (mode == AES_OFB && iv_ready_chk == SP2V_HIGH) ? SP2V_HIGH : SP2V_LOW; |
| assign start_ctr = (mode == AES_CTR && iv_ready_chk == SP2V_HIGH && |
| ctr_ready == SP2V_HIGH) ? SP2V_HIGH : SP2V_LOW; |
| |
| // If set to start manually, we just wait for the trigger. Otherwise, check common as well as |
| // mode-specific start conditions. |
| assign start = |
| (cfg_valid && no_alert && (SP2V_HIGH == |
| // Manual operation has priority. |
| (manual_operation_i ? (start_trigger ? SP2V_HIGH : SP2V_LOW) : |
| // Check start conditions for automatic operation. |
| (start_ecb == SP2V_HIGH || |
| start_cbc == SP2V_HIGH || |
| start_cfb == SP2V_HIGH || |
| start_ofb == SP2V_HIGH || |
| start_ctr == SP2V_HIGH) ? start_common : SP2V_LOW)) |
| ) ? SP2V_HIGH : SP2V_LOW; |
| |
| // If not set to overwrite data, we wait for any previous output data to be read. data_out_read |
| // synchronously clears output_valid_q, unless new output data is written in the exact same |
| // clock cycle. |
| assign finish = |
| (cfg_valid && no_alert && (SP2V_HIGH == |
| // Manual operation has priority. |
| (manual_operation_i ? SP2V_HIGH : |
| // Make sure previous output data has been read. |
| (output_valid_q == SP2V_LOW || |
| data_out_read_chk == SP2V_HIGH) ? SP2V_HIGH : SP2V_LOW)) |
| ) ? SP2V_HIGH : SP2V_LOW; |
| |
| // Helper signals for FSM |
| assign crypt = (cipher_crypt_o == SP2V_HIGH || |
| cipher_crypt_i == SP2V_HIGH) ? SP2V_HIGH : SP2V_LOW; |
| |
| assign doing_cbc_enc = (mode == AES_CBC && op_i == AES_ENC) ? crypt_chk : SP2V_LOW; |
| assign doing_cbc_dec = (mode == AES_CBC && op_i == AES_DEC) ? crypt_chk : SP2V_LOW; |
| assign doing_cfb_enc = (mode == AES_CFB && op_i == AES_ENC) ? crypt_chk : SP2V_LOW; |
| assign doing_cfb_dec = (mode == AES_CFB && op_i == AES_DEC) ? crypt_chk : SP2V_LOW; |
| assign doing_ofb = (mode == AES_OFB) ? crypt_chk : SP2V_LOW; |
| assign doing_ctr = (mode == AES_CTR) ? crypt_chk : SP2V_LOW; |
| |
| // FSM |
| always_comb begin : aes_ctrl_fsm |
| |
| // Previous input data register control |
| data_in_prev_sel_o = DIP_CLEAR; |
| data_in_prev_we_o = SP2V_LOW; |
| |
| // Cipher I/O mux control |
| state_in_sel_o = SI_DATA; |
| add_state_in_sel_o = ADD_SI_ZERO; |
| add_state_out_sel_o = ADD_SO_ZERO; |
| |
| // Counter control |
| ctr_incr_o = SP2V_LOW; |
| |
| // Cipher core control |
| cipher_in_valid_o = SP2V_LOW; |
| cipher_out_ready_o = SP2V_LOW; |
| cipher_out_done = SP2V_LOW; |
| cipher_crypt_o = SP2V_LOW; |
| cipher_dec_key_gen_o = SP2V_LOW; |
| cipher_key_clear_o = 1'b0; |
| cipher_data_out_clear_o = 1'b0; |
| |
| // Initial key registers |
| key_init_sel_o = sideload_i ? KEY_INIT_KEYMGR : KEY_INIT_INPUT; |
| for (int s = 0; s < NumSharesKey; s++) begin |
| key_init_we_o[s] = {NumRegsKey{SP2V_LOW}}; |
| end |
| |
| // IV registers |
| iv_sel_o = IV_INPUT; |
| iv_we_o = {NumSlicesCtr{SP2V_LOW}}; |
| |
| // Control register |
| ctrl_we_o = 1'b0; |
| |
| // Alert |
| alert_o = 1'b0; |
| |
| // Pseudo-random number generator control |
| prng_data_req_o = 1'b0; |
| prng_reseed_req_o = 1'b0; |
| |
| // Trigger register control |
| start_we = 1'b0; |
| key_iv_data_in_clear_we = 1'b0; |
| data_out_clear_we = 1'b0; |
| prng_reseed_we = 1'b0; |
| |
| // Status register |
| idle = 1'b0; |
| idle_we = 1'b0; |
| stall = 1'b0; |
| stall_we = 1'b0; |
| |
| // Key, data I/O register control |
| data_in_load = 1'b0; |
| data_in_we_o = 1'b0; |
| data_out_we_o = SP2V_LOW; |
| |
| // Register status tracker control |
| key_init_clear = 1'b0; |
| key_init_load = 1'b0; |
| key_init_arm = 1'b0; |
| iv_clear = 1'b0; |
| iv_load = 1'b0; |
| iv_arm = 1'b0; |
| |
| // FSM |
| aes_ctrl_ns = aes_ctrl_cs; |
| |
| unique case (aes_ctrl_cs) |
| |
| IDLE: begin |
| idle = (start_chk == SP2V_HIGH || key_iv_data_in_clear_i || data_out_clear_i || |
| prng_reseed_i) ? 1'b0 : 1'b1; |
| idle_we = 1'b1; |
| |
| if (idle) begin |
| // Initial key and IV updates are ignored if we are not idle. If key sideload is enabled, |
| // software writes to the initial key registers are ignored. |
| for (int s = 0; s < NumSharesKey; s++) begin |
| for (int i = 0; i < NumRegsKey; i++) begin |
| key_init_we_o[s][i] = |
| sideload_i ? (key_sideload ? SP2V_HIGH : SP2V_LOW) : |
| key_init_qe_i[s][i] ? SP2V_HIGH : SP2V_LOW; |
| end |
| end |
| for (int i = 0; i < NumSlicesCtr; i++) begin |
| iv_we_o[i] = iv_qe[i] ? SP2V_HIGH : SP2V_LOW; |
| end |
| |
| // Updates to the control register are only allowed if we are idle and we don't have a |
| // storage error. A storage error is unrecoverable and requires a reset. |
| ctrl_we_o = !ctrl_err_storage_i ? ctrl_qe_i : 1'b0; |
| |
| // Control register updates clear all register status trackers. |
| key_init_clear = ctrl_we_o; |
| iv_clear = ctrl_we_o; |
| end |
| |
| if (prng_reseed_i) begin |
| // PRNG reseeding has highest priority. |
| aes_ctrl_ns = PRNG_RESEED; |
| |
| end else if (key_iv_data_in_clear_i || data_out_clear_i) begin |
| // To clear registers, we must first request fresh pseudo-random data. |
| aes_ctrl_ns = PRNG_UPDATE; |
| |
| end else if (start_chk == SP2V_HIGH) begin |
| // Signal that we want to start encryption/decryption. |
| cipher_crypt_o = SP2V_HIGH; |
| |
| // We got a new initial key, but want to do decryption. The cipher core must first |
| // generate the start key for decryption. |
| cipher_dec_key_gen_o = (cipher_op_i == CIPH_INV) ? key_init_new_chk : SP2V_LOW; |
| |
| // Previous input data register control |
| data_in_prev_sel_o = (doing_cbc_dec_chk == SP2V_HIGH) ? DIP_DATA_IN : |
| (doing_cfb_enc_chk == SP2V_HIGH) ? DIP_DATA_IN : |
| (doing_cfb_dec_chk == SP2V_HIGH) ? DIP_DATA_IN : |
| (doing_ofb == SP2V_HIGH) ? DIP_DATA_IN : |
| (doing_ctr == SP2V_HIGH) ? DIP_DATA_IN : DIP_CLEAR; |
| data_in_prev_we_o = (doing_cbc_dec_chk == SP2V_HIGH) ? SP2V_HIGH : |
| (doing_cfb_enc_chk == SP2V_HIGH) ? SP2V_HIGH : |
| (doing_cfb_dec_chk == SP2V_HIGH) ? SP2V_HIGH : |
| (doing_ofb_chk == SP2V_HIGH) ? SP2V_HIGH : |
| (doing_ctr_chk == SP2V_HIGH) ? SP2V_HIGH : SP2V_LOW; |
| |
| // State input mux control |
| state_in_sel_o = (doing_cfb_enc_chk == SP2V_HIGH) ? SI_ZERO : |
| (doing_cfb_dec_chk == SP2V_HIGH) ? SI_ZERO : |
| (doing_ofb_chk == SP2V_HIGH) ? SI_ZERO : |
| (doing_ctr_chk == SP2V_HIGH) ? SI_ZERO : SI_DATA; |
| |
| // State input additon mux control |
| add_state_in_sel_o = (doing_cbc_enc_chk == SP2V_HIGH) ? ADD_SI_IV : |
| (doing_cfb_enc_chk == SP2V_HIGH) ? ADD_SI_IV : |
| (doing_cfb_dec_chk == SP2V_HIGH) ? ADD_SI_IV : |
| (doing_ofb_chk == SP2V_HIGH) ? ADD_SI_IV : |
| (doing_ctr_chk == SP2V_HIGH) ? ADD_SI_IV : ADD_SI_ZERO; |
| |
| // We have work for the cipher core, perform handshake. |
| cipher_in_valid_o = SP2V_HIGH; |
| if (cipher_in_ready == SP2V_HIGH) begin |
| // Do not yet clear a possible start trigger if we are just starting the generation of |
| // the start key for decryption. |
| start_we = (cipher_dec_key_gen_o == SP2V_LOW); |
| aes_ctrl_ns = LOAD; |
| end |
| end |
| end |
| |
| LOAD: begin |
| // Signal that we have used the current key, IV, data input to register status tracking. |
| key_init_load = (cipher_dec_key_gen == SP2V_HIGH); // This key is no longer "new", but |
| // still clean. |
| key_init_arm = (cipher_dec_key_gen == SP2V_LOW); // The key is still "new", prevent |
| // partial updates. |
| iv_load = (cipher_dec_key_gen == SP2V_HIGH) & |
| (doing_cbc_enc_chk == SP2V_HIGH | doing_cbc_dec_chk == SP2V_HIGH | |
| doing_cfb_enc_chk == SP2V_HIGH | doing_cfb_dec_chk == SP2V_HIGH | |
| doing_ofb_chk == SP2V_HIGH | doing_ctr_chk == SP2V_HIGH); |
| data_in_load = (cipher_dec_key_gen == SP2V_LOW); |
| |
| // Trigger counter increment. |
| ctr_incr_o = doing_ctr_chk; |
| |
| // Unless we are just generating the start key for decryption, we must update the PRNG. |
| aes_ctrl_ns = (cipher_dec_key_gen == SP2V_LOW) ? PRNG_UPDATE : FINISH; |
| end |
| |
| PRNG_UPDATE: begin |
| // Fresh pseudo-random data is used to: |
| // - clear the state in the final cipher round, |
| // - clear any other registers in the CLEAR state. |
| |
| // IV control in case of ongoing encryption/decryption |
| // - CTR: IV registers are updated by counter during cipher operation |
| iv_sel_o = (doing_ctr_chk == SP2V_HIGH) ? IV_CTR : IV_INPUT; |
| iv_we_o = (doing_ctr_chk == SP2V_HIGH) ? ctr_we : {NumSlicesCtr{SP2V_LOW}}; |
| |
| // Request fresh pseudo-random data, perform handshake. |
| prng_data_req_o = 1'b1; |
| if (prng_data_ack_i) begin |
| |
| // Ongoing encryption/decryption operations have the highest priority. The clear triggers |
| // might have become asserted after the handshake with the cipher core. |
| if (cipher_crypt == SP2V_HIGH) begin |
| aes_ctrl_ns = FINISH; |
| |
| end else begin // (key_iv_data_in_clear_i || data_out_clear_i) |
| // To clear the output data registers, we re-use the muxing resources of the cipher |
| // core. To clear all key material, some key registers inside the cipher core need to |
| // be cleared. |
| cipher_key_clear_o = key_iv_data_in_clear_i; |
| cipher_data_out_clear_o = data_out_clear_i; |
| |
| // We have work for the cipher core, perform handshake. |
| cipher_in_valid_o = SP2V_HIGH; |
| if (cipher_in_ready == SP2V_HIGH) begin |
| aes_ctrl_ns = CLEAR; |
| end |
| end // cipher_crypt |
| end // prng_data_ack_i |
| end |
| |
| PRNG_RESEED: begin |
| // Request a reseed of the PRNG, perform handshake. |
| prng_reseed_req_o = 1'b1; |
| if (prng_reseed_ack_i) begin |
| // Clear the trigger and return. |
| prng_reseed_we = 1'b1; |
| aes_ctrl_ns = IDLE; |
| end |
| end |
| |
| FINISH: begin |
| // Wait for cipher core to finish. |
| |
| if (cipher_dec_key_gen == SP2V_HIGH) begin |
| // We are ready. |
| cipher_out_ready_o = SP2V_HIGH; |
| if (cipher_out_valid == SP2V_HIGH) begin |
| aes_ctrl_ns = IDLE; |
| end |
| end else begin |
| // Handshake signals: We are ready once the output data registers can be written. Don't |
| // let data propagate in case of mux selector or sparsely encoded signals taking on |
| // invalid values. |
| cipher_out_ready_o = finish_chk; |
| cipher_out_done = (finish_chk == SP2V_HIGH && cipher_out_valid == SP2V_HIGH && |
| !mux_sel_err_i && !sp_enc_err) ? SP2V_HIGH : SP2V_LOW; |
| |
| // Signal if the cipher core is stalled (because previous output has not yet been read). |
| stall = (finish_chk == SP2V_LOW) & (cipher_out_valid == SP2V_HIGH); |
| stall_we = 1'b1; |
| |
| // State out addition mux control |
| add_state_out_sel_o = (doing_cbc_dec_chk == SP2V_HIGH) ? ADD_SO_IV : |
| (doing_cfb_enc_chk == SP2V_HIGH) ? ADD_SO_DIP : |
| (doing_cfb_dec_chk == SP2V_HIGH) ? ADD_SO_DIP : |
| (doing_ofb_chk == SP2V_HIGH) ? ADD_SO_DIP : |
| (doing_ctr_chk == SP2V_HIGH) ? ADD_SO_DIP : ADD_SO_ZERO; |
| |
| // IV control |
| // - CBC/CFB/OFB: IV registers are only updated when cipher finishes. |
| // - CTR: IV registers are updated by counter during cipher operation. |
| iv_sel_o = (doing_cbc_enc_chk == SP2V_HIGH) ? IV_DATA_OUT : |
| (doing_cbc_dec_chk == SP2V_HIGH) ? IV_DATA_IN_PREV : |
| (doing_cfb_enc_chk == SP2V_HIGH) ? IV_DATA_OUT : |
| (doing_cfb_dec_chk == SP2V_HIGH) ? IV_DATA_IN_PREV : |
| (doing_ofb_chk == SP2V_HIGH) ? IV_DATA_OUT_RAW : |
| (doing_ctr_chk == SP2V_HIGH) ? IV_CTR : IV_INPUT; |
| iv_we_o = (doing_cbc_enc_chk == SP2V_HIGH) || |
| (doing_cbc_dec_chk == SP2V_HIGH) || |
| (doing_cfb_enc_chk == SP2V_HIGH) || |
| (doing_cfb_dec_chk == SP2V_HIGH) || |
| (doing_ofb_chk == SP2V_HIGH) ? {NumSlicesCtr{cipher_out_done_chk}} : |
| (doing_ctr_chk == SP2V_HIGH) ? ctr_we : {NumSlicesCtr{SP2V_LOW}}; |
| |
| // Arm the IV status tracker: After finishing, the IV registers can be written again |
| // by software. We need to make sure software does not partially update the IV. |
| iv_arm = (doing_cbc_enc_chk == SP2V_HIGH) || |
| (doing_cbc_dec_chk == SP2V_HIGH) || |
| (doing_cfb_enc_chk == SP2V_HIGH) || |
| (doing_cfb_dec_chk == SP2V_HIGH) || |
| (doing_ofb_chk == SP2V_HIGH) || |
| (doing_ctr_chk == SP2V_HIGH) ? (cipher_out_done_chk == SP2V_HIGH) : 1'b0; |
| |
| // Proceed upon successful handshake. |
| if (cipher_out_done_chk == SP2V_HIGH) begin |
| data_out_we_o = SP2V_HIGH; |
| aes_ctrl_ns = IDLE; |
| end |
| end |
| end |
| |
| CLEAR: begin |
| // Initial Key, IV and input data registers can be cleared right away. |
| if (key_iv_data_in_clear_i) begin |
| // Initial Key |
| key_init_sel_o = KEY_INIT_CLEAR; |
| for (int s = 0; s < NumSharesKey; s++) begin |
| key_init_we_o[s] = {NumRegsKey{SP2V_HIGH}}; |
| end |
| key_init_clear = 1'b1; |
| |
| // IV |
| iv_sel_o = IV_CLEAR; |
| iv_we_o = {NumSlicesCtr{SP2V_HIGH}}; |
| iv_clear = 1'b1; |
| |
| // Input data |
| data_in_we_o = 1'b1; |
| data_in_prev_sel_o = DIP_CLEAR; |
| data_in_prev_we_o = SP2V_HIGH; |
| end |
| |
| // Perform handshake with cipher core. |
| cipher_out_ready_o = SP2V_HIGH; |
| if (cipher_out_valid == SP2V_HIGH) begin |
| |
| // Full Key and Decryption Key registers are cleared by the cipher core. |
| // key_iv_data_in_clear_i is acknowledged by the cipher core with cipher_key_clear_i. |
| if (cipher_key_clear_i) begin |
| // Clear the trigger bit. |
| key_iv_data_in_clear_we = 1'b1; |
| end |
| |
| // To clear the output data registers, we re-use the muxing resources of the cipher core. |
| // data_out_clear_i is acknowledged by the cipher core with cipher_data_out_clear_i. |
| if (cipher_data_out_clear_i) begin |
| // Clear output data and the trigger bit. Don't release data from cipher core in case |
| // of mux selector or sparsely encoded signals taking on invalid values. |
| data_out_we_o = (!mux_sel_err_i && !sp_enc_err) ? SP2V_HIGH : SP2V_LOW; |
| data_out_clear_we = 1'b1; |
| end |
| |
| aes_ctrl_ns = IDLE; |
| end |
| end |
| |
| ERROR: begin |
| // Terminal error state |
| alert_o = 1'b1; |
| end |
| |
| // We should never get here. If we do (e.g. via a malicious glitch), error out immediately. |
| default: begin |
| aes_ctrl_ns = ERROR; |
| end |
| endcase |
| |
| // Unconditionally jump into the terminal error state in case a mux selector or a sparsely |
| // encoded signal becomes invalid, or if the life cycle controller triggers an escalation. |
| if (mux_sel_err_i || sp_enc_err || lc_escalate_en_i != lc_ctrl_pkg::Off) begin |
| aes_ctrl_ns = ERROR; |
| end |
| end |
| |
| // This primitive is used to place a size-only constraint on the |
| // flops in order to prevent FSM state encoding optimizations. |
| logic [StateWidth-1:0] aes_ctrl_cs_raw; |
| assign aes_ctrl_cs = aes_ctrl_e'(aes_ctrl_cs_raw); |
| prim_flop #( |
| .Width(StateWidth), |
| .ResetValue(StateWidth'(IDLE)) |
| ) u_state_regs ( |
| .clk_i, |
| .rst_ni, |
| .d_i ( aes_ctrl_ns ), |
| .q_o ( aes_ctrl_cs_raw ) |
| ); |
| |
| ///////////////////// |
| // Status Tracking // |
| ///////////////////// |
| |
| // We only take a new sideload key if sideload is enabled, if the provided sideload key is marked |
| // as valid, and after the control register has been written. After that point we don't update |
| // the key anymore, as we don't have a notion of when it actually changes. This would be required |
| // to trigger decryption key generation for ECB/CBC decryption. |
| // To update the sideload key, software has to: |
| // 1) wait unitl AES is idle, |
| // 2) wait for the key manager to provide the new key, |
| // 3) start a new message by writing the control register and providing the IV (if needed). |
| assign key_sideload = sideload_i & key_sideload_valid_i & ctrl_we_q; |
| |
| // We only use clean initial keys. Either software/counter has updated |
| // - all initial key registers, or |
| // - none of the initial key registers but the registers were updated in the past. |
| logic [NumRegsKey-1:0] key_init_we [NumSharesKey]; |
| for (genvar s = 0; s < NumSharesKey; s++) begin : gen_status_key_init_we_shares |
| for (genvar i = 0; i < NumRegsKey; i++) begin : gen_status_key_init_we |
| assign key_init_we[s][i] = (key_init_we_o[s][i] == SP2V_HIGH); |
| end |
| end |
| aes_reg_status #( |
| .Width ( $bits(key_init_we) ) |
| ) u_reg_status_key_init ( |
| .clk_i ( clk_i ), |
| .rst_ni ( rst_ni ), |
| .we_i ( {key_init_we[1], key_init_we[0]} ), |
| .use_i ( key_init_load ), |
| .clear_i ( key_init_clear ), |
| .arm_i ( key_init_arm ), |
| .new_o ( key_init_new ), |
| .clean_o ( key_init_ready ) |
| ); |
| |
| // We only use clean and unused IVs. Either software/counter has updated |
| // - all IV registers, or |
| // - none of the IV registers but the registers were updated in the past |
| // and this particular IV has not yet been used. |
| logic [NumSlicesCtr-1:0] iv_we; |
| for (genvar i = 0; i < NumSlicesCtr; i++) begin : gen_status_iv_we |
| assign iv_we[i] = (iv_we_o[i] == SP2V_HIGH); |
| end |
| aes_reg_status #( |
| .Width ( $bits(iv_we) ) |
| ) u_reg_status_iv ( |
| .clk_i ( clk_i ), |
| .rst_ni ( rst_ni ), |
| .we_i ( iv_we ), |
| .use_i ( iv_load ), |
| .clear_i ( iv_clear ), |
| .arm_i ( iv_arm ), |
| .new_o ( iv_ready ), |
| .clean_o ( ) |
| ); |
| |
| // Input and output data register status tracking detects if: |
| // - A complete new data input block is available, and |
| // - An output data block has been read completely. |
| // The status tracking needs to be cleared upon writes to the control register. The clearing is |
| // applied one cycle later here to avoid zero-latency loops. This additional delay is not |
| // relevant as if we are about to start encryption/decryption, we anyway don't allow writes |
| // to the control register. |
| always_ff @(posedge clk_i or negedge rst_ni) begin : reg_ctrl_we |
| if (!rst_ni) begin |
| ctrl_we_q <= 1'b0; |
| end else begin |
| ctrl_we_q <= ctrl_we_o; |
| end |
| end |
| assign clear_in_out_status = ctrl_we_q; |
| |
| // Collect writes to data input registers. Cleared if: |
| // - data is loaded into cipher core, |
| // - clearing data input registers with random data, |
| // - clearing the status tracking. |
| assign data_in_new_d = data_in_load || data_in_we_o || clear_in_out_status ? '0 : |
| data_in_new_q | data_in_qe_i; |
| assign data_in_new = &data_in_new_d ? SP2V_HIGH : SP2V_LOW; |
| |
| // Collect reads of data output registers. data_out_read is high for one clock cycle only and |
| // clears output_valid_q unless new output is written in the exact same cycle. Cleared if: |
| // - clearing data ouput registers with random data, |
| // - clearing the status tracking. |
| assign data_out_read_d = &data_out_read_q || clear_in_out_status ? '0 : |
| data_out_read_q | data_out_re_i; |
| assign data_out_read = &data_out_read_d ? SP2V_HIGH : SP2V_LOW; |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin : reg_edge_detection |
| if (!rst_ni) begin |
| data_in_new_q <= '0; |
| data_out_read_q <= '0; |
| end else begin |
| data_in_new_q <= data_in_new_d; |
| data_out_read_q <= data_out_read_d; |
| end |
| end |
| |
| // Status register bits for data input and output |
| // Cleared to 1 if: |
| // - data is loaded into cipher core, |
| // - clearing data input registers with random data, |
| // - clearing the status tracking. |
| assign input_ready = (data_in_new == SP2V_LOW); |
| assign input_ready_we = (data_in_new == SP2V_HIGH) | data_in_load | data_in_we_o | |
| clear_in_out_status; |
| |
| // Cleared if: |
| // - all data output registers have been read (unless new output is written in the same cycle), |
| // - clearing data ouput registers with random data, |
| // - clearing the status tracking. |
| assign output_valid = (data_out_we_o == SP2V_HIGH) & ~data_out_clear_we; |
| assign output_valid_we = (data_out_we_o == SP2V_HIGH) | (data_out_read_chk == SP2V_HIGH) | |
| data_out_clear_we | clear_in_out_status; |
| |
| assign output_valid_d = !output_valid_we ? output_valid_q : |
| output_valid_o ? SP2V_HIGH : SP2V_LOW; |
| |
| // This primitive is used to place a size-only constraint on the |
| // flops in order to prevent optimizations on this status signal. |
| logic [Sp2VWidth-1:0] output_valid_q_raw; |
| prim_flop #( |
| .Width ( Sp2VWidth ), |
| .ResetValue ( Sp2VWidth'(SP2V_LOW) ) |
| ) u_output_valid_regs ( |
| .clk_i ( clk_i ), |
| .rst_ni ( rst_ni ), |
| .d_i ( output_valid_d ), |
| .q_o ( output_valid_q_raw ) |
| ); |
| |
| // Output lost status register bit |
| // Cleared when updating the Control Register. Set when overwriting previous output data that has |
| // not yet been read. |
| assign output_lost = ctrl_we_o ? 1'b0 : |
| output_lost_i ? 1'b1 : |
| (output_valid_q == SP2V_HIGH) & (data_out_read_chk == SP2V_LOW); |
| assign output_lost_we = ctrl_we_o | (data_out_we_o == SP2V_HIGH); |
| |
| ///////////////////// |
| // Status Register // |
| ///////////////////// |
| // Fatal alerts clear all other bits in the status register. |
| assign idle_o = alert_fatal_i ? 1'b0 : idle; |
| assign idle_we_o = alert_fatal_i ? 1'b1 : idle_we; |
| assign stall_o = alert_fatal_i ? 1'b0 : stall; |
| assign stall_we_o = alert_fatal_i ? 1'b1 : stall_we; |
| assign output_lost_o = alert_fatal_i ? 1'b0 : output_lost; |
| assign output_lost_we_o = alert_fatal_i ? 1'b1 : output_lost_we; |
| assign output_valid_o = alert_fatal_i ? 1'b0 : output_valid; |
| assign output_valid_we_o = alert_fatal_i ? 1'b1 : output_valid_we; |
| assign input_ready_o = alert_fatal_i ? 1'b0 : input_ready; |
| assign input_ready_we_o = alert_fatal_i ? 1'b1 : input_ready_we; |
| |
| ////////////////////// |
| // Trigger Register // |
| ////////////////////// |
| // Triggers are only ever cleared by control. Fatal alerts clear all bits in the trigger |
| // register. |
| assign start_o = 1'b0; |
| assign start_we_o = alert_fatal_i ? 1'b1 : start_we; |
| assign key_iv_data_in_clear_o = 1'b0; |
| assign key_iv_data_in_clear_we_o = alert_fatal_i ? 1'b1 : key_iv_data_in_clear_we; |
| assign data_out_clear_o = 1'b0; |
| assign data_out_clear_we_o = alert_fatal_i ? 1'b1 : data_out_clear_we; |
| assign prng_reseed_o = 1'b0; |
| assign prng_reseed_we_o = alert_fatal_i ? 1'b1 : prng_reseed_we; |
| |
| ////////////////////////////// |
| // Sparsely Encoded Signals // |
| ////////////////////////////// |
| |
| // We use sparse encodings for various critical signals and must ensure that: |
| // 1. The synthesis tool doesn't optimize away the sparse encoding. |
| // 2. The sparsely encoded signal is always valid. More precisely, an alert or SVA is triggered |
| // if a sparse signal takes on an invalid value. |
| // 3. The alert signal remains asserted until reset even if the sparse signal becomes valid again |
| // This is achieved by driving the control FSM into the terminal error state whenever any |
| // sparsely encoded signal becomes invalid. |
| // |
| // If any sparsely encoded signal becomes invalid, the controller further immediately de-asserts |
| // data_out_we_o and other write-enable signals to prevent any data from being released. |
| |
| // We use vectors of sparsely encoded signals to reduce code duplication. |
| localparam int unsigned NumSp2VSig = 21 + NumSlicesCtr; |
| sp2v_e [NumSp2VSig-1:0] sp2v_sig; |
| sp2v_e [NumSp2VSig-1:0] sp2v_sig_chk; |
| logic [NumSp2VSig-1:0][Sp2VWidth-1:0] sp2v_sig_chk_raw; |
| logic [NumSp2VSig-1:0] sp2v_sig_err; |
| |
| assign sp2v_sig[0] = cipher_in_ready_i; |
| assign sp2v_sig[1] = cipher_out_valid_i; |
| assign sp2v_sig[2] = cipher_crypt_i; |
| assign sp2v_sig[3] = cipher_dec_key_gen_i; |
| assign sp2v_sig[4] = crypt; |
| assign sp2v_sig[5] = doing_cbc_enc; |
| assign sp2v_sig[6] = doing_cbc_dec; |
| assign sp2v_sig[7] = doing_cfb_enc; |
| assign sp2v_sig[8] = doing_cfb_dec; |
| assign sp2v_sig[9] = doing_ofb; |
| assign sp2v_sig[10] = doing_ctr; |
| assign sp2v_sig[11] = key_init_new; |
| assign sp2v_sig[12] = key_init_ready; |
| assign sp2v_sig[13] = iv_ready; |
| assign sp2v_sig[14] = data_in_new; |
| assign sp2v_sig[15] = data_out_read; |
| assign sp2v_sig[16] = sp2v_e'(output_valid_q_raw); |
| assign sp2v_sig[17] = ctr_ready_i; |
| assign sp2v_sig[18] = start; |
| assign sp2v_sig[19] = finish; |
| for (genvar i = 0; i < NumSlicesCtr; i++) begin : gen_use_ctr_we_i |
| assign sp2v_sig[20+i] = ctr_we_i[i]; |
| end |
| assign sp2v_sig[20 + NumSlicesCtr] = cipher_out_done; |
| |
| // Individually check sparsely encoded signals. |
| for (genvar i = 0; i < NumSp2VSig; i++) begin : gen_sel_buf_chk |
| aes_sel_buf_chk #( |
| .Num ( Sp2VNum ), |
| .Width ( Sp2VWidth ) |
| ) u_aes_sp2v_sig_buf_chk_i ( |
| .clk_i ( clk_i ), |
| .rst_ni ( rst_ni ), |
| .sel_i ( sp2v_sig[i] ), |
| .sel_o ( sp2v_sig_chk_raw[i] ), |
| .err_o ( sp2v_sig_err[i] ) |
| ); |
| assign sp2v_sig_chk[i] = sp2v_e'(sp2v_sig_chk_raw[i]); |
| end |
| |
| assign cipher_in_ready = sp2v_sig_chk[0]; |
| assign cipher_out_valid = sp2v_sig_chk[1]; |
| assign cipher_crypt = sp2v_sig_chk[2]; |
| assign cipher_dec_key_gen = sp2v_sig_chk[3]; |
| assign crypt_chk = sp2v_sig_chk[4]; |
| assign doing_cbc_enc_chk = sp2v_sig_chk[5]; |
| assign doing_cbc_dec_chk = sp2v_sig_chk[6]; |
| assign doing_cfb_enc_chk = sp2v_sig_chk[7]; |
| assign doing_cfb_dec_chk = sp2v_sig_chk[8]; |
| assign doing_ofb_chk = sp2v_sig_chk[9]; |
| assign doing_ctr_chk = sp2v_sig_chk[10]; |
| assign key_init_new_chk = sp2v_sig_chk[11]; |
| assign key_init_ready_chk = sp2v_sig_chk[12]; |
| assign iv_ready_chk = sp2v_sig_chk[13]; |
| assign data_in_new_chk = sp2v_sig_chk[14]; |
| assign data_out_read_chk = sp2v_sig_chk[15]; |
| assign output_valid_q = sp2v_sig_chk[16]; |
| assign ctr_ready = sp2v_sig_chk[17]; |
| assign start_chk = sp2v_sig_chk[18]; |
| assign finish_chk = sp2v_sig_chk[19]; |
| for (genvar i = 0; i < NumSlicesCtr; i++) begin : gen_ctr_we |
| assign ctr_we[i] = sp2v_sig_chk[20+i]; |
| end |
| assign cipher_out_done_chk = sp2v_sig_chk[20 + NumSlicesCtr]; |
| assign cipher_out_done_err_d = sp2v_sig_err[20 + NumSlicesCtr]; |
| |
| // We need to register the error signal for cipher_out_done to avoid circular loops in the FSM. |
| always_ff @(posedge clk_i or negedge rst_ni) begin : reg_sp_enc_err |
| if (!rst_ni) begin |
| cipher_out_done_err_q <= 1'b0; |
| end else if (cipher_out_done_err_d) begin |
| cipher_out_done_err_q <= 1'b1; |
| end |
| end |
| |
| // Collect encoding errors. |
| // We instantiate the checker modules as close as possible to where the sparsely encoded signals |
| // are used. Here, we collect also encoding errors detected in other places of the core. |
| assign sp_enc_err = |sp2v_sig_err[NumSp2VSig-2:0] | cipher_out_done_err_q | sp_enc_err_i; |
| |
| // Prevent synthesis optimizations on the mode signal. |
| logic [$bits(aes_mode_e)-1:0] mode_in_raw, mode_buf_raw; |
| assign mode_in_raw = {mode_i}; |
| for (genvar i = 0; i < $bits(mode_i); i++) begin : gen_mode_buf |
| prim_buf u_prim_buf_sel_i ( |
| .in_i ( mode_in_raw[i] ), |
| .out_o ( mode_buf_raw[i] ) |
| ); |
| end |
| assign mode = aes_mode_e'(mode_buf_raw); |
| |
| //////////////// |
| // Assertions // |
| //////////////// |
| |
| // Selectors must be known/valid |
| `ASSERT(AesModeValid, !ctrl_err_storage_i |-> mode inside { |
| AES_ECB, |
| AES_CBC, |
| AES_CFB, |
| AES_OFB, |
| AES_CTR, |
| AES_NONE |
| }) |
| `ASSERT_KNOWN(AesOpKnown, op_i) |
| `ASSERT_KNOWN(AesCiphOpKnown, cipher_op_i) |
| `ASSERT(AesControlStateValid, !alert_o |-> aes_ctrl_cs inside { |
| IDLE, |
| LOAD, |
| PRNG_UPDATE, |
| PRNG_RESEED, |
| FINISH, |
| CLEAR |
| }) |
| |
| // Check parameters |
| `ASSERT_INIT(AesNumSlicesCtr, NumSlicesCtr == 8) |
| |
| endmodule |