| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| // |
| // Life cycle interface for performing life cycle transitions in OTP. |
| // |
| |
| `include "prim_assert.sv" |
| |
| module otp_ctrl_lci |
| import otp_ctrl_pkg::*; |
| import otp_ctrl_reg_pkg::*; |
| #( |
| // Lifecycle partition information |
| parameter part_info_t Info |
| ) ( |
| input clk_i, |
| input rst_ni, |
| input lci_en_i, |
| // Escalation input. This moves the FSM into a terminal state and locks down |
| // the partition. |
| input lc_tx_t escalate_en_i, |
| // Life cycle transition request. In order to perform a state transition, |
| // the LC controller signals the differential value with respect to the |
| // current state. The OTP controller then only programs the non-zero |
| // state differential. |
| input lc_req_i, |
| input lc_state_e lc_state_diff_i, |
| input lc_value_e [NumLcCountValues-1:0] lc_count_diff_i, |
| output logic lc_ack_o, |
| output logic lc_err_o, |
| // Output error state of partition, to be consumed by OTP error/alert logic. |
| // Note that most errors are not recoverable and move the partition FSM into |
| // a terminal error state. |
| output otp_err_e error_o, |
| output logic lci_idle_o, |
| // OTP interface |
| output logic otp_req_o, |
| output prim_otp_cmd_e otp_cmd_o, |
| output logic [OtpSizeWidth-1:0] otp_size_o, |
| output logic [OtpIfWidth-1:0] otp_wdata_o, |
| output logic [OtpAddrWidth-1:0] otp_addr_o, |
| input otp_gnt_i, |
| input otp_rvalid_i, |
| input [ScrmblBlockWidth-1:0] otp_rdata_i, |
| input otp_err_e otp_err_i |
| ); |
| |
| //////////////////////// |
| // Integration Checks // |
| //////////////////////// |
| |
| import prim_util_pkg::vbits; |
| |
| localparam int NumLcOtpWords = Info.size >> OtpAddrShift; |
| localparam int CntWidth = vbits(NumLcOtpWords); |
| |
| //////////////////// |
| // Controller FSM // |
| //////////////////// |
| |
| // Encoding generated with ./sparse-fsm-encode.py -d 5 -m 5 -n 9 -s 558234734 |
| // 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] { |
| ResetSt = 9'b010110111, |
| IdleSt = 9'b101010010, |
| WriteSt = 9'b111101110, |
| WriteWaitSt = 9'b100011101, |
| ErrorSt = 9'b010000000 |
| } state_e; |
| |
| logic cnt_clr, cnt_en; |
| logic [CntWidth-1:0] cnt_d, cnt_q; |
| otp_err_e error_d, error_q; |
| logic diff_data_is_set; |
| state_e state_d, state_q; |
| |
| // Output LCI errors |
| assign error_o = error_q; |
| |
| always_comb begin : p_fsm |
| state_d = state_q; |
| |
| // Counter |
| cnt_en = 1'b0; |
| cnt_clr = 1'b0; |
| |
| // Idle status |
| lci_idle_o = 1'b0; |
| |
| // OTP signals |
| otp_req_o = 1'b0; |
| otp_cmd_o = OtpRead; |
| |
| // Respone to LC controller |
| lc_err_o = 1'b0; |
| lc_ack_o = 1'b0; |
| |
| // Error Register |
| error_d = error_q; |
| |
| unique case (state_q) |
| /////////////////////////////////////////////////////////////////// |
| // State right after reset. Wait here until LCI gets enabled. |
| ResetSt: begin |
| if (lci_en_i) begin |
| state_d = IdleSt; |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // Wait for a request from the life cycle controller |
| IdleSt: begin |
| if (lc_req_i) begin |
| state_d = WriteSt; |
| cnt_clr = 1'b1; |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // Loop through the lifecycle sate and burn in the words that are set. |
| WriteSt: begin |
| // Check whether the OTP word is nonzero. |
| if (diff_data_is_set) begin |
| otp_req_o = 1'b1; |
| otp_cmd_o = OtpWrite; |
| if (otp_gnt_i) begin |
| state_d = WriteWaitSt; |
| end |
| // Check whether we examined all OTP words. |
| // If yes, we are done and can go back to idle. |
| end else if (cnt_q == NumLcOtpWords-1) begin |
| state_d = IdleSt; |
| lc_ack_o = 1'b1; |
| // Otherwise we increase the OTP word counter. |
| end else begin |
| cnt_en = 1'b1; |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // Wait for OTP response, and check whether there are more words to burn in. |
| // In case an OTP transaction fails, latch the OTP error code, and jump to |
| // terminal error state. |
| WriteWaitSt: begin |
| if (otp_rvalid_i) begin |
| // Check OTP return code. We do not tolerate any errors here. |
| if (otp_err_i != NoErr) begin |
| state_d = ErrorSt; |
| error_d = otp_err_i; |
| lc_ack_o = 1'b1; |
| lc_err_o = 1'b1; |
| end else begin |
| // Check whether we examined all OTP words. |
| // If yes, we are done and can go back to idle. |
| if (cnt_q == NumLcOtpWords-1) begin |
| state_d = IdleSt; |
| lc_ack_o = 1'b1; |
| // Otherwise we increase the OTP word counter. |
| end else begin |
| state_d = WriteSt; |
| cnt_en = 1'b1; |
| end |
| end |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // Terminal Error State. This locks access to the partition. |
| // Make sure the partition signals an error state if no error |
| // code has been latched so far, and lock the buffer regs down. |
| ErrorSt: begin |
| if (!error_q) begin |
| error_d = FsmErr; |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // We should never get here. If we do (e.g. via a malicious |
| // glitch), error out immediately. |
| 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; |
| error_d = EscErr; |
| end |
| end |
| |
| end |
| |
| ////////////////////////////// |
| // Counter and address calc // |
| ////////////////////////////// |
| |
| // Native OTP word counter |
| assign cnt_d = (cnt_clr) ? '0 : |
| (cnt_en) ? cnt_q + 1'b1 : cnt_q; |
| |
| // Note that OTP works on halfword (16bit) addresses, hence need to |
| // shift the addresses appropriately. |
| assign otp_addr_o = (Info.offset >> OtpAddrShift) + cnt_q; |
| |
| // Always transfer 16bit blocks. |
| assign otp_size_o = '0; |
| |
| logic [NumLcOtpWords-1:0][OtpWidth-1:0] diff_data; |
| assign diff_data = {lc_count_diff_i, lc_state_diff_i}; |
| assign otp_wdata_o = OtpIfWidth'(diff_data[cnt_q]); |
| assign diff_data_is_set = (diff_data[cnt_q] != Blk); |
| |
| logic unused_rdata; |
| assign unused_rdata = ^otp_rdata_i; |
| |
| /////////////// |
| // 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'(ResetSt)) |
| ) 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 |
| error_q <= NoErr; |
| cnt_q <= '0; |
| end else begin |
| error_q <= error_d; |
| cnt_q <= cnt_d; |
| end |
| end |
| |
| //////////////// |
| // Assertions // |
| //////////////// |
| |
| `ASSERT_KNOWN(LcAckKnown_A, lc_ack_o) |
| `ASSERT_KNOWN(LcErrKnown_A, lc_err_o) |
| `ASSERT_KNOWN(ErrorKnown_A, error_o) |
| `ASSERT_KNOWN(LciIdleKnown_A, lci_idle_o) |
| `ASSERT_KNOWN(OtpReqKnown_A, otp_req_o) |
| `ASSERT_KNOWN(OtpCmdKnown_A, otp_cmd_o) |
| `ASSERT_KNOWN(OtpSizeKnown_A, otp_size_o) |
| `ASSERT_KNOWN(OtpWdataKnown_A, otp_wdata_o) |
| `ASSERT_KNOWN(OtpAddrKnown_A, otp_addr_o) |
| |
| endmodule : otp_ctrl_lci |