| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| // This module contains the scrambling datapath for the OTP controller. It basically consists of |
| // two single-round PRESENT primitives (one for encryption and one for decryption mode), a counter |
| // with a simple FSM and four working registers, as listed below. |
| // |
| // key_state_q (128bit): working register to hold the round key (needed for the key schedule). |
| // |
| // data_state_q (64bit): working register to hold the data state in between rounds. |
| // |
| // data_shadow_q (64bit): shadow register for holding a second 64bit block of input data. This is |
| // used to form a 128bit data block for the digest mode, which has a block |
| // size of 128bit. |
| // |
| // digest_state_q (64bit): register to hold the digest state in between digest updates. Technically, |
| // this is not needed when the data for the digest is fed into this block |
| // back-to-back. However, the partition integrity checks require that it is |
| // possible to interleave encryption operations and digest update steps, |
| // hence an additional state register is needed, as otherwise the digest |
| // state would be lost. |
| // |
| // The scrambling datapath is arranged such that it can also be used for calculating a digest using |
| // the encryption primitive in a Merkle-Damgard construction. Note however that this makes the |
| // digest block size 128bit wide, since the Merkle-Damgard construction leverages the cipher key |
| // input to ingest data. |
| // |
| // The scrambling datapath exposes a few simple commands and the FSM hides the complexity |
| // of steering the appropriate muxes and keeping track of the cipher rounds. These commands are |
| // briefly explained below. |
| // |
| // Decrypt: This decrypts the data block provided via data_i with the key at index sel_i. |
| // |
| // Encrypt: This encrypts the data block provided via data_i with the key at index sel_i. |
| // In addition, this command copies the prvious result into a shadow register before |
| // the first encryption round for later use in the digest (see description further below). |
| // This enables interleaved encrypt/digest operation needed for the integrity checks of |
| // the secret partitions. |
| // |
| // LoadShadow: In "StandardMode", the LoadShadow command loads the data provided via data_i into a |
| // shadow register that is mapped to the lower 64bit of the 128bit digest input data |
| // block. In "ChainedMode", this command copies the contents of the data state register |
| // into the shadow register. |
| // |
| // DigestInit: This ensures that the digest initialization vector (IV) is selected upon the next |
| // call of the Digest command. Also, sel_i can be used to set the digest mode. If sel_i |
| // is set to "StandardMode", the data to be digested has to be provided via data_i and |
| // LoadShadow. If sel_i is set to "ChainedMode", the digest input is formed by |
| // concatenating the results of the revious two encryption commands. |
| // |
| // Digest: In "StandardMode", this command concatenates the data input supplied via data_i with |
| // the shadow register in order to form a 128bit block ({data_i, data_shadow_q}). This block |
| // is then used to encrypt the digest state. In "ChainedMode" digest mode, the 128bit block |
| // to be digested is formed by concatenating {data_state_q, data_shadow_q}. If a DigestInit |
| // command has been executed right before calling Digest, the IV selected with sel_i is |
| // used to initialize the state. |
| // |
| // DigestFinalize: This command encrypts the digest state with the finalization constant selected |
| // by sel_i in order to form the final digest. |
| // |
| // References: - https://docs.opentitan.org/hw/ip/otp_ctrl/doc/index.html#design-details |
| // - https://docs.opentitan.org/hw/ip/prim/doc/prim_present/ |
| // - https://en.wikipedia.org/wiki/Merkle-Damgard_construction |
| // - https://en.wikipedia.org/wiki/PRESENT |
| // - http://www.lightweightcrypto.org/present/present_ches2007.pdf |
| // |
| |
| module otp_ctrl_scrmbl import otp_ctrl_pkg::*; ( |
| input clk_i, |
| input rst_ni, |
| // input data and command |
| input otp_scrmbl_cmd_e cmd_i, |
| input [ConstSelWidth-1:0] sel_i, |
| input [ScrmblBlockWidth-1:0] data_i, |
| input valid_i, |
| output logic ready_o, |
| // output data |
| output logic [ScrmblBlockWidth-1:0] data_o, |
| output logic valid_o, |
| // escalation input and FSM error indication |
| input lc_tx_t escalate_en_i, |
| output logic fsm_err_o |
| ); |
| |
| import prim_util_pkg::vbits; |
| |
| //////////////////////// |
| // Decryption Key LUT // |
| //////////////////////// |
| |
| logic [NumPresentRounds-1:0][4:0] present_round_lut; |
| logic [NumScrmblKeys-1:0][ScrmblKeyWidth-1:0] otp_dec_key_lut; |
| |
| // This pre-calculates the inverse scrambling keys at elab time. |
| for (genvar k = 0; k < NumPresentRounds; k++) begin : gen_round_lut |
| assign present_round_lut[k] = 5'(unsigned'(k+1)); |
| end |
| `ASSERT_INIT(NumMaxPresentRounds_A, NumPresentRounds <= 31) |
| |
| always_comb begin : p_inv_keys |
| for (int k = 0; k < NumScrmblKeys; k++) begin |
| // Initialize with encryption key |
| otp_dec_key_lut[k] = OtpKey[k]; |
| for (int j = 0; j < NumPresentRounds; j++) begin |
| // Due to the PRESENT key schedule, we have to step the key schedule function by |
| // NumPresentRounds forwards to get the decryption key. |
| otp_dec_key_lut[k] = prim_cipher_pkg::present_update_key128(otp_dec_key_lut[k], |
| present_round_lut[j]); |
| end |
| end |
| end |
| `ASSERT_KNOWN(DecKeyLutKnown_A, otp_dec_key_lut) |
| |
| ////////////// |
| // Datapath // |
| ////////////// |
| |
| logic [ScrmblKeyWidth-1:0] key_state_d, key_state_q; |
| logic [ScrmblBlockWidth-1:0] data_state_d, data_state_q, data_shadow_q; |
| logic [ScrmblBlockWidth-1:0] digest_state_d, digest_state_q; |
| logic [ScrmblBlockWidth-1:0] enc_data_out, dec_data_out; |
| logic [ScrmblKeyWidth-1:0] dec_key_out, enc_key_out; |
| logic [ScrmblKeyWidth-1:0] otp_digest_const_mux, otp_enc_key_mux, otp_dec_key_mux; |
| logic [ScrmblBlockWidth-1:0] otp_digest_iv_mux; |
| |
| typedef enum logic [2:0] {SelEncDataOut, |
| SelDecDataOut, |
| SelDigestState, |
| SelDigestIV, |
| SelDataInput} data_state_sel_e; |
| |
| typedef enum logic [2:0] {SelDecKeyOut, |
| SelEncKeyOut, |
| SelDecKeyInit, |
| SelEncKeyInit, |
| SelDigestConst, |
| SelDigestInput, |
| SelDigestChained} key_state_sel_e; |
| |
| data_state_sel_e data_state_sel; |
| key_state_sel_e key_state_sel; |
| logic data_state_en, data_shadow_copy, data_shadow_load, digest_state_en, key_state_en; |
| logic [ConstSelWidth-1:0] sel_d, sel_q; |
| digest_mode_e digest_mode_d, digest_mode_q; |
| |
| assign otp_enc_key_mux = (sel_d < NumScrmblKeys) ? |
| OtpKey[sel_d[vbits(NumScrmblKeys)-1:0]] : '0; |
| assign otp_dec_key_mux = (sel_d < NumScrmblKeys) ? |
| otp_dec_key_lut[sel_d[vbits(NumScrmblKeys)-1:0]] : '0; |
| assign otp_digest_const_mux = (sel_d < NumDigestSets) ? |
| OtpDigestConst[sel_d[vbits(NumDigestSets)-1:0]] : '0; |
| assign otp_digest_iv_mux = (sel_d < NumDigestSets) ? |
| OtpDigestIV[sel_d[vbits(NumDigestSets)-1:0]] : '0; |
| |
| // Make sure we always select a valid key / digest constant. |
| `ASSERT(CheckNumEncKeys_A, data_state_sel == SelEncKeyInit |-> sel_d < NumScrmblKeys) |
| `ASSERT(CheckNumDecKeys_A, data_state_sel == SelDecKeyInit |-> sel_d < NumScrmblKeys) |
| `ASSERT(CheckNumDigest0_A, data_state_sel == SelDigestIV |-> sel_d < NumDigestSets) |
| `ASSERT(CheckNumDigest1_A, data_state_sel == SelDigestConst |-> sel_d < NumDigestSets) |
| |
| assign data_state_d = (data_state_sel == SelEncDataOut) ? enc_data_out : |
| (data_state_sel == SelDecDataOut) ? dec_data_out : |
| (data_state_sel == SelDigestState) ? digest_state_q : |
| (data_state_sel == SelDigestIV) ? otp_digest_iv_mux : |
| data_i; |
| |
| assign key_state_d = (key_state_sel == SelDecKeyOut) ? dec_key_out : |
| (key_state_sel == SelEncKeyOut) ? enc_key_out : |
| (key_state_sel == SelDecKeyInit) ? otp_dec_key_mux : |
| (key_state_sel == SelEncKeyInit) ? otp_enc_key_mux : |
| (key_state_sel == SelDigestConst) ? otp_digest_const_mux : |
| (key_state_sel == SelDigestChained) ? {data_state_q, data_shadow_q} : |
| {data_i, data_shadow_q}; |
| |
| assign digest_state_d = enc_data_out; |
| |
| assign data_o = data_state_q; |
| |
| ///////// |
| // FSM // |
| ///////// |
| |
| // Encoding generated with ./sparse-fsm-encode.py -d 5 -m 5 -n 9 -s 2193087944 |
| // Hamming distance histogram: |
| // |
| // 0: -- |
| // 1: -- |
| // 2: -- |
| // 3: -- |
| // 4: -- |
| // 5: |||||||||||||||||||| (60.00%) |
| // 6: ||||||||||||| (40.00%) |
| // 7: -- |
| // 8: -- |
| // 9: -- |
| // |
| // Minimum Hamming distance: 5 |
| // Maximum Hamming distance: 6 |
| // |
| localparam int StateWidth = 9; |
| typedef enum logic [StateWidth-1:0] { |
| IdleSt = 9'b100010111, |
| DecryptSt = 9'b001010000, |
| EncryptSt = 9'b011001011, |
| DigestSt = 9'b100101000, |
| ErrorSt = 9'b010111101 |
| } state_e; |
| |
| localparam int CntWidth = $clog2(NumPresentRounds+1); |
| state_e state_d, state_q; |
| logic [CntWidth-1:0] cnt_d, cnt_q; |
| logic cnt_clr, cnt_en; |
| logic valid_d, valid_q; |
| logic is_first_d, is_first_q; |
| |
| assign valid_o = valid_q; |
| |
| assign cnt_d = (cnt_clr) ? '0 : |
| (cnt_en) ? cnt_q + 1 : |
| cnt_q; |
| |
| always_comb begin : p_fsm |
| state_d = state_q; |
| is_first_d = is_first_q; |
| sel_d = sel_q; |
| digest_mode_d = digest_mode_q; |
| data_state_sel = SelDataInput; |
| key_state_sel = SelDigestInput; |
| data_state_en = 1'b0; |
| data_shadow_copy = 1'b0; |
| data_shadow_load = 1'b0; |
| key_state_en = 1'b0; |
| digest_state_en = 1'b0; |
| cnt_en = 1'b0; |
| cnt_clr = 1'b0; |
| valid_d = 1'b0; |
| ready_o = 1'b0; |
| fsm_err_o = 1'b0; |
| |
| unique case (state_q) |
| /////////////////////////////////////////////////////////////////// |
| // Idle State: decode command and |
| // load working regs accordingly |
| IdleSt: begin |
| cnt_clr = 1'b1; |
| ready_o = 1'b1; |
| |
| if (valid_i) begin |
| sel_d = sel_q; |
| unique case (cmd_i) |
| Decrypt: begin |
| state_d = DecryptSt; |
| key_state_sel = SelDecKeyInit; |
| data_state_en = 1'b1; |
| key_state_en = 1'b1; |
| sel_d = sel_i; |
| end |
| Encrypt: begin |
| state_d = EncryptSt; |
| key_state_sel = SelEncKeyInit; |
| data_state_en = 1'b1; |
| key_state_en = 1'b1; |
| sel_d = sel_i; |
| end |
| LoadShadow: begin |
| if (digest_mode_q == ChainedMode) begin |
| data_shadow_copy = 1'b1; |
| end else begin |
| data_shadow_load = 1'b1; |
| end |
| end |
| Digest: begin |
| state_d = DigestSt; |
| data_state_sel = (is_first_q) ? SelDigestIV : SelDigestState; |
| key_state_sel = (digest_mode_q == ChainedMode) ? SelDigestChained : SelDigestInput; |
| data_state_en = 1'b1; |
| key_state_en = 1'b1; |
| is_first_d = 1'b0; |
| sel_d = sel_i; |
| end |
| DigestInit: begin |
| digest_mode_d = digest_mode_e'(sel_i); |
| is_first_d = 1'b1; |
| end |
| DigestFinalize: begin |
| state_d = DigestSt; |
| data_state_sel = (is_first_q) ? SelDigestIV : SelDigestState; |
| key_state_sel = SelDigestConst; |
| data_state_en = 1'b1; |
| key_state_en = 1'b1; |
| is_first_d = 1'b1; |
| digest_mode_d = StandardMode; |
| sel_d = sel_i; |
| end |
| default: ; // ignore |
| endcase // cmd_i |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // Perform decrypt rounds. |
| DecryptSt: begin |
| data_state_sel = SelDecDataOut; |
| key_state_sel = SelDecKeyOut; |
| data_state_en = 1'b1; |
| key_state_en = 1'b1; |
| cnt_en = 1'b1; |
| if (cnt_q == NumPresentRounds-1) begin |
| state_d = IdleSt; |
| valid_d = 1'b1; |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // Perform encrypt rounds. |
| EncryptSt: begin |
| data_state_sel = SelEncDataOut; |
| key_state_sel = SelEncKeyOut; |
| data_state_en = 1'b1; |
| key_state_en = 1'b1; |
| cnt_en = 1'b1; |
| if (cnt_q == NumPresentRounds-1) begin |
| state_d = IdleSt; |
| valid_d = 1'b1; |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // The digest is calculated with a Merkle-Damgard construction that |
| // employs the PRESENT encryption datapath. |
| DigestSt: begin |
| data_state_sel = SelEncDataOut; |
| key_state_sel = SelEncKeyOut; |
| data_state_en = 1'b1; |
| key_state_en = 1'b1; |
| cnt_en = 1'b1; |
| if (cnt_q == NumPresentRounds-1) begin |
| state_d = IdleSt; |
| valid_d = 1'b1; |
| // Backup digest state for next round of updates. We can't keep this state in the |
| // data state register as a digest may be calculated together with encryption |
| // operations in an interleaved way. |
| digest_state_en = 1'b1; |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // Terminal error state. This raises an alert. |
| ErrorSt: begin |
| fsm_err_o = 1'b1; |
| end |
| /////////////////////////////////////////////////////////////////// |
| // This should never happen, hence we directly jump into the |
| // error state, where an alert will be triggered. |
| default: begin |
| state_d = ErrorSt; |
| end |
| /////////////////////////////////////////////////////////////////// |
| endcase // state_q |
| |
| if (state_q != ErrorSt) begin |
| // Unconditionally jump into the terminal error state in case of escalation. |
| if (escalate_en_i != Off) begin |
| state_d = ErrorSt; |
| end |
| end |
| end |
| |
| ///////////////////////////// |
| // PRESENT DEC/ENC Modules // |
| ///////////////////////////// |
| |
| prim_present #( |
| .KeyWidth(128), |
| .NumRounds(1) |
| ) u_prim_present_enc ( |
| .data_i ( data_state_q ), |
| .key_i ( key_state_q ), |
| .data_o ( enc_data_out ), |
| .key_o ( enc_key_out ) |
| ); |
| |
| prim_present #( |
| .KeyWidth(128), |
| .NumRounds(1), |
| .Decrypt(1) |
| ) u_prim_present_dec ( |
| .data_i ( data_state_q ), |
| .key_i ( key_state_q ), |
| .data_o ( dec_data_out ), |
| .key_o ( dec_key_out ) |
| ); |
| |
| /////////////// |
| // Registers // |
| /////////////// |
| |
| // This primitive is used to place a size-only constraint on the |
| // flops in order to prevent FSM state encoding optimizations. |
| prim_flop #( |
| .Width(StateWidth), |
| .ResetValue(StateWidth'(IdleSt)) |
| ) u_state_regs ( |
| .clk_i, |
| .rst_ni, |
| .d_i ( state_d ), |
| .q_o ( state_q ) |
| ); |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs |
| if (!rst_ni) begin |
| cnt_q <= '0; |
| key_state_q <= '0; |
| data_state_q <= '0; |
| data_shadow_q <= '0; |
| digest_state_q <= '0; |
| valid_q <= 1'b0; |
| is_first_q <= 1'b1; |
| sel_q <= '0; |
| digest_mode_q <= 1'b0; |
| end else begin |
| cnt_q <= cnt_d; |
| valid_q <= valid_d; |
| is_first_q <= is_first_d; |
| sel_q <= sel_d; |
| digest_mode_q <= digest_mode_d; |
| |
| // enable regs |
| if (key_state_en) begin |
| key_state_q <= key_state_d; |
| end |
| if (data_state_en) begin |
| data_state_q <= data_state_d; |
| end |
| if (data_shadow_copy) begin |
| data_shadow_q <= data_state_q; |
| end else if (data_shadow_load) begin |
| data_shadow_q <= data_state_d; |
| end |
| if (digest_state_en) begin |
| digest_state_q <= digest_state_d; |
| end |
| end |
| end |
| |
| endmodule : otp_ctrl_scrmbl |