| // 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. To that end, the PRESENT block cipher |
| // is turned into a one way function according to the Davies-Meyer scheme. 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, mode_i can be used to set the digest mode. If |
| // mode_i is set to "StandardMode", the data to be digested has to be provided via |
| // data_i and LoadShadow. If mode_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/One-way_compression_function#Davies%E2%80%93Meyer |
| // - https://en.wikipedia.org/wiki/PRESENT |
| // - http://www.lightweightcrypto.org/present/present_ches2007.pdf |
| // |
| |
| `include "prim_flop_macros.sv" |
| |
| module otp_ctrl_scrmbl |
| import otp_ctrl_pkg::*; |
| import otp_ctrl_part_pkg::*; |
| ( |
| input clk_i, |
| input rst_ni, |
| // input data and command |
| input otp_scrmbl_cmd_e cmd_i, |
| input digest_mode_e mode_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_ctrl_pkg::lc_tx_t escalate_en_i, |
| output logic fsm_err_o |
| ); |
| |
| import prim_util_pkg::vbits; |
| |
| //////////////////////// |
| // Decryption Key LUT // |
| //////////////////////// |
| |
| // Anchor keys, constants and IVs |
| key_array_t rnd_cnst_key_anchor; |
| digest_const_array_t rnd_cnst_digest_anchor; |
| digest_iv_array_t rnd_cnst_digest_iv_anchor; |
| |
| for (genvar i = 0; i < NumScrmblKeys; i++) begin : gen_anchor_keys |
| prim_sec_anchor_buf #( |
| .Width(ScrmblKeyWidth) |
| ) u_key_anchor_buf ( |
| .in_i(RndCnstKey[i]), |
| .out_o(rnd_cnst_key_anchor[i]) |
| ); |
| end |
| |
| for (genvar i = 0; i < NumDigestSets; i++) begin : gen_anchor_digests |
| prim_sec_anchor_buf #( |
| .Width(ScrmblKeyWidth) |
| ) u_const_anchor_buf ( |
| .in_i(RndCnstDigestConst[i]), |
| .out_o(rnd_cnst_digest_anchor[i]) |
| ); |
| |
| prim_sec_anchor_buf #( |
| .Width(ScrmblBlockWidth) |
| ) u_iv_anchor_buf ( |
| .in_i(RndCnstDigestIV[i]), |
| .out_o(rnd_cnst_digest_iv_anchor[i]) |
| ); |
| end |
| |
| |
| // Align these arrays to power of 2's to prevent X's in the muxing operations further below. |
| logic [2**$clog2(NumScrmblKeys)-1:0][ScrmblKeyWidth-1:0] otp_enc_key_lut; |
| logic [2**$clog2(NumScrmblKeys)-1:0][ScrmblKeyWidth-1:0] otp_dec_key_lut; |
| logic [2**$clog2(NumDigestSets)-1:0][ScrmblKeyWidth-1:0] digest_const_lut; |
| logic [2**$clog2(NumDigestSets)-1:0][ScrmblBlockWidth-1:0] digest_iv_lut; |
| |
| // This pre-calculates the inverse scrambling keys at elab time. |
| `ASSERT_INIT(NumMaxPresentRounds_A, NumPresentRounds <= 31) |
| |
| always_comb begin : p_luts |
| otp_enc_key_lut = '0; |
| otp_dec_key_lut = '0; |
| digest_const_lut = '0; |
| digest_iv_lut = '0; |
| |
| for (int k = 0; k < NumScrmblKeys; k++) begin |
| otp_enc_key_lut[k] = rnd_cnst_key_anchor[k]; |
| // 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_get_dec_key128(rnd_cnst_key_anchor[k], 5'(NumPresentRounds)); |
| end |
| |
| for (int k = 0; k < NumDigestSets; k++) begin |
| digest_const_lut[k] = rnd_cnst_digest_anchor[k]; |
| digest_iv_lut[k] = rnd_cnst_digest_iv_anchor[k]; |
| end |
| end |
| `ASSERT_KNOWN(EncKeyLutKnown_A, otp_enc_key_lut) |
| `ASSERT_KNOWN(DecKeyLutKnown_A, otp_dec_key_lut) |
| `ASSERT_KNOWN(DigestConstLutKnown_A, digest_const_lut) |
| `ASSERT_KNOWN(DigestIvLutKnown_A, digest_iv_lut) |
| |
| ////////////// |
| // Datapath // |
| ////////////// |
| |
| logic [4:0] idx_state_d, idx_state_q; |
| 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, enc_data_out_xor, dec_data_out; |
| logic [ScrmblKeyWidth-1:0] dec_key_out, enc_key_out; |
| logic [4:0] dec_idx_out, enc_idx_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, |
| SelEncDataOutXor, |
| SelDataInput} data_state_sel_e; |
| |
| typedef enum logic [2:0] {SelDecKeyOut, |
| SelEncKeyOut, |
| SelDecKeyInit, |
| SelEncKeyInit, |
| SelDigestConst, |
| SelDigestInput, |
| SelDigestChained} key_state_sel_e; |
| |
| logic digest_init; |
| 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; |
| digest_mode_e digest_mode_d, digest_mode_q; |
| |
| assign otp_enc_key_mux = otp_enc_key_lut[ScrmblKeySelWidth'(sel_i)]; |
| assign otp_dec_key_mux = otp_dec_key_lut[ScrmblKeySelWidth'(sel_i)]; |
| assign otp_digest_const_mux = digest_const_lut[DigestSetSelWidth'(sel_i)]; |
| assign otp_digest_iv_mux = digest_iv_lut[DigestSetSelWidth'(sel_i)]; |
| |
| // Make sure we always select a valid key / digest constant. |
| `ASSERT(CheckNumEncKeys_A, key_state_sel == SelEncKeyInit |-> sel_i < NumScrmblKeys) |
| `ASSERT(CheckNumDecKeys_A, key_state_sel == SelDecKeyInit |-> sel_i < NumScrmblKeys) |
| `ASSERT(CheckNumDigest1_A, key_state_sel == SelDigestConst |-> sel_i < 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 == SelEncDataOutXor) ? enc_data_out_xor : |
| 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}; |
| |
| // Initialize the round index state with 1 in all cases, except for the decrypt operation. |
| assign idx_state_d = (key_state_sel == SelDecKeyOut) ? dec_idx_out : |
| (key_state_sel == SelEncKeyOut) ? enc_idx_out : |
| (key_state_sel == SelDecKeyInit) ? unsigned'(5'(NumPresentRounds)) : |
| 5'd1; |
| |
| // The XOR is for the Davies-Mayer one-way function construction. |
| assign enc_data_out_xor = enc_data_out ^ digest_state_q; |
| assign digest_state_d = (digest_init) ? otp_digest_iv_mux : enc_data_out_xor; |
| |
| logic valid_q; //valid_d defined below |
| assign data_o = (valid_q) ? data_state_q : 0; |
| |
| ///////// |
| // FSM // |
| ///////// |
| |
| // SEC_CM: SCRMBL.FSM.SPARSE |
| // Encoding generated with: |
| // $ ./util/design/sparse-fsm-encode.py -d 5 -m 5 -n 9 \ |
| // -s 2193087944 --language=sv |
| // |
| // 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 |
| // Minimum Hamming weight: 4 |
| // Maximum Hamming weight: 7 |
| // |
| localparam int StateWidth = 9; |
| typedef enum logic [StateWidth-1:0] { |
| IdleSt = 9'b100011001, |
| DecryptSt = 9'b101101111, |
| EncryptSt = 9'b010010111, |
| DigestSt = 9'b111000010, |
| ErrorSt = 9'b011111000 |
| } state_e; |
| |
| localparam int CntWidth = $clog2(NumPresentRounds+1); |
| localparam int unsigned LastPresentRoundInt = NumPresentRounds - 1; |
| localparam bit [CntWidth-1:0] LastPresentRound = LastPresentRoundInt[CntWidth-1:0]; |
| |
| state_e state_d, state_q; |
| logic [CntWidth-1:0] cnt; |
| logic cnt_clr, cnt_en, cnt_err; |
| logic valid_d; //valid_q defined above |
| |
| assign valid_o = valid_q; |
| |
| // SEC_CM: SCRMBL.CTR.REDUN |
| prim_count #( |
| .Width(CntWidth) |
| ) u_prim_count ( |
| .clk_i, |
| .rst_ni, |
| .clr_i(cnt_clr), |
| .set_i(1'b0), |
| .set_cnt_i('0), |
| .incr_en_i(cnt_en), |
| .decr_en_i(1'b0), |
| .step_i(CntWidth'(1)), |
| .cnt_o(cnt), |
| .cnt_next_o(), |
| .err_o(cnt_err) |
| ); |
| |
| always_comb begin : p_fsm |
| state_d = state_q; |
| digest_mode_d = digest_mode_q; |
| data_state_sel = SelDataInput; |
| key_state_sel = SelDigestInput; |
| digest_init = 1'b0; |
| 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 |
| unique case (cmd_i) |
| Decrypt: begin |
| state_d = DecryptSt; |
| key_state_sel = SelDecKeyInit; |
| data_state_en = 1'b1; |
| key_state_en = 1'b1; |
| end |
| Encrypt: begin |
| state_d = EncryptSt; |
| key_state_sel = SelEncKeyInit; |
| data_state_en = 1'b1; |
| key_state_en = 1'b1; |
| 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 = SelDigestState; |
| key_state_sel = (digest_mode_q == ChainedMode) ? SelDigestChained : SelDigestInput; |
| data_state_en = 1'b1; |
| key_state_en = 1'b1; |
| end |
| DigestInit: begin |
| digest_mode_d = mode_i; |
| digest_init = 1'b1; |
| digest_state_en = 1'b1; |
| end |
| DigestFinalize: begin |
| state_d = DigestSt; |
| data_state_sel = SelDigestState; |
| key_state_sel = SelDigestConst; |
| data_state_en = 1'b1; |
| key_state_en = 1'b1; |
| digest_mode_d = StandardMode; |
| 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 == LastPresentRound) 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 == LastPresentRound) 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 == LastPresentRound) begin |
| state_d = IdleSt; |
| valid_d = 1'b1; |
| // Apply XOR for Davies-Meyer construction. |
| data_state_sel = SelEncDataOutXor; |
| // 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; |
| fsm_err_o = 1'b1; |
| end |
| /////////////////////////////////////////////////////////////////// |
| endcase // state_q |
| |
| // Unconditionally jump into the terminal error state in case of escalation. |
| // SEC_CM: SCRMBL.FSM.LOCAL_ESC, SCRMBL.FSM.GLOBAL_ESC |
| if (escalate_en_i != lc_ctrl_pkg::Off || cnt_err) begin |
| state_d = ErrorSt; |
| fsm_err_o = 1'b1; |
| end |
| end |
| |
| ///////////////////////////// |
| // PRESENT DEC/ENC Modules // |
| ///////////////////////////// |
| |
| prim_present #( |
| .KeyWidth(128), |
| .NumRounds(NumPresentRounds), |
| .NumPhysRounds(1) |
| ) u_prim_present_enc ( |
| .data_i ( data_state_q ), |
| .key_i ( key_state_q ), |
| .idx_i ( idx_state_q ), |
| .data_o ( enc_data_out ), |
| .key_o ( enc_key_out ), |
| .idx_o ( enc_idx_out ) |
| ); |
| |
| prim_present #( |
| .KeyWidth(128), |
| // We are using an iterative full-round implementation here. |
| .NumRounds(NumPresentRounds), |
| .NumPhysRounds(1), |
| .Decrypt(1) |
| ) u_prim_present_dec ( |
| .data_i ( data_state_q ), |
| .key_i ( key_state_q ), |
| .idx_i ( idx_state_q ), |
| .data_o ( dec_data_out ), |
| .key_o ( dec_key_out ), |
| .idx_o ( dec_idx_out ) |
| ); |
| |
| /////////////// |
| // Registers // |
| /////////////// |
| |
| `PRIM_FLOP_SPARSE_FSM(u_state_regs, state_d, state_q, state_e, IdleSt) |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs |
| if (!rst_ni) begin |
| key_state_q <= '0; |
| idx_state_q <= '0; |
| data_state_q <= '0; |
| data_shadow_q <= '0; |
| digest_state_q <= '0; |
| valid_q <= 1'b0; |
| digest_mode_q <= StandardMode; |
| end else begin |
| valid_q <= valid_d; |
| digest_mode_q <= digest_mode_d; |
| |
| // enable regs |
| if (key_state_en) begin |
| key_state_q <= key_state_d; |
| idx_state_q <= idx_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 |