[otp_ctrl] First cut implementation of the OTP controller This adds an initial implementation of the OTP controller. Still missing are: - LFSR timer for triggering integrity/consistency checks - Life cycle interface - Scrambling key derivation Signed-off-by: Michael Schaffner <msf@opentitan.org>
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl.sv index 9ec1cd1..889bb3a 100644 --- a/hw/ip/otp_ctrl/rtl/otp_ctrl.sv +++ b/hw/ip/otp_ctrl/rtl/otp_ctrl.sv
@@ -20,8 +20,8 @@ input tlul_pkg::tl_h2d_t tl_i, output tlul_pkg::tl_d2h_t tl_o, // Interrupt Requests - output logic intr_otp_access_done_o, - output logic intr_otp_ctrl_err_o, + output logic intr_otp_operation_done_o, + output logic intr_otp_error_o, // Alerts input prim_alert_pkg::alert_rx_t [NumAlerts-1:0] alert_rx_i, output prim_alert_pkg::alert_tx_t [NumAlerts-1:0] alert_tx_o, @@ -33,6 +33,7 @@ input lc_otp_program_req_t lc_otp_program_req_i, output lc_otp_program_rsp_t lc_otp_program_rsp_o, // Lifecycle broadcast inputs + input lc_tx_t lc_escalate_en_i, input lc_tx_t lc_provision_en_i, input lc_tx_t lc_test_en_i, // OTP broadcast outputs @@ -42,227 +43,546 @@ // TODO: other hardware broadcast outputs ); - ////////////////////////////////// - // Regfile Breakout and Mapping // - ////////////////////////////////// + import prim_util_pkg::vbits; - tlul_pkg::tl_h2d_t tl_win_h2d[2]; - tlul_pkg::tl_d2h_t tl_win_d2h[2]; + //////////////////////// + // Integration Checks // + //////////////////////// + + // This ensures that we can transfer scrambler data blocks in and out of OTP atomically. + `ASSERT_INIT(OtpIfWidth_A, OtpIfWidth == ScrmblBlockWidth) + + ///////////// + // Regfile // + ///////////// + + tlul_pkg::tl_h2d_t tl_win_h2d[3]; + tlul_pkg::tl_d2h_t tl_win_d2h[3]; otp_ctrl_reg_pkg::otp_ctrl_reg2hw_t reg2hw; otp_ctrl_reg_pkg::otp_ctrl_hw2reg_t hw2reg; otp_ctrl_reg_top u_reg ( - .clk_i , + .clk_i, .rst_ni, .tl_i, .tl_o, - .tl_win_o ( tl_win_h2d ), - .tl_win_i ( tl_win_d2h ), - .reg2hw ( reg2hw ), - .hw2reg ( hw2reg ), - .devmode_i( 1'b1 ) + .tl_win_o ( tl_win_h2d ), + .tl_win_i ( tl_win_d2h ), + .reg2hw ( reg2hw ), + .hw2reg ( hw2reg ), + .devmode_i ( 1'b1 ) ); - //////////////// - // Interrupts // - //////////////// + /////////////////////////////// + // Digests and LC State CSRs // + /////////////////////////////// - logic otp_access_done; - logic otp_ctrl_err; + logic [ScrmblBlockWidth-1:0] unused_digest; + logic [NumPart-1:0][ScrmblBlockWidth-1:0] part_digest; + assign hw2reg.creator_sw_cfg_digest = part_digest[CreatorSwCfgIdx]; + assign hw2reg.owner_sw_cfg_digest = part_digest[OwnerSwCfgIdx]; + assign hw2reg.hw_cfg_digest = part_digest[HwCfgIdx]; + assign hw2reg.secret0_digest = part_digest[Secret0Idx]; + assign hw2reg.secret1_digest = part_digest[Secret1Idx]; + assign hw2reg.secret2_digest = part_digest[Secret2Idx]; + // LC partition has no digest + assign unused_digest = part_digest[LifeCycleIdx]; - // dummy connections - assign otp_access_done = reg2hw.direct_access_size.q[0]; - assign otp_ctrl_err = reg2hw.direct_access_size.q[1]; + // TODO: connect these + assign hw2reg.lc_state = '0; + assign hw2reg.lc_transition_cnt = '0; + + ////////////////////////////// + // Access Defaults and CSRs // + ////////////////////////////// + + part_access_t [NumPart-1:0] part_access_csrs; + always_comb begin : p_access_control + // Default (this will be overridden by partition-internal settings). + part_access_csrs = {{32'(2*NumPart)}{Unlocked}}; + // Propagate CSR read enables down to the SW_CFG partitions. + if (!reg2hw.creator_sw_cfg_read_lock) part_access_csrs[CreatorSwCfgIdx].read_lock = Locked; + if (!reg2hw.owner_sw_cfg_read_lock) part_access_csrs[OwnerSwCfgIdx].read_lock = Locked; + // The SECRET2 partition can only be accessed when provisioning is enabled. + if (lc_provision_en_i != On) part_access_csrs[Secret2Idx].read_lock = Locked; + // Permanently lock DAI write access to the life cycle partition + part_access_csrs[LifeCycleIdx].write_lock = Locked; + end + + ////////////////////// + // DAI-related CSRs // + ////////////////////// + + logic dai_idle; + logic dai_req; + dai_cmd_e dai_cmd; + logic [OtpByteAddrWidth-1:0] dai_addr; + logic [NumDaiWords-1:0][31:0] dai_wdata, dai_rdata; + logic unused_wdata_qe; + logic unused_addr_qe; + + // Any write to this register triggers a DAI command. + assign dai_req = reg2hw.direct_access_cmd.digest.qe | + reg2hw.direct_access_cmd.write.qe | + reg2hw.direct_access_cmd.read.qe; + + assign dai_cmd = {reg2hw.direct_access_cmd.digest.q, + reg2hw.direct_access_cmd.write.q, + reg2hw.direct_access_cmd.read.q}; + + assign dai_addr = reg2hw.direct_access_address.q; + assign dai_wdata = reg2hw.direct_access_wdata; + assign hw2reg.direct_access_rdata = dai_rdata; + // This write-protects all DAI regs during pending operations. + assign hw2reg.direct_access_regwen.d = dai_idle; + + // The DAI and the LCI can initiate write transactions, which + // are critical and we must not power down if such transactions + // are pending. Hence, we signal the LCI/DAI idle state to the + // power manager + logic lci_idle; + assign otp_pwr_state_o.idle = lci_idle & dai_idle; + + ////////////////////////////////////// + // Ctrl/Status CSRs, Errors, Alerts // + ////////////////////////////////////// + + // Status and error reporting CSRs, error interrupt generation and alerts. + otp_err_e [NumPart+1:0] part_error; + logic [NumPart+1:0] part_errors_reduced; + logic otp_operation_done, otp_error; + logic otp_fatal_error, otp_check_failed; + always_comb begin : p_errors_alerts + hw2reg.err_code = part_error; + otp_fatal_error = 1'b0; + otp_check_failed = 1'b0; + // Aggregate all the errors from the partitions and the DAI/LCI + for (int k = 0; k < NumPart+2; k++) begin + // Set the error bit if the error status of the corresponding partition is nonzero. + // Note that due to the enumeration of the fields in the otp_ctrl_hw2reg_status_reg_t + // we have to reverse the bitpositions here such that the mapping is correct. + part_errors_reduced[NumPart+1-k] = |part_error[k]; + // Filter for critical error codes that should not occur in the field. + otp_fatal_error |= part_error[k] inside {OtpCmdInvErr, + OtpInitErr, + OtpReadUncorrErr, + OtpReadErr, + OtpWriteErr}; + + // Filter for integrity and consistency check failures. + otp_check_failed |= part_error[k] inside {ParityErr, + IntegErr, + CnstyErr, + FsmErr}; + end + end + + // Assign these to the status register. + assign hw2reg.status = {part_errors_reduced, dai_idle}; + // If we got an error, we trigger an interrupt. + assign otp_error = |part_errors_reduced; + + ////////////////////////////////// + // Interrupts and Alert Senders // + ////////////////////////////////// prim_intr_hw #( .Width(1) - ) i_intr_esc0 ( + ) u_intr_esc0 ( .clk_i, .rst_ni, - .event_intr_i ( otp_access_done ), - .reg2hw_intr_enable_q_i ( reg2hw.intr_enable.otp_access_done.q ), - .reg2hw_intr_test_q_i ( reg2hw.intr_test.otp_access_done.q ), - .reg2hw_intr_test_qe_i ( reg2hw.intr_test.otp_access_done.qe ), - .reg2hw_intr_state_q_i ( reg2hw.intr_state.otp_access_done.q ), - .hw2reg_intr_state_de_o ( hw2reg.intr_state.otp_access_done.de ), - .hw2reg_intr_state_d_o ( hw2reg.intr_state.otp_access_done.d ), - .intr_o ( intr_otp_access_done_o ) + .event_intr_i ( otp_operation_done ), + .reg2hw_intr_enable_q_i ( reg2hw.intr_enable.otp_operation_done.q ), + .reg2hw_intr_test_q_i ( reg2hw.intr_test.otp_operation_done.q ), + .reg2hw_intr_test_qe_i ( reg2hw.intr_test.otp_operation_done.qe ), + .reg2hw_intr_state_q_i ( reg2hw.intr_state.otp_operation_done.q ), + .hw2reg_intr_state_de_o ( hw2reg.intr_state.otp_operation_done.de ), + .hw2reg_intr_state_d_o ( hw2reg.intr_state.otp_operation_done.d ), + .intr_o ( intr_otp_operation_done_o ) ); prim_intr_hw #( .Width(1) - ) i_intr_esc1 ( + ) u_intr_esc1 ( .clk_i, .rst_ni, - .event_intr_i ( otp_ctrl_err ), - .reg2hw_intr_enable_q_i ( reg2hw.intr_enable.otp_ctrl_err.q ), - .reg2hw_intr_test_q_i ( reg2hw.intr_test.otp_ctrl_err.q ), - .reg2hw_intr_test_qe_i ( reg2hw.intr_test.otp_ctrl_err.qe ), - .reg2hw_intr_state_q_i ( reg2hw.intr_state.otp_ctrl_err.q ), - .hw2reg_intr_state_de_o ( hw2reg.intr_state.otp_ctrl_err.de ), - .hw2reg_intr_state_d_o ( hw2reg.intr_state.otp_ctrl_err.d ), - .intr_o ( intr_otp_ctrl_err_o ) + .event_intr_i ( otp_error ), + .reg2hw_intr_enable_q_i ( reg2hw.intr_enable.otp_error.q ), + .reg2hw_intr_test_q_i ( reg2hw.intr_test.otp_error.q ), + .reg2hw_intr_test_qe_i ( reg2hw.intr_test.otp_error.qe ), + .reg2hw_intr_state_q_i ( reg2hw.intr_state.otp_error.q ), + .hw2reg_intr_state_de_o ( hw2reg.intr_state.otp_error.de ), + .hw2reg_intr_state_d_o ( hw2reg.intr_state.otp_error.d ), + .intr_o ( intr_otp_error_o ) ); - /////////////////// - // Alert Senders // - /////////////////// - - logic parity_mismatch; - logic digest_mismatch; - - // dummy connections - assign parity_mismatch = reg2hw.direct_access_cmd.read.q && - reg2hw.direct_access_cmd.read.qe; - assign digest_mismatch = reg2hw.direct_access_cmd.write.q && - reg2hw.direct_access_cmd.write.qe; - prim_alert_sender #( .AsyncOn(AlertAsyncOn[0]) - ) i_prim_alert_sender0 ( + ) u_prim_alert_sender0 ( .clk_i, .rst_ni, - .alert_i ( parity_mismatch ), + .alert_i ( otp_fatal_error ), .alert_rx_i ( alert_rx_i[0] ), .alert_tx_o ( alert_tx_o[0] ) ); prim_alert_sender #( .AsyncOn(AlertAsyncOn[1]) - ) i_prim_alert_sender1 ( + ) u_prim_alert_sender1 ( .clk_i, .rst_ni, - .alert_i ( digest_mismatch ), + .alert_i ( otp_check_failed ), .alert_rx_i ( alert_rx_i[1] ), .alert_tx_o ( alert_tx_o[1] ) ); - /////////////// - // OTP Macro // - /////////////// + //////////////// + // LFSR Timer // + //////////////// - localparam int OtpWidth = 8; - localparam int OtpDepth = 1024; - localparam int OtpAddrWidth = $clog2(OtpDepth); - localparam int OtpErrWidth = 8; + // TBD: should we incorporate a timeout in the LFSR counter to cover the case where a + // partition check never completes due to wedged arbitration (or some other condition + // induced due to a tampering attempt)? This will likely be constructed in a similar + // way as the ping timer inside the alert handler. - logic otp_init_req, otp_init_done; - logic otp_err_valid; - logic [OtpErrWidth-1:0] otp_err_code; - logic otp_valid, otp_ready; - logic [OtpAddrWidth-1:0] otp_addr; - logic [OtpWidth-1:0] otp_wdata, otp_rdata; - logic otp_wren, otp_rvalid; + logic [NumPart-1:0] integ_chk_req, integ_chk_ack; + logic [NumPart-1:0] cnsty_chk_req, cnsty_chk_ack; - // a couple of dummy connections to the OTP macro - assign otp_valid = reg2hw.direct_access_wdata[1].qe | reg2hw.direct_access_cmd.write.qe; - assign otp_wren = reg2hw.direct_access_wdata[1].qe; - assign otp_addr = reg2hw.direct_access_address.q; - assign otp_wdata = reg2hw.direct_access_wdata[1].q[OtpWidth-1:0]; + /////////////////////////////// + // OTP Macro and Arbitration // + /////////////////////////////// - assign otp_init_req = 1'b1; + typedef struct packed { + prim_otp_cmd_e cmd; + logic [OtpSizeWidth-1:0] size; // Number of native words to write. + logic [OtpIfWidth-1:0] wdata; + logic [OtpAddrWidth-1:0] addr; // Halfword address. + } otp_bundle_t; + + logic [NumPart+1:0] part_otp_arb_req, part_otp_arb_gnt; + otp_bundle_t part_otp_arb_bundle [NumPart+2]; + logic otp_arb_valid, otp_arb_ready; + logic [vbits(NumPart+2)-1:0] otp_arb_idx; + otp_bundle_t otp_arb_bundle; + + // The OTP interface is arbitrated on a per-cycle basis, meaning that back-to-back + // transactions can be completely independent. + prim_arbiter_tree #( + .N(NumPart+2), + .DW($bits(otp_bundle_t)) + ) u_otp_arb ( + .clk_i, + .rst_ni, + .req_i ( part_otp_arb_req ), + .data_i ( part_otp_arb_bundle ), + .gnt_o ( part_otp_arb_gnt ), + .idx_o ( otp_arb_idx ), + .valid_o ( otp_arb_valid ), + .data_o ( otp_arb_bundle ), + .ready_i ( otp_arb_ready ) + ); + + otp_err_e part_otp_err; + logic [OtpIfWidth-1:0] part_otp_rdata; + logic otp_rvalid; + tlul_pkg::tl_h2d_t tl_win_h2d_gated; + tlul_pkg::tl_d2h_t tl_win_d2h_gated; + + // Life cycle qualification of TL-UL test interface. + assign tl_win_h2d_gated = (lc_test_en_i == On) ? tl_win_h2d[$high(tl_win_h2d)] : '0; + assign tl_win_d2h[$high(tl_win_h2d)] = (lc_test_en_i == On) ? tl_win_d2h_gated : '0; prim_otp #( .Width(OtpWidth), .Depth(OtpDepth), + .CmdWidth(OtpCmdWidth), .ErrWidth(OtpErrWidth) - ) i_prim_otp ( + ) u_otp ( .clk_i, .rst_ni, - // Test inerface - .test_tl_i ( tl_win_h2d[1] ), - .test_tl_o ( tl_win_d2h[1] ), - // Init and error signals - .init_req_i ( otp_init_req ), - .init_done_o ( otp_init_done ), - .err_valid_o ( otp_err_valid ), - .err_code_o ( otp_err_code ), + // Test interface + .test_tl_i ( tl_win_h2d_gated ), + .test_tl_o ( tl_win_d2h_gated ), // Read / Write command interface - .ready_o ( otp_ready ), - .valid_i ( otp_valid ), - .addr_i ( otp_addr ), - .wdata_i ( otp_wdata ), - .wren_i ( otp_wren ), + .ready_o ( otp_arb_ready ), + .valid_i ( otp_arb_valid ), + .cmd_i ( otp_arb_bundle.cmd ), + .size_i ( otp_arb_bundle.size ), + .addr_i ( otp_arb_bundle.addr ), + .wdata_i ( otp_arb_bundle.wdata ), // Read data out - .rdata_o ( otp_rdata ), - .rvalid_o ( otp_rvalid ) + .valid_o ( otp_rvalid ), + .rdata_o ( part_otp_rdata ), + .err_o ( part_otp_err ) ); - //////////////////// - // OTP Ctrl Logic // - //////////////////// + logic otp_fifo_valid; + logic [NumPartWidth-1:0] otp_part_idx; + logic [NumPart+1:0] part_otp_rvalid; - logic [31:0] gate_gen_out; - logic gate_gen_out_valid; - - // gate generator with dummy connection to regfile - prim_gate_gen #( - .NumGates(30000) - ) i_prim_gate_gen ( + // We can have up to two OTP commands in flight, hence we size this to be 2 deep. + // The partitions can unconditionally sink requested data. + prim_fifo_sync #( + .Width(NumPartWidth), + .Depth(2) + ) u_otp_rsp_fifo ( .clk_i, .rst_ni, - .data_i ( reg2hw.direct_access_wdata[0].q ), - .valid_i ( reg2hw.direct_access_wdata[0].qe ), - .data_o ( gate_gen_out ), - .valid_o ( gate_gen_out_valid ) + .clr_i ( 1'b0 ), + .wvalid_i ( otp_arb_valid & otp_arb_ready ), + .wready_o ( ), + .wdata_i ( otp_arb_idx ), + .rvalid_o ( otp_fifo_valid ), + .rready_i ( otp_rvalid ), + .rdata_o ( otp_part_idx ), + .depth_o ( ) ); - ///////////////////// - // PRESENT ENC/DEC // - ///////////////////// - - logic [63:0] otp_scrambler_out; - logic otp_scrambler_out_valid; - - otp_ctrl_scrmbl i_otp_ctrl_scrmbl ( - .clk_i, - .rst_ni, - .data_i ( {reg2hw.direct_access_wdata[1].q, - reg2hw.direct_access_wdata[0].q} ), - .cmd_i ( otp_scrmbl_cmd_e'(reg2hw.direct_access_wdata[1].q[2:0]) ), - .valid_i ( reg2hw.direct_access_wdata[1].qe ), - .ready_o ( ), - .data_o ( otp_scrambler_out ), - .valid_o ( otp_scrambler_out_valid ) - ); - - /////////////////////////////////// - // Dummy Connections and Tie Off // - /////////////////////////////////// - - always_comb begin : p_tie_off_or_connect - // tie off - hw2reg.status = '0; - hw2reg.err_code = '0; - hw2reg.direct_access_rdata = '0; - hw2reg.lc_state = '0; - hw2reg.id_state = '0; - hw2reg.test_xxx_cnt = '0; - hw2reg.transition_cnt = '0; - hw2reg.secret_integrity_digest = '0; - hw2reg.hw_cfg_lock = '0; - hw2reg.hw_cfg = '0; - hw2reg.hw_cfg_integrity_digest = '0; - hw2reg.sw_cfg_integrity_digest = '0; - - // TODO: initialization should only performed once after reset. subsequent - // init requests should just be immediately ack'ed without performing the complete - // OTP boot sequence. - pwr_otp_init_rsp_o = '0; - otp_pwr_state_o = '0; - lc_otp_program_rsp_o = '0; - otp_lc_data_o = '0; - otp_keymgr_key_o = '0; - otp_flash_key_o = '0; - - // dummy connections - {hw2reg.direct_access_rdata[0].d, - hw2reg.direct_access_rdata[1].d} = otp_scrambler_out ^ {gate_gen_out, gate_gen_out}; - hw2reg.direct_access_rdata[0].de = gate_gen_out_valid ^ gate_gen_out_valid; - hw2reg.direct_access_rdata[1].de = gate_gen_out_valid ^ gate_gen_out_valid; - hw2reg.lc_state[0] = otp_rdata ^ {8{otp_rvalid}}; + // Steer response back to the partition where this request originated. + always_comb begin : p_rvalid + part_otp_rvalid = '0; + part_otp_rvalid[otp_part_idx] = otp_rvalid & otp_fifo_valid; end + // Note that this must be true by construction. + `ASSERT(OtpRespFifoUnderflow_A, otp_rvalid |-> otp_fifo_valid) + + ///////////////////////////////////////// + // Scrambling Datapath and Arbitration // + ///////////////////////////////////////// + + // Note: as opposed to the OTP arbitration above, we do not perform cycle-wise arbitration, but + // transaction-wise arbitration. This is implemented using a RR arbiter that acts as a mutex. + // I.e., each agent (e.g. the DAI or a partition) can request a lock on the mutex. Once granted, + // the partition can keep the the lock as long as needed for the transaction to complete. The + // partition must yield its lock by deasserting the request signal for the arbiter to proceed. + // Since this scheme does not have built-in preemtion, it must be ensured that the agents + // eventually release their locks for this to be fair. + typedef struct packed { + otp_scrmbl_cmd_e cmd; + logic [ConstSelWidth-1:0] sel; + logic [ScrmblBlockWidth-1:0] data; + logic valid; + } scrmbl_bundle_t; + + logic [NumPart:0] part_scrmbl_mtx_req, part_scrmbl_mtx_gnt; + scrmbl_bundle_t part_scrmbl_req_bundle [NumPart+1]; + scrmbl_bundle_t scrmbl_req_bundle; + logic [vbits(NumPart+1)-1:0] scrmbl_mtx_idx; + logic scrmbl_mtx_valid; + + // Note that arbiter decisions do not change when backpressured. + // Hence, the idx_o signal is guaranteed to remain stable until ack'ed. + prim_arbiter_tree #( + .N(NumPart+1), + .DW($bits(scrmbl_bundle_t)) + ) u_scrmbl_mtx ( + .clk_i, + .rst_ni, + .req_i ( part_scrmbl_mtx_req ), + .data_i ( part_scrmbl_req_bundle ), + .gnt_o ( ), + .idx_o ( scrmbl_mtx_idx ), + .valid_o ( scrmbl_mtx_valid ), + .data_o ( scrmbl_req_bundle ), + .ready_i ( 1'b0 ) + ); + + // Since the ready_i signal of the arbiter is statically set to 1'b0 above, we are always in a + // "backpressure" situation, where the RR arbiter will automatically advance the internal RR state + // to give the current winner max priority in subsequent cycles in order to keep the decision + // stable. Rearbitration occurs once the winning agent deasserts its request. + always_comb begin : p_mutex + part_scrmbl_mtx_gnt = '0; + part_scrmbl_mtx_gnt[scrmbl_mtx_idx] = scrmbl_mtx_valid; + end + + logic [ScrmblBlockWidth-1:0] part_scrmbl_rsp_data; + logic scrmbl_arb_req_ready, scrmbl_arb_rsp_valid; + logic [NumPart:0] part_scrmbl_req_ready, part_scrmbl_rsp_valid; + + otp_ctrl_scrmbl u_scrmbl ( + .clk_i, + .rst_ni, + .cmd_i ( scrmbl_req_bundle.cmd ), + .sel_i ( scrmbl_req_bundle.sel ), + .data_i ( scrmbl_req_bundle.data ), + .valid_i ( scrmbl_req_bundle.valid ), + .ready_o ( scrmbl_arb_req_ready ), + .data_o ( part_scrmbl_rsp_data ), + .valid_o ( scrmbl_arb_rsp_valid ) + ); + + // steer back responses + always_comb begin : p_scmrbl_resp + part_scrmbl_req_ready = '0; + part_scrmbl_rsp_valid = '0; + part_scrmbl_req_ready[scrmbl_mtx_idx] = scrmbl_arb_req_ready; + part_scrmbl_rsp_valid[scrmbl_mtx_idx] = scrmbl_arb_rsp_valid; + end + + ///////////////////////////// + // Direct Access Interface // + ///////////////////////////// + + logic part_init_req; + logic [NumPart-1:0] part_init_done; + part_access_t [NumPart-1:0] part_access_dai; + + otp_ctrl_dai u_otp_ctrl_dai ( + .clk_i, + .rst_ni, + .init_req_i ( pwr_otp_init_req_i.init ), + .init_done_o ( pwr_otp_init_rsp_o.done ), + .part_init_req_o ( part_init_req ), + .part_init_done_i ( part_init_done ), + .escalate_en_i ( lc_escalate_en_i ), + .error_o ( part_error[DaiIdx] ), + .part_access_i ( part_access_dai ), + .dai_addr_i ( dai_addr ), + .dai_cmd_i ( dai_cmd ), + .dai_req_i ( dai_req ), + .dai_wdata_i ( dai_wdata ), + .dai_idle_o ( dai_idle ), + .dai_cmd_done_o ( otp_operation_done ), + .dai_rdata_o ( dai_rdata ), + .otp_req_o ( part_otp_arb_req[DaiIdx] ), + .otp_cmd_o ( part_otp_arb_bundle[DaiIdx].cmd ), + .otp_size_o ( part_otp_arb_bundle[DaiIdx].size ), + .otp_wdata_o ( part_otp_arb_bundle[DaiIdx].wdata ), + .otp_addr_o ( part_otp_arb_bundle[DaiIdx].addr ), + .otp_gnt_i ( part_otp_arb_gnt[DaiIdx] ), + .otp_rvalid_i ( part_otp_rvalid[DaiIdx] ), + .otp_rdata_i ( part_otp_rdata ), + .otp_err_i ( part_otp_err ), + .scrmbl_mtx_req_o ( part_scrmbl_mtx_req[DaiIdx] ), + .scrmbl_mtx_gnt_i ( part_scrmbl_mtx_gnt[DaiIdx] ), + .scrmbl_cmd_o ( part_scrmbl_req_bundle[DaiIdx].cmd ), + .scrmbl_sel_o ( part_scrmbl_req_bundle[DaiIdx].sel ), + .scrmbl_data_o ( part_scrmbl_req_bundle[DaiIdx].data ), + .scrmbl_valid_o ( part_scrmbl_req_bundle[DaiIdx].valid ), + .scrmbl_ready_i ( part_scrmbl_req_ready[DaiIdx] ), + .scrmbl_valid_i ( part_scrmbl_rsp_valid[DaiIdx] ), + .scrmbl_data_i ( part_scrmbl_rsp_data ) + ); + + //////////////////////////////////// + // Lifecycle Transition Interface // + //////////////////////////////////// + + otp_ctrl_lci #( + .Info(PartInfo[LifeCycleIdx]) + ) u_otp_ctrl_lci ( + .clk_i, + .rst_ni, + .escalate_en_i ( lc_escalate_en_i ), + .error_o ( part_error[LciIdx] ), + .lci_idle_o ( lci_idle ), + // TODO: add LC interface + .otp_req_o ( part_otp_arb_req[LciIdx] ), + .otp_cmd_o ( part_otp_arb_bundle[LciIdx].cmd ), + .otp_size_o ( part_otp_arb_bundle[LciIdx].size ), + .otp_wdata_o ( part_otp_arb_bundle[LciIdx].wdata ), + .otp_addr_o ( part_otp_arb_bundle[LciIdx].addr ), + .otp_gnt_i ( part_otp_arb_gnt[LciIdx] ), + .otp_rvalid_i ( part_otp_rvalid[LciIdx] ), + .otp_rdata_i ( part_otp_rdata ), + .otp_err_i ( part_otp_err ) + ); + + ///////////////////////// + // Partition Instances // + ///////////////////////// + + for (genvar k = 0; k < NumPart; k ++) begin : gen_partitions + //////////////////////////////////////////////////////////////////////////////////////////////// + if (PartInfo[k].variant == Unbuffered) begin : gen_unbuffered + otp_ctrl_part_unbuf #( + .Info(PartInfo[k]) + ) u_part_unbuf ( + .clk_i, + .rst_ni, + .init_req_i ( part_init_req ), + .init_done_o ( part_init_done[k] ), + .escalate_en_i ( lc_escalate_en_i ), + .error_o ( part_error[k] ), + .access_i ( part_access_csrs[k] ), + .access_o ( part_access_dai[k] ), + .digest_o ( part_digest[k] ), + // Note: this assumes that partitions with windows always come first. + .tl_i ( tl_win_h2d[k] ), + .tl_o ( tl_win_d2h[k] ), + .otp_req_o ( part_otp_arb_req[k] ), + .otp_cmd_o ( part_otp_arb_bundle[k].cmd ), + .otp_size_o ( part_otp_arb_bundle[k].size ), + .otp_wdata_o ( part_otp_arb_bundle[k].wdata ), + .otp_addr_o ( part_otp_arb_bundle[k].addr ), + .otp_gnt_i ( part_otp_arb_gnt[k] ), + .otp_rvalid_i ( part_otp_rvalid[k] ), + .otp_rdata_i ( part_otp_rdata ), + .otp_err_i ( part_otp_err ) + ); + + // Tie off unused connections. + assign part_scrmbl_mtx_req[k] = '0; + assign part_scrmbl_req_bundle[k] = '0; + // These checks do not exist in this partition type, + // so we always acknowledge the request. + assign integ_chk_ack[k] = 1'b1; + assign cnsty_chk_ack[k] = 1'b1; + + // This stops lint from complaining about unused signals. + logic unused_part_scrmbl_req_ready, unused_part_scrmbl_rsp_valid, unused_part_scrmbl_mtx_gnt; + logic unused_integ_chk_req, unused_cnsty_chk_req; + assign unused_part_scrmbl_mtx_gnt = part_scrmbl_mtx_gnt[k]; + assign unused_part_scrmbl_req_ready = part_scrmbl_req_ready[k]; + assign unused_part_scrmbl_rsp_valid = part_scrmbl_rsp_valid[k]; + assign unused_integ_chk_req = integ_chk_req[k]; + assign unused_cnsty_chk_req = cnsty_chk_req[k]; + + //////////////////////////////////////////////////////////////////////////////////////////////// + end else if (PartInfo[k].variant == Buffered) begin : gen_buffered + otp_ctrl_part_buf #( + .Info(PartInfo[k]) + ) u_part_buf ( + .clk_i, + .rst_ni, + .init_req_i ( part_init_req ), + .init_done_o ( part_init_done[k] ), + .integ_chk_req_i ( integ_chk_req[k] ), + .integ_chk_ack_o ( integ_chk_ack[k] ), + .cnsty_chk_req_i ( cnsty_chk_req[k] ), + .cnsty_chk_ack_o ( cnsty_chk_ack[k] ), + .escalate_en_i ( lc_escalate_en_i ), + .error_o ( part_error[k] ), + .access_i ( part_access_csrs[k] ), + .access_o ( part_access_dai[k] ), + .digest_o ( part_digest[k] ), + .data_o ( ), // TODO: make breakout connections + .otp_req_o ( part_otp_arb_req[k] ), + .otp_cmd_o ( part_otp_arb_bundle[k].cmd ), + .otp_size_o ( part_otp_arb_bundle[k].size ), + .otp_wdata_o ( part_otp_arb_bundle[k].wdata ), + .otp_addr_o ( part_otp_arb_bundle[k].addr ), + .otp_gnt_i ( part_otp_arb_gnt[k] ), + .otp_rvalid_i ( part_otp_rvalid[k] ), + .otp_rdata_i ( part_otp_rdata ), + .otp_err_i ( part_otp_err ), + .scrmbl_mtx_req_o ( part_scrmbl_mtx_req[k] ), + .scrmbl_mtx_gnt_i ( part_scrmbl_mtx_gnt[k] ), + .scrmbl_cmd_o ( part_scrmbl_req_bundle[k].cmd ), + .scrmbl_sel_o ( part_scrmbl_req_bundle[k].sel ), + .scrmbl_data_o ( part_scrmbl_req_bundle[k].data ), + .scrmbl_valid_o ( part_scrmbl_req_bundle[k].valid ), + .scrmbl_ready_i ( part_scrmbl_req_ready[k] ), + .scrmbl_valid_i ( part_scrmbl_rsp_valid[k] ), + .scrmbl_data_i ( part_scrmbl_rsp_data ) + ); + end else begin : gen_invalid + // This is invalid and should break elaboration + assert_static_in_generate_invalid assert_static_in_generate_invalid(); + end + end endmodule : otp_ctrl
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_dai.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_dai.sv new file mode 100644 index 0000000..054b702 --- /dev/null +++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_dai.sv
@@ -0,0 +1,682 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// Direct access interface for OTP controller. +// + +`include "prim_assert.sv" + +module otp_ctrl_dai + import otp_ctrl_pkg::*; + import otp_ctrl_reg_pkg::*; +( + input clk_i, + input rst_ni, + // Init reqest from power manager + input init_req_i, + output logic init_done_o, + // Init request going to partitions + output logic part_init_req_o, + input [NumPart-1:0] part_init_done_i, + // Escalation input. This moves the FSM into a terminal state and locks down + // the DAI. + input lc_tx_t escalate_en_i, + // Output error state of DAI, to be consumed by OTP error/alert logic. + // Note that most errors are not recoverable and move the DAI FSM into + // a terminal error state. + output otp_err_e error_o, + // Access/lock status from partitions + input part_access_t [NumPart-1:0] part_access_i, + // CSR interface + input [OtpByteAddrWidth-1:0] dai_addr_i, + input dai_cmd_e dai_cmd_i, + input logic dai_req_i, + input [NumDaiWords-1:0][31:0] dai_wdata_i, + output logic dai_idle_o, // wired to the status CSRs + output logic dai_cmd_done_o, // this is used to raise an IRQ + output logic [NumDaiWords-1:0][31:0] dai_rdata_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, + // Scrambling mutex request + output logic scrmbl_mtx_req_o, + input scrmbl_mtx_gnt_i, + // Scrambling datapath interface + output otp_scrmbl_cmd_e scrmbl_cmd_o, + output logic [ConstSelWidth-1:0] scrmbl_sel_o, + output logic [ScrmblBlockWidth-1:0] scrmbl_data_o, + output logic scrmbl_valid_o, + input logic scrmbl_ready_i, + input logic scrmbl_valid_i, + input logic [ScrmblBlockWidth-1:0] scrmbl_data_i +); + + //////////////////////// + // Integration Checks // + //////////////////////// + + import prim_util_pkg::vbits; + + localparam int CntWidth = OtpByteAddrWidth - $clog2(ScrmblBlockWidth/8); + + // Integration checks for parameters. + `ASSERT_INIT(CheckNativeOtpWidth0_A, ScrmblBlockWidth % OtpWidth == 0) + `ASSERT_INIT(CheckNativeOtpWidth1_A, 32 % OtpWidth == 0) + + ///////////////////// + // DAI Control FSM // + ///////////////////// + + // Encoding generated with ./sparse-fsm-encode -d 5 -m 20 -n 12 + // Hamming distance histogram: + // + // 0: -- + // 1: -- + // 2: -- + // 3: -- + // 4: -- + // 5: ||||||||||||||| (30.53%) + // 6: |||||||||||||||||||| (38.42%) + // 7: |||||||| (15.79%) + // 8: |||| (8.42%) + // 9: | (3.68%) + // 10: (1.05%) + // 11: | (2.11%) + // 12: -- + // + // Minimum Hamming distance: 5 + // Maximum Hamming distance: 11 + // + typedef enum logic [11:0] { + ResetSt = 12'b000000011100, + InitOtpSt = 12'b010011000101, + InitPartSt = 12'b101011010111, + IdleSt = 12'b000010001011, + ErrorSt = 12'b010010110000, + ReadSt = 12'b111110000001, + ReadWaitSt = 12'b001111100011, + DescrSt = 12'b100101000110, + DescrWaitSt = 12'b011000000010, + WriteSt = 12'b001100111010, + WriteWaitSt = 12'b110111101111, + ScrSt = 12'b101010101000, + ScrWaitSt = 12'b111110011110, + DigClrSt = 12'b011101110100, + DigReadSt = 12'b001101001101, + DigReadWaitSt = 12'b111001111001, + DigSt = 12'b100111011000, + DigPadSt = 12'b100000100101, + DigFinSt = 12'b010101010011, + DigWaitSt = 12'b010100101001 + } state_e; + + typedef enum logic [1:0] { + OtpData = 2'b00, + DaiData = 2'b01, + ScrmblData = 2'b10 + } data_sel_e; + + + typedef enum logic { + PartOffset = 1'b0, + DaiOffset = 1'b1 + } addr_sel_e; + + state_e state_d, state_q; + logic [CntWidth-1:0] cnt_d, cnt_q; + logic cnt_en, cnt_clr; + otp_err_e error_d, error_q; + logic data_en, data_clr; + data_sel_e data_sel; + addr_sel_e base_sel_d, base_sel_q; + logic [ScrmblBlockWidth-1:0] data_q; + logic [NumPartWidth-1:0] part_idx; + logic [NumPart-1:0][OtpAddrWidth-1:0] digest_addr_lut; + + // Output partition error state. + assign error_o = error_q; + // Working register is connected to data outputs. + assign dai_rdata_o = data_q; + assign otp_wdata_o = data_q; + assign scrmbl_data_o = data_q; + + always_comb begin : p_fsm + state_d = state_q; + + // Init signals + init_done_o = 1'b1; + part_init_req_o = 1'b0; + + // DAI signals + dai_idle_o = 1'b0; + dai_cmd_done_o = 1'b0; + + // OTP signals + otp_req_o = 1'b0; + otp_cmd_o = OtpInit; + + // Scrambling mutex + scrmbl_mtx_req_o = 1'b0; + + // Scrambling datapath + scrmbl_cmd_o = LoadShadow; + scrmbl_sel_o = '0; + scrmbl_valid_o = 1'b0; + + // Counter + cnt_en = 1'b0; + cnt_clr = 1'b0; + base_sel_d = base_sel_q; + + // Temporary data register + data_en = 1'b0; + data_clr = 1'b0; + data_sel = OtpData; + + // Error Register + error_d = error_q; + + unique case (state_q) + /////////////////////////////////////////////////////////////////// + // We get here after reset and wait until the power manager + // requests OTP initialization. If initialization is requested, + // an init command is written to the OTP macro, and we move on + // to the InitOtpSt waiting state. + ResetSt: begin + init_done_o = 1'b0; + data_clr = 1'b1; + if (init_req_i) begin + otp_req_o = 1'b1; + if (otp_gnt_i) begin + state_d = InitOtpSt; + end + end + end + /////////////////////////////////////////////////////////////////// + // We wait here unitl the OTP macro has initialized without + // error. If an error occurred during this stage, we latch that + // error and move into a terminal error state. + InitOtpSt: begin + init_done_o = 1'b0; + if (otp_rvalid_i) begin + if ((!(otp_err_i inside {NoErr, OtpReadCorrErr}))) begin + state_d = ErrorSt; + error_d = otp_err_i; + end else begin + state_d = InitPartSt; + end + end + end + /////////////////////////////////////////////////////////////////// + // Since the OTP macro is now functional, we can send out an + // initialization request to all partitions and wait until they + // all have initialized. + InitPartSt: begin + init_done_o = 1'b0; + part_init_req_o = 1'b1; + if (part_init_done_i == {NumPart{1'b1}}) begin + state_d = IdleSt; + end + end + /////////////////////////////////////////////////////////////////// + // Idle state where we wait for incoming commands. + // Invalid commands trigger a CmdInvErr, which is recoverable. + IdleSt: begin + dai_idle_o = 1'b1; + if (dai_req_i) begin + // This clears previous (recoverable) errors. + error_d = NoErr; + // Clear the temporary data register. + data_clr = 1'b1; + unique case (dai_cmd_i) + DaiRead: begin + state_d = ReadSt; + base_sel_d = DaiOffset; + end + DaiWrite: begin + // Fetch data block. + data_sel = DaiData; + data_en = 1'b1; + base_sel_d = DaiOffset; + // If this partition is scrambled, directly go to write scrambling first. + if (PartInfo[part_idx].scrambled) begin + state_d = ScrSt; + end else begin + state_d = WriteSt; + end + end + DaiDigest: begin + state_d = DigClrSt; + scrmbl_mtx_req_o = 1'b1; + base_sel_d = PartOffset; + end + default: begin + // Invalid commands get caught here. This is a recoverable error. + error_d = CmdInvErr; + end + endcase // dai_cmd_i + end // dai_req_i + end + /////////////////////////////////////////////////////////////////// + // Each time we request a block of data from OTP, we re-check + // whether read access has been locked for this partition. If + // that is the case, we immediately bail out. Otherwise, we + // request a block of data from OTP. + ReadSt: begin + if (part_access_i[part_idx].read_lock == Unlocked) begin + otp_req_o = 1'b1; + otp_cmd_o = OtpRead; + if (otp_gnt_i) begin + state_d = ReadWaitSt; + end + end else begin + state_d = IdleSt; + error_d = AccessErr; // Signal this error, but do not go into terminal error state. + dai_cmd_done_o = 1'b1; + end + end + /////////////////////////////////////////////////////////////////// + // Wait for OTP response and write to readout register. Check + // whether descrambling is required or not. In case an OTP + // transaction fails, latch the OTP error code, and jump to + // terminal error state. + ReadWaitSt: begin + if (otp_rvalid_i) begin + // Check OTP return code. + if ((!(otp_err_i inside {NoErr, OtpReadCorrErr}))) begin + state_d = ErrorSt; + error_d = otp_err_i; + end else begin + data_en = 1'b1; + if (PartInfo[part_idx].scrambled) begin + state_d = DescrSt; + end else begin + state_d = IdleSt; + dai_cmd_done_o = 1'b1; + end + // Signal soft ECC errors, but do not go into terminal error state. + if (otp_err_i == OtpReadCorrErr) begin + error_d = otp_err_i; + end + end + end + end + /////////////////////////////////////////////////////////////////// + // Descrambling state. This first acquires the scrambling + // datapath mutex. Note that once the mutex is acquired, we have + // exclusive access to the scrambling datapath until we release + // the mutex by deasserting scrmbl_mtx_req_o. + DescrSt: begin + scrmbl_mtx_req_o = 1'b1; + scrmbl_valid_o = 1'b1; + scrmbl_cmd_o = Decrypt; + scrmbl_sel_o = PartInfo[part_idx].key_idx; + if (scrmbl_mtx_gnt_i && scrmbl_ready_i) begin + state_d = DescrWaitSt; + end + end + /////////////////////////////////////////////////////////////////// + // Wait for the descrambled data to return. Note that we release + // the mutex lock upon leaving this state. + DescrWaitSt: begin + scrmbl_mtx_req_o = 1'b1; + scrmbl_sel_o = PartInfo[part_idx].key_idx; + data_sel = ScrmblData; + if (scrmbl_valid_i) begin + state_d = IdleSt; + data_en = 1'b1; + dai_cmd_done_o = 1'b1; + end + end + /////////////////////////////////////////////////////////////////// + // First, check whether write accesses are allowed to this + // partition, and error out otherwise. Note that for buffered + // partitions, we do not allow DAI writes to the digest offset. + // Unbuffered partitions have SW managed digests, hence that + // check is not needed in that case. The LC partition is + // permanently write locked and can hence not be written via the DAI. + WriteSt: begin + if (part_access_i[part_idx].write_lock == Unlocked && + // If this is a HW digest write to a buffered partition. + ((PartInfo[part_idx].variant == Buffered && PartInfo[part_idx].hw_digest && + base_sel_q == PartOffset && otp_addr_o == digest_addr_lut[part_idx]) || + // If this is a non HW digest write to a buffered partition. + (PartInfo[part_idx].variant == Buffered && PartInfo[part_idx].hw_digest && + base_sel_q == DaiOffset && otp_addr_o < digest_addr_lut[part_idx]) || + // If this is a write to an unbuffered partition + (PartInfo[part_idx].variant != Buffered && base_sel_q == DaiOffset))) begin + otp_req_o = 1'b1; + otp_cmd_o = OtpWrite; + if (otp_gnt_i) begin + state_d = WriteWaitSt; + end + end else begin + state_d = IdleSt; + error_d = AccessErr; // Signal this error, but do not go into terminal error state. + dai_cmd_done_o = 1'b1; + end + end + /////////////////////////////////////////////////////////////////// + // Wait for OTP response, and then go back to idle. 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. Note that non-blank errors are recoverable. + if ((!(otp_err_i inside {NoErr, OtpWriteBlankErr}))) begin + state_d = ErrorSt; + error_d = otp_err_i; + end else begin + state_d = IdleSt; + dai_cmd_done_o = 1'b1; + // Signal non-blank state, but do not go to terminal error state. + if (otp_err_i == OtpWriteBlankErr) begin + error_d = otp_err_i; + end + end + end + end + /////////////////////////////////////////////////////////////////// + // Scrambling state. This first acquires the scrambling + // datapath mutex. Note that once the mutex is acquired, we have + // exclusive access to the scrambling datapath until we release + // the mutex by deasserting scrmbl_mtx_req_o. + ScrSt: begin + scrmbl_mtx_req_o = 1'b1; + scrmbl_valid_o = 1'b1; + scrmbl_cmd_o = Encrypt; + scrmbl_sel_o = PartInfo[part_idx].key_idx; + if (scrmbl_mtx_gnt_i && scrmbl_ready_i) begin + state_d = ScrWaitSt; + end + end + /////////////////////////////////////////////////////////////////// + // Wait for the scrambled data to return. Note that we release + // the mutex lock upon leaving this state. + ScrWaitSt: begin + scrmbl_mtx_req_o = 1'b1; + data_sel = ScrmblData; + if (scrmbl_valid_i) begin + state_d = WriteSt; + data_en = 1'b1; + end + end + /////////////////////////////////////////////////////////////////// + // First, acquire the mutex for the digest and clear the digest state. + DigClrSt: begin + scrmbl_mtx_req_o = 1'b1; + scrmbl_valid_o = 1'b1; + // Need to reset the digest state and set digest mode to "standard". + scrmbl_sel_o = StandardMode; + scrmbl_cmd_o = DigestInit; + if (scrmbl_mtx_gnt_i && scrmbl_ready_i) begin + state_d = DigReadSt; + end + end + /////////////////////////////////////////////////////////////////// + // This requests a 64bit block to be pushed into the digest datapath. + // We also check here whether the partition has been write locked. + DigReadSt: begin + scrmbl_mtx_req_o = 1'b1; + if (part_access_i[part_idx].read_lock == Unlocked && + part_access_i[part_idx].write_lock == Unlocked) begin + otp_req_o = 1'b1; + otp_cmd_o = OtpRead; + if (otp_gnt_i) begin + state_d = DigReadWaitSt; + end + end else begin + state_d = IdleSt; + error_d = AccessErr; // Signal this error, but do not go into terminal error state. + dai_cmd_done_o = 1'b1; + end + end + /////////////////////////////////////////////////////////////////// + // Wait for OTP response and write to readout register. Check + // whether descrambling is required or not. In case an OTP + // transaction fails, latch the OTP error code, and jump to + // terminal error state. + DigReadWaitSt: begin + scrmbl_mtx_req_o = 1'b1; + if (otp_rvalid_i) begin + cnt_en = 1'b1; + // Check OTP return code. + if ((!(otp_err_i inside {NoErr, OtpReadCorrErr}))) begin + state_d = ErrorSt; + error_d = otp_err_i; + end else begin + data_en = 1'b1; + state_d = DigSt; + // Signal soft ECC errors, but do not go into terminal error state. + if (otp_err_i == OtpReadCorrErr) begin + error_d = otp_err_i; + end + end + end + end + /////////////////////////////////////////////////////////////////// + // Push the word read into the scrambling datapath. The last + // block is repeated in case the number blocks in this partition + // is odd. + DigSt: begin + scrmbl_mtx_req_o = 1'b1; + scrmbl_valid_o = 1'b1; + // No need to digest the digest value itself + if (otp_addr_o == digest_addr_lut[part_idx]) begin + // Trigger digest round in case this is the second block in a row. + if (cnt_q[0]) begin + scrmbl_cmd_o = Digest; + if (scrmbl_ready_i) begin + state_d = DigFinSt; + end + // Otherwise, just load low word and go to padding state. + end else if (scrmbl_ready_i) begin + state_d = DigPadSt; + end + end else begin + // Trigger digest round in case this is the second block in a row. + if (cnt_q[0]) begin + scrmbl_cmd_o = Digest; + end + // Go back and fetch more data blocks. + if (scrmbl_ready_i) begin + state_d = DigReadSt; + end + end + end + /////////////////////////////////////////////////////////////////// + // Padding state, just repeat the last block and go to digest + // finalization. + DigPadSt: begin + scrmbl_mtx_req_o = 1'b1; + scrmbl_valid_o = 1'b1; + scrmbl_cmd_o = Digest; + if (scrmbl_ready_i) begin + state_d = DigFinSt; + end + end + /////////////////////////////////////////////////////////////////// + // Trigger digest finalization and go wait for the result. + DigFinSt: begin + scrmbl_mtx_req_o = 1'b1; + scrmbl_valid_o = 1'b1; + scrmbl_cmd_o = DigestFinalize; + if (scrmbl_ready_i) begin + state_d = DigWaitSt; + end + end + /////////////////////////////////////////////////////////////////// + // Wait for the digest to return, and write the result to OTP. + // Note that the write address will be correct in this state, + // since the counter has been stepped to the correct address as + // part of the readout sequence, and the correct size for this + // access has been loaded before. + DigWaitSt: begin + scrmbl_mtx_req_o = 1'b1; + data_sel = ScrmblData; + if (scrmbl_valid_i) begin + state_d = WriteSt; + data_en = 1'b1; + end + end + /////////////////////////////////////////////////////////////////// + // Terminal Error State. This locks access to the DAI. Make sure + // an FsmErr error code is assigned here, in case no error code has + // been assigned yet. + 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 termninal error state in case of + // escalation, and lock access to the DAI down. + if (escalate_en_i != Off) begin + state_d = ErrorSt; + error_d = EscErr; + end + end + end + + //////////////////////////// + // Partition Select Logic // + //////////////////////////// + + // This checks which partition the address belongs to by comparing + // the incoming address to the partition address ranges. The onehot + // bitvector generated by the parallel comparisons is fed into a + // binary tree that determines the partition index with O(log(N)) delay. + + logic [NumPart-1:0] part_sel_oh; + for (genvar k = 0; k < NumPart; k++) begin : gen_part_sel + localparam logic [OtpByteAddrWidth:0] PartEnd = (OtpByteAddrWidth+1)'(PartInfo[k].offset) + + (OtpByteAddrWidth+1)'(PartInfo[k].size); + assign part_sel_oh[k] = (dai_addr_i >= PartInfo[k].offset) & ({1'b0, dai_addr_i} < PartEnd); + // Needed for other address and access checks. + localparam logic [OtpByteAddrWidth-1:0] DigestOffset = OtpByteAddrWidth'(PartEnd - + ScrmblBlockWidth/8); + assign digest_addr_lut[k] = DigestOffset >> OtpAddrShift; + end + + `ASSERT(PartSelMustBeOnehot_A, $onehot0(part_sel_oh)) + + prim_arbiter_fixed #( + .N(NumPart), + .EnDataPort(0) + ) i_prim_arbiter_fixed ( + .clk_i, + .rst_ni, + .req_i ( part_sel_oh ), + .data_i ( '{default: '0} ), + .gnt_o ( ), // unused + .idx_o ( part_idx ), + .valid_o ( ), // unused + .data_o ( ), // unused + .ready_i ( 1'b0 ) + ); + + ///////////////////////////////////// + // Address Calculations for Digest // + ///////////////////////////////////// + + // Depending on whether this is a 32bit or 64bit partition, we cut off the lower address bits. + logic [OtpByteAddrWidth-1:0] addr_base; + assign addr_base = (base_sel_q == PartOffset) ? PartInfo[part_idx].offset : + (PartInfo[part_idx].scrambled) ? {dai_addr_i[OtpByteAddrWidth-1:3], 3'h0} : + {dai_addr_i[OtpByteAddrWidth-1:2], 2'h0}; + + // OTP transaction sizes are 64bit for scrambled partitions, and when digesting. + // Otherwise, they are 32bit. + assign otp_size_o = (PartInfo[part_idx].scrambled || base_sel_q == PartOffset) ? + OtpSizeWidth'(unsigned'(ScrmblBlockWidth / OtpWidth - 1)) : + OtpSizeWidth'(unsigned'(32 / OtpWidth - 1)); + + // Address counter - this is only used for computing a digest, hence the increment is + // fixed to 8 byte. + 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. + logic [OtpByteAddrWidth-1:0] addr_calc; + assign addr_calc = {cnt_q, {$clog2(ScrmblBlockWidth/8){1'b0}}} + addr_base; + assign otp_addr_o = addr_calc >> OtpAddrShift; + + /////////////// + // Registers // + /////////////// + + always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs + if (!rst_ni) begin + state_q <= ResetSt; + error_q <= NoErr; + cnt_q <= '0; + data_q <= '0; + base_sel_q <= DaiOffset; + end else begin + state_q <= state_d; + error_q <= error_d; + cnt_q <= cnt_d; + base_sel_q <= base_sel_d; + + // Working register + if (data_clr) begin + data_q <= '0; + end else if (data_en) begin + if (data_sel == ScrmblData) begin + data_q <= scrmbl_data_i; + end else if (data_sel == DaiData) begin + data_q <= dai_wdata_i; + end else begin + data_q <= otp_rdata_i; + end + end + end + end + + //////////////// + // Assertions // + //////////////// + + // Known assertions + `ASSERT_KNOWN(InitDoneKnown_A, init_done_o) + `ASSERT_KNOWN(PartInitReqKnown_A, part_init_req_o) + `ASSERT_KNOWN(ErrorKnown_A, error_o) + `ASSERT_KNOWN(DaiIdleKnown_A, dai_idle_o) + `ASSERT_KNOWN(DaiRdataKnown_A, dai_rdata_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) + `ASSERT_KNOWN(ScrmblMtxReqKnown_A, scrmbl_mtx_req_o) + `ASSERT_KNOWN(ScrmblCmdKnown_A, scrmbl_cmd_o) + `ASSERT_KNOWN(ScrmblSelKnown_A, scrmbl_sel_o) + `ASSERT_KNOWN(ScrmblDataKnown_A, scrmbl_data_o) + `ASSERT_KNOWN(ScrmblValidKnown_A, scrmbl_valid_o) + + // OTP error response + `ASSERT(OtpErrorState_A, + state_q inside {InitOtpSt, ReadWaitSt, WriteWaitSt, DigReadWaitSt} && otp_rvalid_i && + !(otp_err_i inside {NoErr, OtpReadCorrErr}) + |=> + state_q == ErrorSt && error_o == $past(otp_err_i)) + +endmodule : otp_ctrl_dai
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_lci.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_lci.sv new file mode 100644 index 0000000..7e47aa4 --- /dev/null +++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_lci.sv
@@ -0,0 +1,41 @@ +// 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, + // Escalation input. This moves the FSM into a terminal state and locks down + // the partition. + input lc_tx_t escalate_en_i, + + // TODO: add lifecycle transition interface + // 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 +); + +endmodule : otp_ctrl_lci
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_parity_reg.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_parity_reg.sv new file mode 100644 index 0000000..34bd250 --- /dev/null +++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_parity_reg.sv
@@ -0,0 +1,88 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// Parity-protected register file for buffered OTP partitions. +// +// TODO: discuss whether reset is allowed here. We may also need a secure wiping feature. + +`include "prim_assert.sv" + +module otp_ctrl_parity_reg #( + parameter int Width = 32, // bit + parameter int Depth = 128, + localparam int Aw = prim_util_pkg::vbits(Depth) // derived parameter +) ( + input logic clk_i, + input logic rst_ni, + + input logic wren_i, + input logic [Aw-1:0] addr_i, + input logic [Width-1:0] wdata_i, + + // Concurrent output of the register state. + output logic [Depth-1:0][Width-1:0] data_o, + // Concurrent parity check error is flagged via this signal. + output logic parity_err_o +); + + // Integration checks for parameters. + `ASSERT_INIT(WidthMustBeByteAligned_A, Width % 8 == 0) + + logic [Depth-1:0][Width-1:0] data_d, data_q; + logic [Depth-1:0][Width/8-1:0] parity_d, parity_q; + + if (Depth == 1) begin : gen_one_word_only + always_comb begin : p_write + data_o = data_q; + data_d = data_q; + parity_d = parity_q; + + if (wren_i && 32'(addr_i) < Depth) begin + data_d[0] = wdata_i; + // Calculate EVEN parity bit for each Byte written. + for (int k = 0; k < Width/8; k ++) begin + parity_d[0][k] = ^wdata_i[k*8 +: 8]; + end + end + end + end else begin : gen_multiple_words + always_comb begin : p_write + data_o = data_q; + data_d = data_q; + parity_d = parity_q; + + if (wren_i && 32'(addr_i) < Depth) begin + data_d[addr_i] = wdata_i; + // Calculate EVEN parity bit for each Byte written. + for (int k = 0; k < Width/8; k ++) begin + parity_d[addr_i][k] = ^wdata_i[k*8 +: 8]; + end + end + end + end + + always_comb begin : p_parity + // Concurrent parity check. + parity_err_o = 1'b0; + for (int j = 0; j < Depth; j ++) begin + for (int k = 0; k < Width/8; k ++) begin + parity_err_o |= ^{data_q[j][k*8 +: 8], parity_q[j][k]}; + end + end + end + + always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs + if (!rst_ni) begin + parity_q <= '0; + data_q <= '0; + end else begin + parity_q <= parity_d; + data_q <= data_d; + end + end + + `ASSERT_KNOWN(ParityKnown_A, parity_q) + `ASSERT_KNOWN(DataKnown_A, data_q) + +endmodule : otp_ctrl_parity_reg
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_part_buf.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_part_buf.sv new file mode 100644 index 0000000..170cc84 --- /dev/null +++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_part_buf.sv
@@ -0,0 +1,656 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// Buffered partition for OTP controller. +// + +`include "prim_assert.sv" + +module otp_ctrl_part_buf + import otp_ctrl_pkg::*; + import otp_ctrl_reg_pkg::*; +#( + // Partition information. + parameter part_info_t Info +) ( + input clk_i, + input rst_ni, + // Pulse to start partition initialisation (required once per power cycle). + input init_req_i, + output logic init_done_o, + // Integrity check requests + input integ_chk_req_i, + output logic integ_chk_ack_o, + // Consistency check requests + input cnsty_chk_req_i, + output logic cnsty_chk_ack_o, + // Escalation input. This moves the FSM into a terminal state and locks down + // the partition. + input lc_tx_t escalate_en_i, + // 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, + // Access/lock status + input part_access_t access_i, // runtime lock from CSRs + output part_access_t access_o, + // Buffered 64bit digest output. + output logic [ScrmblBlockWidth-1:0] digest_o, + output logic [Info.size*8-1:0] data_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, + // Scrambling mutex request + output logic scrmbl_mtx_req_o, + input scrmbl_mtx_gnt_i, + // Scrambling datapath interface + output otp_scrmbl_cmd_e scrmbl_cmd_o, + output logic [ConstSelWidth-1:0] scrmbl_sel_o, + output logic [ScrmblBlockWidth-1:0] scrmbl_data_o, + output logic scrmbl_valid_o, + input logic scrmbl_ready_i, + input logic scrmbl_valid_i, + input logic [ScrmblBlockWidth-1:0] scrmbl_data_i +); + + //////////////////////// + // Integration Checks // + //////////////////////// + + import prim_util_pkg::vbits; + + localparam int DigestOffset = Info.offset + Info.size - ScrmblBlockWidth/8; + localparam int NumScrmblBlocks = Info.size / (ScrmblBlockWidth/8); + localparam int CntWidth = vbits(NumScrmblBlocks); + + // Integration checks for parameters. + `ASSERT_INIT(OffsetMustBeBlockAligned_A, Info.offset % ScrmblBlockWidth/8 == 0) + `ASSERT_INIT(SizeMustBeBlockAligned_A, Info.size % ScrmblBlockWidth/8 == 0) + `ASSERT(ScrambledImpliesDigest_A, Info.scrambled |-> Info.hw_digest) + `ASSERT(WriteLockImpliesDigest_A, Info.read_lock |-> Info.hw_digest) + `ASSERT(ReadLockImpliesDigest_A, Info.write_lock |-> Info.hw_digest) + + /////////////////////// + // OTP Partition FSM // + /////////////////////// + + // Encoding generated with ./sparse-fsm-encode -d 5 -m 16 -n 12 + // Hamming distance histogram: + // + // 0: -- + // 1: -- + // 2: -- + // 3: -- + // 4: -- + // 5: |||||||||||||||| (29.17%) + // 6: |||||||||||||||||||| (35.83%) + // 7: ||||||||||| (20.83%) + // 8: |||| (7.50%) + // 9: | (2.50%) + // 10: | (3.33%) + // 11: (0.83%) + // 12: -- + // + // Minimum Hamming distance: 5 + // Maximum Hamming distance: 11 + // + typedef enum logic [11:0] { + ResetSt = 12'b110001101001, + InitSt = 12'b000100100000, + InitWaitSt = 12'b011101011010, + InitDescrSt = 12'b111010110000, + InitDescrWaitSt = 12'b110111000011, + IdleSt = 12'b000000011100, + IntegScrSt = 12'b001111101011, + IntegScrWaitSt = 12'b000001000111, + IntegDigClrSt = 12'b100110001101, + IntegDigSt = 12'b101000110111, + IntegDigPadSt = 12'b110101010100, + IntegDigFinSt = 12'b101011001000, + IntegDigWaitSt = 12'b010010101111, + CnstyReadSt = 12'b001110000110, + CnstyReadWaitSt = 12'b011011011101, + ErrorSt = 12'b000111110101 + } state_e; + + typedef enum logic { + ScrmblData, + OtpData + } data_sel_e; + + typedef enum logic { + PartOffset, + DigOffset + } base_sel_e; + + state_e state_d, state_q; + otp_err_e error_d, error_q; + data_sel_e data_sel; + base_sel_e base_sel; + access_e dout_gate_d, dout_gate_q; + logic [CntWidth-1:0] cnt_d, cnt_q; + logic cnt_en, cnt_clr; + logic parity_err; + logic buffer_reg_en; + logic [ScrmblBlockWidth-1:0] data_mux; + + // Output partition error state. + assign error_o = error_q; + + // This partition cannot do any write accesses, hence we tie this + // constantly off. + assign otp_wdata_o = '0; + assign otp_cmd_o = OtpRead; + + always_comb begin : p_fsm + state_d = state_q; + + // Redundantly encoded lock signal for buffer regs. + dout_gate_d = dout_gate_q; + + // OTP signals + otp_req_o = 1'b0; + + // Scrambling mutex + scrmbl_mtx_req_o = 1'b0; + + // Scrambling datapath + scrmbl_cmd_o = LoadShadow; + scrmbl_sel_o = '0; + scrmbl_valid_o = 1'b0; + + // Counter + cnt_en = 1'b0; + cnt_clr = 1'b0; + base_sel = PartOffset; + + // Buffer register + buffer_reg_en = 1'b0; + data_sel = OtpData; + + // Error Register + error_d = error_q; + + // Integrity/Consistency check responses + cnsty_chk_ack_o = 1'b0; + integ_chk_ack_o = 1'b0; + + unique case (state_q) + /////////////////////////////////////////////////////////////////// + // State right after reset. Wait here until we get a an + // initialization request. + ResetSt: begin + if (init_req_i) begin + state_d = InitSt; + end + end + /////////////////////////////////////////////////////////////////// + // Initialization reads out the digest only in unbuffered + // partitions. Wait here until the OTP request has been granted. + // And then wait until the OTP word comes back. + InitSt: begin + otp_req_o = 1'b1; + if (otp_gnt_i) begin + state_d = InitWaitSt; + end + end + /////////////////////////////////////////////////////////////////// + // Wait for OTP response and write to buffer register, then go to + // descrambling state. In case an OTP transaction fails, latch the + // OTP error code and jump to a + // terminal error state. + InitWaitSt: begin + if (otp_rvalid_i) begin + buffer_reg_en = 1'b1; + // The only error we tolerate is an ECC soft error. However, + // we still signal that error via the error state output. + if (!(otp_err_i inside {NoErr, OtpReadCorrErr})) begin + state_d = ErrorSt; + error_d = otp_err_i; + end else begin + // Once we've read and descrambled the whole partition, we can go to integrity + // verification. Note that the last block is the digest value, which does not + // have to be descrambled. + if (cnt_q == NumScrmblBlocks-1) begin + state_d = IntegDigClrSt; + // Only need to descramble if this is a scrambled partition. + // Otherwise, we can just go back to InitSt and read the next block. + end else if (Info.scrambled) begin + state_d = InitDescrSt; + end else begin + state_d = InitSt; + cnt_en = 1'b1; + end + // Signal ECC soft errors, but do not go into terminal error state. + if (otp_err_i == OtpReadCorrErr) begin + error_d = otp_err_i; + end + end + end + end + /////////////////////////////////////////////////////////////////// + // Descrambling state. This first acquires the scrambling + // datapath mutex. Note that once the mutex is acquired, we have + // exclusive access to the scrambling datapath until we release + // the mutex by deasserting scrmbl_mtx_req_o. + InitDescrSt: begin + scrmbl_mtx_req_o = 1'b1; + scrmbl_valid_o = 1'b1; + scrmbl_cmd_o = Decrypt; + scrmbl_sel_o = Info.key_idx; + if (scrmbl_mtx_gnt_i && scrmbl_ready_i) begin + state_d = InitDescrWaitSt; + end + end + /////////////////////////////////////////////////////////////////// + // Wait for the descrambled data to return. Note that we release + // the mutex lock upon leaving this state. + InitDescrWaitSt: begin + scrmbl_mtx_req_o = 1'b1; + scrmbl_sel_o = Info.key_idx; + data_sel = ScrmblData; + if (scrmbl_valid_i) begin + state_d = InitSt; + buffer_reg_en = 1'b1; + cnt_en = 1'b1; + end + end + /////////////////////////////////////////////////////////////////// + // Idle state. We basically wait for integrity and consistency check + // triggers in this state. + IdleSt: begin + if (integ_chk_req_i) begin + state_d = IntegDigClrSt; + end else if (cnsty_chk_req_i) begin + state_d = CnstyReadSt; + cnt_clr = 1'b1; + end + end + /////////////////////////////////////////////////////////////////// + // Read the digest. Wait here until the OTP request has been granted. + // And then wait until the OTP word comes back. + CnstyReadSt: begin + otp_req_o = 1'b1; + // In case this partition has a hardware digest, we only have to read + // and compare the digest value. In that case we select the digest offset here. + // Otherwise we have to read and compare the whole partition, in which case we + // select the partition offset, which is the default assignment of base_sel. + if (Info.hw_digest) begin + base_sel = DigOffset; + end + if (otp_gnt_i) begin + state_d = CnstyReadWaitSt; + end + end + /////////////////////////////////////////////////////////////////// + // Wait for OTP response and and compare the digest. In case there is + // a mismatch, lock down the partition and go into the terminal error + // state. In case an OTP transaction fails, latch the OTP error code + // and jump to a terminal error state. + CnstyReadWaitSt: begin + if (otp_rvalid_i) begin + // The only error we tolerate is an ECC soft error. However, + // we still signal that error via the error state output. + if (!(otp_err_i inside {NoErr, OtpReadCorrErr})) begin + state_d = ErrorSt; + error_d = otp_err_i; + end else begin + // Check whether we need to compare the digest or the full partition + // contents here. + if (Info.hw_digest) begin + // Note that we ignore this check if the digest is still blank. + if (digest_o == data_mux || data_mux == '0 && digest_o == '0) begin + state_d = IdleSt; + cnsty_chk_ack_o = 1'b1; + // Error out and lock the partition if this check fails. + end else begin + state_d = ErrorSt; + error_d = CnstyErr; + end + end else begin + // Check whether the read data corresponds with the data buffered in regs. + if (scrmbl_data_o == data_mux) begin + // Can go back to idle and acknowledge the + // request if this is the last block. + if (cnt_q == NumScrmblBlocks-1) begin + state_d = IdleSt; + cnsty_chk_ack_o = 1'b1; + // Need to go back and read out more blocks. + end else begin + state_d = CnstyReadSt; + cnt_en = 1'b1; + end + end else begin + state_d = ErrorSt; + error_d = CnstyErr; + end + end + end + end + end + /////////////////////////////////////////////////////////////////// + // First, acquire the mutex for the digest and clear the digest state. + IntegDigClrSt: begin + // Check whether this partition requires checking at all. + if (Info.hw_digest) begin + scrmbl_mtx_req_o = 1'b1; + scrmbl_valid_o = 1'b1; + cnt_clr = 1'b1; + // Need to reset the digest state and set it to chained + // mode if this partition is scrambled. + scrmbl_cmd_o = DigestInit; + if (Info.scrambled) begin + scrmbl_sel_o = ChainedMode; + if (scrmbl_mtx_gnt_i && scrmbl_ready_i) begin + state_d = IntegScrSt; + end + // If this partition is not scrambled, we can just directly + // jump to the digest state. + end else begin + scrmbl_sel_o = StandardMode; + if (scrmbl_mtx_gnt_i && scrmbl_ready_i) begin + state_d = IntegDigSt; + end + end + // Otherwise, if this partition is not digest protected, + // we can just acknowledge the request and return to idle, + // since there is nothing to check. + end else begin + state_d = IdleSt; + integ_chk_ack_o = 1'b1; + end + end + /////////////////////////////////////////////////////////////////// + // Scramble buffered data (which is held in plaintext form). + // This moves the previous scrambling result into the shadow reg + // for later use. + IntegScrSt: begin + scrmbl_mtx_req_o = 1'b1; + scrmbl_valid_o = 1'b1; + scrmbl_cmd_o = Encrypt; + scrmbl_sel_o = Info.key_idx; + if (scrmbl_ready_i) begin + state_d = IntegScrWaitSt; + end + end + /////////////////////////////////////////////////////////////////// + // Wait for the scrambled data to return. + IntegScrWaitSt: begin + scrmbl_mtx_req_o = 1'b1; + scrmbl_sel_o = Info.key_idx; + if (scrmbl_valid_i) begin + state_d = IntegDigSt; + end + end + /////////////////////////////////////////////////////////////////// + // Push the word read into the scrambling datapath. The last + // block is repeated in case the number blocks in this partition + // is odd. + IntegDigSt: begin + scrmbl_mtx_req_o = 1'b1; + scrmbl_valid_o = 1'b1; + if (scrmbl_ready_i) begin + cnt_en = 1'b1; + // No need to digest the digest value itself + if (cnt_q == NumScrmblBlocks-2) begin + // Note that the digest operates on 128bit blocks since the data is fed in via the + // PRESENT key input. Therefore, we only trigger a digest update on every second + // 64bit block that is pushed into the scrambling datapath. + if (cnt_q[0]) begin + scrmbl_cmd_o = Digest; + state_d = IntegDigFinSt; + end else begin + state_d = IntegDigPadSt; + end + end else begin + // Trigger digest round in case this is the second block in a row. + if (cnt_q[0]) begin + scrmbl_cmd_o = Digest; + end + // Go back and scramble the next data block if this is + // a scrambled partition. Otherwise just stay here. + if (Info.scrambled) begin + state_d = IntegScrSt; + end + end + end + end + /////////////////////////////////////////////////////////////////// + // Padding state. When we get here, we've copied the last encryption + // result into the shadow register such that we've effectively + // repeated the last block twice in order to pad the data to 128bit. + IntegDigPadSt: begin + scrmbl_mtx_req_o = 1'b1; + scrmbl_valid_o = 1'b1; + scrmbl_cmd_o = Digest; + if (scrmbl_ready_i) begin + state_d = IntegDigFinSt; + end + end + /////////////////////////////////////////////////////////////////// + // Trigger digest finalization and go wait for the result. + IntegDigFinSt: begin + scrmbl_mtx_req_o = 1'b1; + scrmbl_valid_o = 1'b1; + scrmbl_cmd_o = DigestFinalize; + if (scrmbl_ready_i) begin + state_d = IntegDigWaitSt; + end + end + /////////////////////////////////////////////////////////////////// + // Wait for the digest to return, and double check whether the digest + // matches. If yes, unlock the partition. Otherwise, go into the terminal + // error state, where the partition will be locked down. + IntegDigWaitSt: begin + scrmbl_mtx_req_o = 1'b1; + data_sel = ScrmblData; + if (scrmbl_valid_i) begin + // This is the only way the buffer regs can get unlocked. + // Note that we ignore this check if the digest is still blank. + if (digest_o == data_mux || digest_o == '0) begin + state_d = IdleSt; + // If the partition is still locked, this is the first integrity check after + // initialization. This is the only way the buffer regs can get unlocked. + if (dout_gate_q != Unlocked) begin + dout_gate_d = Unlocked; + // Otherwise, this integrity check has requested by the LFSR timer, and we have + // to acknowledge its completion. + end else begin + integ_chk_ack_o = 1'b1; + end + // Error out and lock the partition if this check fails. + end else begin + state_d = ErrorSt; + error_d = IntegErr; + 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 + dout_gate_d = Locked; + 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 termninal error state in case of + // a parity error or escalation, and lock access to the partition down. + if (parity_err) begin + state_d = ErrorSt; + error_d = ParityErr; + end + if (escalate_en_i != Off) begin + state_d = ErrorSt; + error_d = EscErr; + end + end + end + + //////////////////////////// + // Address Calc and Muxes // + //////////////////////////// + + // Address counter - this is only used for computing a digest, hence the increment is + // fixed to 8 byte. + assign cnt_d = (cnt_clr) ? '0 : + (cnt_en) ? cnt_q + 1'b1 : cnt_q; + + logic [OtpByteAddrWidth-1:0] addr_base; + assign addr_base = (base_sel == DigOffset) ? DigestOffset : Info.offset; + + // Note that OTP works on halfword (16bit) addresses, hence need to + // shift the addresses appropriately. + logic [OtpByteAddrWidth-1:0] addr_calc; + assign addr_calc = OtpByteAddrWidth'({cnt_q, {$clog2(ScrmblBlockWidth/8){1'b0}}}) + addr_base; + assign otp_addr_o = addr_calc >> OtpAddrShift; + + // Always transfer 64bit blocks. + assign otp_size_o = OtpSizeWidth'(unsigned'(ScrmblBlockWidth / OtpWidth) - 1); + + assign scrmbl_data_o = data_o[cnt_q << $clog2(ScrmblBlockWidth) +: ScrmblBlockWidth]; + + assign data_mux = (data_sel == ScrmblData) ? scrmbl_data_i : otp_rdata_i; + + ///////////////// + // Buffer Regs // + ///////////////// + + // TODO: need to add secure erase feature here. + logic [Info.size*8-1:0] data; + otp_ctrl_parity_reg #( + .Width ( ScrmblBlockWidth ), + .Depth ( NumScrmblBlocks ) + ) u_otp_ctrl_parity_reg ( + .clk_i, + .rst_ni, + .wren_i ( buffer_reg_en ), + .addr_i ( cnt_q ), + .wdata_i ( data_mux ), + .data_o ( data ), + .parity_err_o ( parity_err ) + ); + + // Hardware output gating. + // Note that this is decoupled from the DAI access rules further below. + // TODO: This may need a data-specific default. + assign data_o = (dout_gate_q == Unlocked) ? data : '0; + // The digest does not have to be gated. + assign digest_o = data[$high(data_o) -: ScrmblBlockWidth]; + // We have successfully initialized the partition once it has been unlocked. + assign init_done_o = (dout_gate_q == Unlocked); + + + //////////////////////// + // DAI Access Control // + //////////////////////// + + // Aggregate all possible DAI write locks. The partition is also locked when uninitialized. + // Note that the locks are redundantly encoded values. + if (Info.write_lock) begin : gen_digest_write_lock + assign access_o.write_lock = ((dout_gate_q != Unlocked) || + (access_i.write_lock != Unlocked) || + (digest_o != '0)) ? Locked : Unlocked; + `ASSERT(DigestWriteLocksPartition_A, digest_o |-> access_o.write_lock == Locked) + end else begin : gen_no_digest_write_lock + assign access_o.write_lock = ((dout_gate_q != Unlocked) || + (access_i.write_lock != Unlocked)) ? Locked : Unlocked; + end + + // Aggregate all possible DAI read locks. The partition is also locked when uninitialized. + // Note that the locks are redundantly encoded 16bit values. + if (Info.read_lock) begin : gen_digest_read_lock + assign access_o.read_lock = ((dout_gate_q != Unlocked) || + (access_i.read_lock != Unlocked) || + (digest_o != '0)) ? Locked : Unlocked; + `ASSERT(DigestReadLocksPartition_A, digest_o |-> access_o.read_lock == Locked) + end else begin : gen_no_digest_read_lock + assign access_o.read_lock = ((dout_gate_q != Unlocked) || + (access_i.read_lock != Unlocked)) ? Locked : Unlocked; + end + + /////////////// + // Registers // + /////////////// + + always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs + if (!rst_ni) begin + state_q <= ResetSt; + error_q <= NoErr; + cnt_q <= '0; + dout_gate_q <= Locked; + end else begin + state_q <= state_d; + error_q <= error_d; + cnt_q <= cnt_d; + dout_gate_q <= dout_gate_d; + end + end + + //////////////// + // Assertions // + //////////////// + + // Known assertions + `ASSERT_KNOWN(InitDoneKnown_A, init_done_o) + `ASSERT_KNOWN(ErrorKnown_A, error_o) + `ASSERT_KNOWN(AccessKnown_A, access_o) + `ASSERT_KNOWN(DataKnown_A, data_o) + `ASSERT_KNOWN(DigestKnown_A, digest_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) + + // Uninitialized partitions should always be locked, no matter what. + `ASSERT(InitWriteLocksPartition_A, + dout_gate_q != Unlocked + |-> + access_o.write_lock == Locked) + `ASSERT(InitReadLocksPartition_A, + dout_gate_q != Unlocked + |-> + access_o.read_lock == Locked) + // Incoming Lock propagation + `ASSERT(WriteLockPropagation_A, + access_i.write_lock != Unlocked + |-> + access_o.write_lock == Locked) + `ASSERT(ReadLockPropagation_A, + access_i.read_lock != Unlocked + |-> + access_o.read_lock == Locked) + // Parity error + `ASSERT(ParityErrorState_A, + parity_err + |=> + state_q == ErrorSt) + // OTP error response + `ASSERT(OtpErrorState_A, + state_q inside {InitWaitSt, CnstyReadWaitSt} && otp_rvalid_i && + !(otp_err_i inside {NoErr, OtpReadCorrErr}) && !parity_err + |=> + state_q == ErrorSt && error_o == $past(otp_err_i)) + +endmodule : otp_ctrl_part_buf
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_part_unbuf.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_part_unbuf.sv new file mode 100644 index 0000000..dde86e4 --- /dev/null +++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_part_unbuf.sv
@@ -0,0 +1,418 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// Unbuffered partition for OTP controller. +// + +`include "prim_assert.sv" + +module otp_ctrl_part_unbuf + import otp_ctrl_pkg::*; + import otp_ctrl_reg_pkg::*; +#( + // Partition information. + parameter part_info_t Info +) ( + input clk_i, + input rst_ni, + // Pulse to start partition initialisation (required once per power cycle). + input init_req_i, + output logic init_done_o, + // Escalation input. This moves the FSM into a terminal state and locks down + // the partition. + input lc_tx_t escalate_en_i, + // 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, + // Access/lock status + input part_access_t access_i, // runtime lock from CSRs + output part_access_t access_o, + // Buffered 64bit digest output. + output logic [ScrmblBlockWidth-1:0] digest_o, + // Bus Interface (device) for window + input tlul_pkg::tl_h2d_t tl_i, + output tlul_pkg::tl_d2h_t tl_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 PartAddrWidth = prim_util_pkg::vbits(Info.size/4); + localparam int DigestOffset = (Info.offset + (Info.size - (ScrmblBlockWidth/8))); + + // Integration checks for parameters. + `ASSERT_INIT(OffsetMustBeBlockAligned_A, Info.offset % ScrmblBlockWidth/8 == 0) + `ASSERT_INIT(SizeMustBeBlockAligned_A, Info.size % ScrmblBlockWidth/8 == 0) + + /////////////////////// + // OTP Partition FSM // + /////////////////////// + + // Encoding generated with ./sparse-fsm-encode -d 5 -m 7 -n 10 + // Hamming distance histogram: + // + // 0: -- + // 1: -- + // 2: -- + // 3: -- + // 4: -- + // 5: |||||||||||||||||||| (42.86%) + // 6: |||||||||||||||||||| (42.86%) + // 7: |||||| (14.29%) + // 8: -- + // 9: -- + // 10: -- + // + // Minimum Hamming distance: 5 + // Maximum Hamming distance: 7 + // + typedef enum logic [9:0] { + ResetSt = 10'b1000011000, + InitSt = 10'b1001110011, + InitWaitSt = 10'b0100000111, + IdleSt = 10'b1101100100, + ReadSt = 10'b1110101011, + ReadWaitSt = 10'b0011011101, + ErrorSt = 10'b0111010010 + } state_e; + + typedef enum logic { + DigestAddr = 1'b0, + DataAddr = 1'b1 + } addr_sel_e; + + state_e state_d, state_q; + addr_sel_e otp_addr_sel; + otp_err_e error_d, error_q; + + logic digest_reg_en; + logic tlul_req, tlul_gnt, tlul_rvalid; + logic [PartAddrWidth-1:0] tlul_addr_d, tlul_addr_q; + logic [1:0] tlul_rerror; + logic parity_err; + + // Output partition error state. + assign error_o = error_q; + + // This partition cannot do any write accesses, hence we tie this + // constantly off. + assign otp_wdata_o = '0; + assign otp_cmd_o = OtpRead; + + `ASSERT_KNOWN(FsmStateKnown_A, state_q) + always_comb begin : p_fsm + // Default assignments + state_d = state_q; + + // Response to init request + init_done_o = 1'b0; + + // OTP signals + otp_req_o = 1'b0; + otp_addr_sel = DigestAddr; + + // TL-UL signals + tlul_gnt = 1'b0; + tlul_rvalid = 1'b0; + tlul_rerror = '0; + + // Enable for buffered digest register + digest_reg_en = 1'b0; + + // Error Register + error_d = error_q; + + unique case (state_q) + /////////////////////////////////////////////////////////////////// + // State right after reset. Wait here until we get a an + // initialization request. + ResetSt: begin + if (init_req_i) begin + state_d = InitSt; + end + end + /////////////////////////////////////////////////////////////////// + // Initialization reads out the digest only in unbuffered + // partitions. Wait here until the OTP request has been granted. + // And then wait until the OTP word comes back. + InitSt: begin + otp_req_o = 1'b1; + if (otp_gnt_i) begin + state_d = InitWaitSt; + end + end + /////////////////////////////////////////////////////////////////// + // Wait for OTP response and write to digest buffer register. In + // case an OTP transaction fails, latch the OTP error code and + // jump to a terminal error state. + InitWaitSt: begin + if (otp_rvalid_i) begin + digest_reg_en = 1'b1; + // The only error we tolerate is an ECC soft error. However, + // we still signal that error via the error state output. + if (!(otp_err_i inside {NoErr, OtpReadCorrErr})) begin + state_d = ErrorSt; + error_d = otp_err_i; + end else begin + state_d = IdleSt; + // Signal ECC soft errors, but do not go into terminal error state. + if (otp_err_i == OtpReadCorrErr) begin + error_d = otp_err_i; + end + end + end + end + /////////////////////////////////////////////////////////////////// + // Wait for TL-UL requests coming in. + // Then latch address and go to readout state. + IdleSt: begin + init_done_o = 1'b1; + if (tlul_req) begin + error_d = NoErr; // clear recoverable soft errors. + state_d = ReadSt; + tlul_gnt = 1'b1; + end + end + /////////////////////////////////////////////////////////////////// + // If the address is out of bounds, or if the partition is + // locked, signal back a bus error. Note that such an error does + // not cause the partition to go into error state. Otherwise if + // these checks pass, an OTP word is requested. + ReadSt: begin + init_done_o = 1'b1; + if (OtpByteAddrWidth'({tlul_addr_q, 2'b00} < Info.size) && + access_o.read_lock == Unlocked) begin + otp_req_o = 1'b1; + otp_addr_sel = DataAddr; + if (otp_gnt_i) begin + state_d = ReadWaitSt; + end + end else begin + state_d = IdleSt; + error_d = AccessErr; // Signal this error, but do not go into terminal error state. + tlul_rvalid = 1'b1; + tlul_rerror = 2'b11; // This causes the TL-UL adapter to return a bus error. + end + end + /////////////////////////////////////////////////////////////////// + // Wait for OTP response and and release the TL-UL response. In + // case an OTP transaction fails, latch the OTP error code, + // signal a TL-Ul bus error and jump to a terminal error state. + ReadWaitSt: begin + init_done_o = 1'b1; + if (otp_rvalid_i) begin + // Check OTP return code. + if ((!(otp_err_i inside {NoErr, OtpReadCorrErr}))) begin + state_d = ErrorSt; + error_d = otp_err_i; + tlul_rvalid = 1'b1; + // This causes the TL-UL adapter to return a bus error. + tlul_rerror = 2'b11; + end else begin + state_d = IdleSt; + // Signal soft ECC errors, but do not go into terminal error state. + if (otp_err_i == OtpReadCorrErr) begin + error_d = otp_err_i; + 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. + 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 termninal error state in case of + // a parity error or escalation, and lock access to the partition down. + if (parity_err) begin + state_d = ErrorSt; + error_d = ParityErr; + end + if (escalate_en_i != Off) begin + state_d = ErrorSt; + error_d = EscErr; + end + end + end + + /////////////////// + // TL-UL Adapter // + /////////////////// + + tlul_adapter_sram #( + .SramAw ( PartAddrWidth ), + .SramDw ( 32 ), + .Outstanding ( 1 ), + .ByteAccess ( 0 ), + .ErrOnWrite ( 1 ) // No write accesses allowed here. + ) u_tlul_adapter_sram ( + .clk_i, + .rst_ni, + .tl_i, + .tl_o, + .req_o ( tlul_req ), + .gnt_i ( tlul_gnt ), + .we_o ( ), // unused + .addr_o ( tlul_addr_d ), + .wdata_o ( ), // unused + .wmask_o ( ), // unused + .rdata_i ( otp_rdata_i[31:0] ), + .rvalid_i ( tlul_rvalid ), + .rerror_i ( tlul_rerror ) + ); + + // Note that OTP works on halfword (16bit) addresses, hence need to + // shift the addresses appropriately. + assign otp_addr_o = (otp_addr_sel == DigestAddr) ? (DigestOffset >> OtpAddrShift) : + {tlul_addr_q, 2'b00} >> OtpAddrShift; + // Request 32bit except in case of the digest. + assign otp_size_o = (otp_addr_sel == DigestAddr) ? + OtpSizeWidth'(unsigned'(32 / OtpWidth - 1)) : + OtpSizeWidth'(unsigned'(ScrmblBlockWidth / OtpWidth - 1)); + + //////////////// + // Digest Reg // + //////////////// + + otp_ctrl_parity_reg #( + .Width ( ScrmblBlockWidth ), + .Depth ( 1 ) + ) u_otp_ctrl_parity_reg ( + .clk_i, + .rst_ni, + .wren_i ( digest_reg_en ), + .addr_i ( '0 ), + .wdata_i ( otp_rdata_i ), + .data_o ( digest_o ), + .parity_err_o ( parity_err ) + ); + + //////////////////////// + // DAI Access Control // + //////////////////////// + + // Aggregate all possible DAI write locks. The partition is also locked when uninitialized. + // Note that the locks are redundantly encoded values. + if (Info.write_lock) begin : gen_digest_write_lock + assign access_o.write_lock = + (~init_done_o || access_i.write_lock != Unlocked || digest_o != '0) ? Locked : Unlocked; + + `ASSERT(DigestWriteLocksPartition_A, digest_o |-> access_o.write_lock == Locked) + + end else begin : gen_no_digest_write_lock + assign access_o.write_lock = + (~init_done_o || access_i.write_lock != Unlocked) ? Locked : Unlocked; + end + + // Aggregate all possible DAI read locks. The partition is also locked when uninitialized. + // Note that the locks are redundantly encoded 16bit values. + if (Info.read_lock) begin : gen_digest_read_lock + assign access_o.read_lock = + (~init_done_o || access_i.read_lock != Unlocked || digest_o != '0) ? Locked : Unlocked; + + `ASSERT(DigestReadLocksPartition_A, digest_o |-> access_o.read_lock == Locked) + + end else begin : gen_no_digest_read_lock + assign access_o.read_lock = + (~init_done_o || access_i.read_lock != Unlocked) ? Locked : Unlocked; + end + + /////////////// + // Registers // + /////////////// + + always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs + if (!rst_ni) begin + state_q <= ResetSt; + error_q <= NoErr; + tlul_addr_q <= '0; + end else begin + state_q <= state_d; + error_q <= error_d; + if (tlul_gnt) begin + tlul_addr_q <= tlul_addr_d; + end + end + end + + //////////////// + // Assertions // + //////////////// + + // Known assertions + `ASSERT_KNOWN(InitDoneKnown_A, init_done_o) + `ASSERT_KNOWN(ErrorKnown_A, error_o) + `ASSERT_KNOWN(AccessKnown_A, access_o) + `ASSERT_KNOWN(DigestKnown_A, digest_o) + `ASSERT_KNOWN(TlKnown_A, tl_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) + + // Uninitialized partitions should always be locked, no matter what. + `ASSERT(InitWriteLocksPartition_A, + ~init_done_o + |-> + access_o.write_lock == Locked) + `ASSERT(InitReadLocksPartition_A, + ~init_done_o + |-> + access_o.read_lock == Locked) + // Incoming Lock propagation + `ASSERT(WriteLockPropagation_A, + access_i.write_lock != Unlocked + |-> + access_o.write_lock == Locked) + `ASSERT(ReadLockPropagation_A, + access_i.read_lock != Unlocked + |-> + access_o.read_lock == Locked) + // If the partition is read locked, the TL-UL access must error out + `ASSERT(TlulReadOnReadLock_A, + tlul_req && tlul_gnt ##1 access_o.read_lock != Unlocked + |=> + tlul_rerror > '0 && tlul_rvalid) + // Parity error + `ASSERT(ParityErrorState_A, + parity_err + |=> + state_q == ErrorSt) + // OTP error response + `ASSERT(OtpErrorState_A, + state_q inside {InitWaitSt, ReadWaitSt} && otp_rvalid_i && + !(otp_err_i inside {NoErr, OtpReadCorrErr}) && !parity_err + |=> + state_q == ErrorSt && error_o == $past(otp_err_i)) + +endmodule : otp_ctrl_part_unbuf
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv index 383f4c3..8e5c685 100644 --- a/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv +++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv
@@ -6,6 +6,36 @@ package otp_ctrl_pkg; import prim_util_pkg::vbits; + import otp_ctrl_reg_pkg::*; + + //////////////////////// + // General Parameters // + //////////////////////// + + parameter int NumPart = 7; + parameter int NumPartWidth = vbits(NumPart); + + // TODO: may need to tune this and make sure that this encoding not optimized away. + // Redundantly encoded and complementary values are used to for signalling to the partition + // controller FSMs and the DAI whether a partition is locked or not. Any other value than + // "Unlocked" is interpreted as "Locked" in those FSMs. + typedef enum logic [7:0] { + Unlocked = 8'h5A, + Locked = 8'hA5 + } access_e; + + // Partition access type + typedef struct packed { + access_e read_lock; + access_e write_lock; + } part_access_t; + + parameter int DaiCmdWidth = 3; + typedef enum logic [DaiCmdWidth-1:0] { + DaiRead = 3'b001, + DaiWrite = 3'b010, + DaiDigest = 3'b100 + } dai_cmd_e; ////////////////////////////////////// // Typedefs for OTP Macro Interface // @@ -13,12 +43,11 @@ // OTP-macro specific parameter int OtpWidth = 16; - parameter int OtpDepth = 1024; + parameter int OtpAddrWidth = OtpByteAddrWidth - $clog2(OtpWidth/8); + parameter int OtpDepth = 2**OtpAddrWidth; parameter int OtpCmdWidth = 2; parameter int OtpSizeWidth = 2; // Allows to transfer up to 4 native OTP words at once. parameter int OtpErrWidth = 4; - parameter int OtpAddrWidth = vbits(OtpDepth); - parameter int OtpByteAddrWidth = vbits(OtpWidth/8 * OtpDepth); parameter int OtpIfWidth = 2**OtpSizeWidth*OtpWidth; // Number of Byte address bits to cut off in order to get the native OTP word address. parameter int OtpAddrShift = OtpByteAddrWidth - OtpAddrWidth; @@ -30,22 +59,21 @@ } prim_otp_cmd_e; typedef enum logic [OtpErrWidth-1:0] { - None = 4'h0, - OtpCmdInvErr = 4'h1, - OtpSizeInvErr = 4'h2, - OtpInitErr = 4'h3, - OtpReadErrEccCorr = 4'h4, - OtpReadErrEccUncorr = 4'h5, - OtpReadErrOther = 4'h6, - OtpWriteErrNotBlank = 4'h7, - OtpWriteErrOther = 4'h8, - ParityErr = 4'h9, - IntegErr = 4'hA, - CnstyErr = 4'hB, - FsmErr = 4'hC, - CmdInvErr = 4'hD, - AccessErr = 4'hE - // TODO: populate with error codes + NoErr = 4'h0, + OtpCmdInvErr = 4'h1, + OtpInitErr = 4'h2, + OtpReadCorrErr = 4'h3, + OtpReadUncorrErr = 4'h4, + OtpReadErr = 4'h5, + OtpWriteBlankErr = 4'h6, + OtpWriteErr = 4'h7, + CmdInvErr = 4'h8, + AccessErr = 4'h9, + ParityErr = 4'hA, + IntegErr = 4'hB, + CnstyErr = 4'hC, + FsmErr = 4'hD, + EscErr = 4'hE } otp_err_e; ///////////////////////////////// @@ -104,11 +132,65 @@ ChainedMode } otp_digest_mode_e; + //////////////////////// + // Partition Metadata // + //////////////////////// + + typedef enum logic { + Unbuffered, + Buffered + } part_variant_e; + + typedef struct packed { + part_variant_e variant; + // Offset and size within the OTP array, in Bytes. + logic [OtpByteAddrWidth-1:0] offset; + logic [OtpByteAddrWidth-1:0] size; + // Key index to use for scrambling. + logic [ConstSelWidth-1:0] key_idx; + // Attributes + logic scrambled; // Whether the partition is scrambled + logic hw_digest; // Whether the partition has a hardware digest + logic write_lock; // Whether the partition is write lockable (via digest) + logic read_lock; // Whether the partition is read lockable (via digest) + } part_info_t; + + // TODO: need to parse this somehow from an hjson + localparam part_info_t PartInfo [NumPart] = '{ + // Variant | offset | size | key_idx | scrambled | HW digest | write_lock | read_lock + // CREATOR_SW_CFG + '{Unbuffered, 32'h0, 768, 0, 1'b0, 1'b0, 1'b1, 1'b0}, + // OWNER_SW_CFG + '{Unbuffered, 32'h300, 768, 0, 1'b0, 1'b0, 1'b1, 1'b0}, + // HW_CFG + '{Buffered, 32'h600, 176, 0, 1'b0, 1'b1, 1'b1, 1'b0}, + // SECRET0 + '{Buffered, 32'h6B0, 40, 0, 1'b1, 1'b1, 1'b1, 1'b1}, + // SECRET1 + '{Buffered, 32'h6D8, 88, 1, 1'b1, 1'b1, 1'b1, 1'b1}, + // SECRET2 + '{Buffered, 32'h730, 120, 2, 1'b1, 1'b1, 1'b1, 1'b1}, + // LIFE_CYCLE + '{Buffered, 32'h7A8, 88, 0, 1'b0, 1'b0, 1'b0, 1'b0} + }; + + parameter int CreatorSwCfgIdx = 0; + parameter int OwnerSwCfgIdx = 1; + parameter int HwCfgIdx = 2; + parameter int Secret0Idx = 3; + parameter int Secret1Idx = 4; + parameter int Secret2Idx = 5; + parameter int LifeCycleIdx = 6; + // These are not "real partitions", but in terms of implementation it is convenient to + // add these at the end of certain arrays. + parameter int DaiIdx = 7; + parameter int LciIdx = 8; + /////////////////////////////// // Typedefs for LC Interface // /////////////////////////////// - // TODO: move to lc_ctrl_pkg + // TODO: update this encoding and move to lc_ctrl_pkg typedef enum logic [7:0] { Value0 = 8'h 00, Value1 = 8'h 6D, @@ -207,21 +289,15 @@ //////////////////////////////// typedef struct packed { - logic init; + logic init; } pwr_otp_init_req_t; typedef struct packed { - logic done; + logic done; } pwr_otp_init_rsp_t; - typedef enum logic [1:0] { - Idle = 2'b00, - Reading = 2'b01, - Writing = 2'b10 - } otp_pwr_state_e; - typedef struct packed { - otp_pwr_state_e state; + logic idle; } otp_pwr_state_t; endpackage : otp_ctrl_pkg
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_scrmbl.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_scrmbl.sv index 19e77d3..39f270d 100644 --- a/hw/ip/otp_ctrl/rtl/otp_ctrl_scrmbl.sv +++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_scrmbl.sv
@@ -84,9 +84,15 @@ // 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 @@ -94,11 +100,12 @@ 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], 5'(j + 1)); + 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); + `ASSERT_KNOWN(DecKeyLutKnown_A, otp_dec_key_lut) ////////////// // Datapath // @@ -129,7 +136,7 @@ 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 sel_d, sel_q; + logic [ConstSelWidth-1:0] sel_d, sel_q; otp_digest_mode_e digest_mode_d, digest_mode_q; assign otp_enc_key_mux = (sel_d < NumScrmblKeys) ? OtpKey[sel_d] : '0; @@ -151,7 +158,7 @@ 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_lut : + (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} : @@ -165,11 +172,27 @@ // FSM // ///////// - typedef enum logic [1:0] { - IdleSt, - DecryptSt, - EncryptSt, - DigestSt + // Encoding generated with ./sparse-fsm-encode -d 5 -m 4 -n 8 + // Hamming distance histogram: + // + // 0: -- + // 1: -- + // 2: -- + // 3: -- + // 4: -- + // 5: |||||||||||||||||||| (66.67%) + // 6: |||||||||| (33.33%) + // 7: -- + // 8: -- + // + // Minimum Hamming distance: 5 + // Maximum Hamming distance: 6 + // + typedef enum logic [7:0] { + IdleSt = 8'b11100001, + DecryptSt = 8'b01011010, + EncryptSt = 8'b10010100, + DigestSt = 8'b00101111 } state_e; localparam int CntWidth = $clog2(NumPresentRounds+1); @@ -218,12 +241,14 @@ 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 @@ -239,6 +264,7 @@ 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 = otp_digest_mode_e'(sel_i); @@ -252,6 +278,7 @@ key_state_en = 1'b1; is_first_d = 1'b1; digest_mode_d = StandardMode; + sel_d = sel_i; end default: ; // ignore endcase // cmd_i
diff --git a/hw/ip/prim_generic/rtl/prim_generic_otp.sv b/hw/ip/prim_generic/rtl/prim_generic_otp.sv index e0141d9..c6ed031 100644 --- a/hw/ip/prim_generic/rtl/prim_generic_otp.sv +++ b/hw/ip/prim_generic/rtl/prim_generic_otp.sv
@@ -46,15 +46,33 @@ // Control logic // /////////////////// - typedef enum logic [2:0] { - ResetSt, - InitSt, - IdleSt, - ReadSt, - ReadWaitSt, - WriteCheckSt, - WriteWaitSt, - WriteSt + // Encoding generated with ./sparse-fsm-encode -d 5 -m 8 -n 10 + // Hamming distance histogram: + // + // 0: -- + // 1: -- + // 2: -- + // 3: -- + // 4: -- + // 5: |||||||||||||||||||| (53.57%) + // 6: ||||||||||||| (35.71%) + // 7: | (3.57%) + // 8: || (7.14%) + // 9: -- + // 10: -- + // + // Minimum Hamming distance: 5 + // Maximum Hamming distance: 8 + // + typedef enum logic [9:0] { + ResetSt = 10'b1100000011, + InitSt = 10'b1100110100, + IdleSt = 10'b1010111010, + ReadSt = 10'b0011100000, + ReadWaitSt = 10'b1001011111, + WriteCheckSt = 10'b0111010101, + WriteWaitSt = 10'b0000001100, + WriteSt = 10'b0110101111 } state_e; state_e state_d, state_q; @@ -80,8 +98,8 @@ // Default state_d = state_q; ready_o = 1'b0; - valid_d = 1'b1; - err_d = otp_ctrl_pkg::None; + valid_d = 1'b0; + err_d = otp_ctrl_pkg::NoErr; req = 1'b0; wren = 1'b0; cnt_clr = 1'b0; @@ -92,14 +110,13 @@ ResetSt: begin ready_o = 1'b1; if (valid_i) begin - unique case (cmd_i) - otp_ctrl_pkg::OtpInit: state_d = InitSt; - default: begin - // Invalid commands get caught here - valid_d = 1'b1; - err_d = otp_ctrl_pkg::OtpCmdInvErr; - end - endcase // cmd_i + if (cmd_i == otp_ctrl_pkg::OtpInit) begin + state_d = InitSt; + end else begin + // Invalid commands get caught here + valid_d = 1'b1; + err_d = otp_ctrl_pkg::OtpCmdInvErr; + end end end // Wait for some time until the OTP macro is ready. @@ -113,7 +130,7 @@ ready_o = 1'b1; if (valid_i) begin cnt_clr = 1'b1; - err_d = otp_ctrl_pkg::None; + err_d = otp_ctrl_pkg::NoErr; unique case (cmd_i) otp_ctrl_pkg::OtpRead: state_d = ReadSt; otp_ctrl_pkg::OtpWrite: state_d = WriteCheckSt; @@ -139,7 +156,7 @@ if (rerror[1]) begin state_d = IdleSt; valid_d = 1'b1; - err_d = otp_ctrl_pkg::OtpReadErrEccUncorr; + err_d = otp_ctrl_pkg::OtpReadUncorrErr; end else begin if (cnt_q == size_q) begin state_d = IdleSt; @@ -149,7 +166,7 @@ end // Correctable error, carry on but signal back. if (rerror[0]) begin - err_d = otp_ctrl_pkg::OtpReadErrEccCorr; + err_d = otp_ctrl_pkg::OtpReadCorrErr; end end end @@ -169,7 +186,7 @@ if (rerror[1] || rdata_d != '0) begin state_d = IdleSt; valid_d = 1'b1; - err_d = otp_ctrl_pkg::OtpWriteErrNotBlank; + err_d = otp_ctrl_pkg::OtpWriteBlankErr; end else begin if (cnt_q == size_q) begin cnt_clr = 1'b1;