|  | // Copyright lowRISC contributors. | 
|  | // Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
|  | // SPDX-License-Identifier: Apache-2.0 | 
|  |  | 
|  | /** | 
|  | * State machine to handle actions that occur around the start and stop of OTBN. | 
|  | * | 
|  | * This recieves the start signals from the top-level and passes them on to the | 
|  | * controller to begin execution when pre-start actions have finished. | 
|  | * | 
|  | * pre-start actions: | 
|  | *  - Seed LFSR for URND | 
|  | * | 
|  | * post-stop actions: | 
|  | *  - Internal Secure Wipe | 
|  | *    -Delete WDRs | 
|  | *    -Delete Base registers | 
|  | *    -Delete Accumulator | 
|  | *    -Delete Modulus | 
|  | *    -Reset stack | 
|  | */ | 
|  |  | 
|  | `include "prim_assert.sv" | 
|  |  | 
|  | module otbn_start_stop_control | 
|  | import otbn_pkg::*; | 
|  | import prim_mubi_pkg::*; | 
|  | #( | 
|  | // Disable URND advance when not in use. Useful for SCA only. | 
|  | parameter bit SecMuteUrnd = 1'b0, | 
|  | // Skip URND re-seed at the start of the operation. Useful for SCA only. | 
|  | parameter bit SecSkipUrndReseedAtStart = 1'b0 | 
|  | ) ( | 
|  | input  logic clk_i, | 
|  | input  logic rst_ni, | 
|  |  | 
|  | input  logic   start_i, | 
|  | input  mubi4_t escalate_en_i, | 
|  | input  mubi4_t rma_req_i, | 
|  | output mubi4_t rma_ack_o, | 
|  |  | 
|  | output logic controller_start_o, | 
|  |  | 
|  | output logic urnd_reseed_req_o, | 
|  | input  logic urnd_reseed_ack_i, | 
|  | output logic urnd_reseed_err_o, | 
|  | output logic urnd_advance_o, | 
|  |  | 
|  | input   logic secure_wipe_req_i, | 
|  | output  logic secure_wipe_ack_o, | 
|  | output  logic secure_wipe_running_o, | 
|  | output  logic done_o, | 
|  |  | 
|  | output logic       sec_wipe_wdr_o, | 
|  | output logic       sec_wipe_wdr_urnd_o, | 
|  | output logic       sec_wipe_base_o, | 
|  | output logic       sec_wipe_base_urnd_o, | 
|  | output logic [4:0] sec_wipe_addr_o, | 
|  |  | 
|  | output logic sec_wipe_acc_urnd_o, | 
|  | output logic sec_wipe_mod_urnd_o, | 
|  | output logic sec_wipe_zero_o, | 
|  |  | 
|  | output logic ispr_init_o, | 
|  | output logic state_reset_o, | 
|  | output logic fatal_error_o | 
|  | ); | 
|  |  | 
|  | import otbn_pkg::*; | 
|  |  | 
|  | // Create lint errors to reduce the risk of accidentally enabling these features. | 
|  | `ASSERT_STATIC_LINT_ERROR(OtbnSecMuteUrndNonDefault, SecMuteUrnd == 0) | 
|  | `ASSERT_STATIC_LINT_ERROR(OtbnSecSkipUrndReseedAtStartNonDefault, SecSkipUrndReseedAtStart == 0) | 
|  |  | 
|  | otbn_start_stop_state_e state_q, state_d; | 
|  | logic init_sec_wipe_done_q, init_sec_wipe_done_d; | 
|  | mubi4_t wipe_after_urnd_refresh_q, wipe_after_urnd_refresh_d; | 
|  | mubi4_t rma_ack_d, rma_ack_q; | 
|  | logic state_error_q, state_error_d; | 
|  | logic mubi_err_q, mubi_err_d; | 
|  | logic urnd_reseed_err_q, urnd_reseed_err_d; | 
|  | logic secure_wipe_error_q, secure_wipe_error_d; | 
|  | logic skip_reseed_q; | 
|  |  | 
|  | logic addr_cnt_inc; | 
|  | logic [5:0] addr_cnt_q, addr_cnt_d; | 
|  |  | 
|  | logic spurious_urnd_ack_error; | 
|  | logic spurious_secure_wipe_req, dropped_secure_wipe_req; | 
|  |  | 
|  | // There are three ways in which the start/stop controller can be told to stop. | 
|  | // 1. secure_wipe_req_i comes from the controller (which means "I've run some instructions and | 
|  | //    I've hit an ECALL or error"). | 
|  | // 2. escalate_en_i can be asserted (which means "Someone else has told us to stop immediately"). | 
|  | // 3. rma_req_i can be asserted (which means "Lifecycle wants to transition to the RMA state"). | 
|  | // If running, all three can be true at once. | 
|  | // | 
|  | // An escalation signal as well as RMA requests get latched into should_lock. We'll then go | 
|  | // through the secure wipe process (unless we weren't running any instructions in case of an | 
|  | // escalation). We'll see the should_lock_q signal when done and go into the local locked | 
|  | // state. If necessary, the RMA request is acknowledged upon secure wipe completion. | 
|  |  | 
|  | // SEC_CM: CONTROLLER.FSM.GLOBAL_ESC | 
|  | logic esc_request, rma_request, should_lock_d, should_lock_q, stop; | 
|  | assign esc_request   = mubi4_test_true_loose(escalate_en_i); | 
|  | assign rma_request   = mubi4_test_true_strict(rma_req_i); | 
|  | assign stop          = esc_request | rma_request | secure_wipe_req_i; | 
|  | assign should_lock_d = should_lock_q | esc_request | rma_request; | 
|  |  | 
|  | // Only if SecSkipUrndReseedAtStart is set, the controller start pulse is sent | 
|  | // one cycle after leaving the Halt state. | 
|  | if (SecSkipUrndReseedAtStart) begin: gen_skip_reseed | 
|  | logic skip_reseed_d; | 
|  |  | 
|  | assign skip_reseed_d = ((state_q == OtbnStartStopStateHalt) & start_i & ~stop); | 
|  |  | 
|  | always_ff @(posedge clk_i or negedge rst_ni) begin | 
|  | if (!rst_ni) begin | 
|  | skip_reseed_q <= 1'b0; | 
|  | end else begin | 
|  | skip_reseed_q <= skip_reseed_d; | 
|  | end | 
|  | end | 
|  | end else begin: gen_reseed | 
|  | assign skip_reseed_q = 1'b0; | 
|  | end | 
|  |  | 
|  | always_ff @(posedge clk_i or negedge rst_ni) begin | 
|  | if (!rst_ni) begin | 
|  | should_lock_q <= 1'b0; | 
|  | end else begin | 
|  | should_lock_q <= should_lock_d; | 
|  | end | 
|  | end | 
|  |  | 
|  | prim_mubi4_sender #( | 
|  | .AsyncOn(1'b1), | 
|  | .EnSecBuf(1'b1), | 
|  | .ResetValue(prim_mubi_pkg::MuBi4False) | 
|  | ) u_prim_mubi4_sender_rma_ack ( | 
|  | .clk_i, | 
|  | .rst_ni, | 
|  | .mubi_i(rma_ack_d), | 
|  | .mubi_o(rma_ack_q) | 
|  | ); | 
|  |  | 
|  | logic allow_secure_wipe, expect_secure_wipe; | 
|  |  | 
|  | // SEC_CM: START_STOP_CTRL.FSM.SPARSE | 
|  | `PRIM_FLOP_SPARSE_FSM(u_state_regs, state_d, state_q, | 
|  | otbn_start_stop_state_e, OtbnStartStopStateInitial) | 
|  |  | 
|  | always_comb begin | 
|  | urnd_reseed_req_o         = 1'b0; | 
|  | urnd_advance_o            = 1'b0; | 
|  | state_d                   = state_q; | 
|  | ispr_init_o               = 1'b0; | 
|  | state_reset_o             = 1'b0; | 
|  | sec_wipe_wdr_o            = 1'b0; | 
|  | sec_wipe_wdr_urnd_o       = 1'b0; | 
|  | sec_wipe_base_o           = 1'b0; | 
|  | sec_wipe_base_urnd_o      = 1'b0; | 
|  | sec_wipe_acc_urnd_o       = 1'b0; | 
|  | sec_wipe_mod_urnd_o       = 1'b0; | 
|  | sec_wipe_zero_o           = 1'b0; | 
|  | addr_cnt_inc              = 1'b0; | 
|  | secure_wipe_ack_o         = 1'b0; | 
|  | secure_wipe_running_o     = 1'b0; | 
|  | state_error_d             = state_error_q; | 
|  | allow_secure_wipe         = 1'b0; | 
|  | expect_secure_wipe        = 1'b0; | 
|  | spurious_urnd_ack_error   = 1'b0; | 
|  | wipe_after_urnd_refresh_d = wipe_after_urnd_refresh_q; | 
|  | rma_ack_d                 = rma_ack_q; | 
|  | mubi_err_d                = mubi_err_q; | 
|  |  | 
|  | unique case (state_q) | 
|  | OtbnStartStopStateInitial: begin | 
|  | secure_wipe_running_o = 1'b1; | 
|  | urnd_reseed_req_o     = 1'b1; | 
|  | if (urnd_reseed_ack_i) begin | 
|  | urnd_advance_o = 1'b1; | 
|  | state_d        = OtbnStartStopSecureWipeWdrUrnd; | 
|  | end | 
|  | end | 
|  | OtbnStartStopStateHalt: begin | 
|  | if (stop && !rma_request) begin | 
|  | state_d = OtbnStartStopStateLocked; | 
|  | end else if (start_i || rma_request) begin | 
|  | urnd_reseed_req_o = ~SecSkipUrndReseedAtStart | rma_request; | 
|  | ispr_init_o       = 1'b1; | 
|  | state_reset_o     = 1'b1; | 
|  | state_d           = OtbnStartStopStateUrndRefresh; | 
|  | end | 
|  | end | 
|  | OtbnStartStopStateUrndRefresh: begin | 
|  | urnd_reseed_req_o = ~skip_reseed_q; | 
|  | if (stop) begin | 
|  | if (mubi4_test_false_strict(wipe_after_urnd_refresh_q) && !rma_request) begin | 
|  | // We are told to stop and don't have to wipe after the current URND refresh is ack'd, | 
|  | // so we lock immediately. | 
|  | state_d = OtbnStartStopStateLocked; | 
|  | end else begin | 
|  | // We are told to stop but should wipe after the current URND refresh is ack'd, so we | 
|  | // wait for the ACK and then do a secure wipe. | 
|  | allow_secure_wipe     = 1'b1; | 
|  | expect_secure_wipe    = 1'b1; | 
|  | secure_wipe_running_o = 1'b1; | 
|  | if (urnd_reseed_ack_i) begin | 
|  | state_d = OtbnStartStopSecureWipeWdrUrnd; | 
|  | end | 
|  | end | 
|  | end else begin | 
|  | if (mubi4_test_false_strict(wipe_after_urnd_refresh_q)) begin | 
|  | // We are not stopping and we don't have to wipe after the current URND refresh is | 
|  | // ack'd, so we wait for the ACK and then start executing. | 
|  | if (urnd_reseed_ack_i || skip_reseed_q) begin | 
|  | state_d = OtbnStartStopStateRunning; | 
|  | end | 
|  | end else begin | 
|  | // We are not stopping but should wipe after the current URND refresh is ack'd, so we | 
|  | // wait for the ACK and then do a secure wipe. | 
|  | allow_secure_wipe     = 1'b1; | 
|  | expect_secure_wipe    = 1'b1; | 
|  | secure_wipe_running_o = 1'b1; | 
|  | if (urnd_reseed_ack_i) begin | 
|  | state_d = OtbnStartStopSecureWipeWdrUrnd; | 
|  | end | 
|  | end | 
|  | end | 
|  | end | 
|  | OtbnStartStopStateRunning: begin | 
|  | urnd_advance_o    = ~SecMuteUrnd; | 
|  | allow_secure_wipe = 1'b1; | 
|  |  | 
|  | if (stop) begin | 
|  | state_d = OtbnStartStopSecureWipeWdrUrnd; | 
|  | end | 
|  | end | 
|  | // SEC_CM: DATA_REG_SW.SEC_WIPE | 
|  | // Writing random numbers to the wide data registers. | 
|  | OtbnStartStopSecureWipeWdrUrnd: begin | 
|  | urnd_advance_o        = 1'b1; | 
|  | addr_cnt_inc          = 1'b1; | 
|  | sec_wipe_wdr_o        = 1'b1; | 
|  | sec_wipe_wdr_urnd_o   = 1'b1; | 
|  | allow_secure_wipe     = 1'b1; | 
|  | expect_secure_wipe    = 1'b1; | 
|  | secure_wipe_running_o = 1'b1; | 
|  |  | 
|  | // Count one extra cycle when wiping the WDR, because the wipe signals to the WDR | 
|  | // (`sec_wipe_wdr_o` and `sec_wipe_wdr_urnd_o`) are flopped once but the wipe signals to the | 
|  | // ACC register, which is wiped directly after the last WDR, are not.  If we would not count | 
|  | // this extra cycle, the last WDR and the ACC register would be wiped simultaneously and | 
|  | // thus with the same random value. | 
|  | if (addr_cnt_q == 6'b100000) begin | 
|  | // Reset `addr_cnt` on the transition out of this state. | 
|  | addr_cnt_inc = 1'b0; | 
|  | // The following two signals are flopped once before they reach the FSM, so clear them one | 
|  | // cycle early here. | 
|  | sec_wipe_wdr_o      = 1'b0; | 
|  | sec_wipe_wdr_urnd_o = 1'b0; | 
|  | state_d = OtbnStartStopSecureWipeAccModBaseUrnd; | 
|  | end | 
|  | end | 
|  | // Writing random numbers to the accumulator, modulus and the base registers. | 
|  | // addr_cnt_q wraps around to 0 when first moving to this state, and we need to | 
|  | // supress writes to the zero register and the call stack. | 
|  | OtbnStartStopSecureWipeAccModBaseUrnd: begin | 
|  | urnd_advance_o        = 1'b1; | 
|  | addr_cnt_inc          = 1'b1; | 
|  | allow_secure_wipe     = 1'b1; | 
|  | expect_secure_wipe    = 1'b1; | 
|  | secure_wipe_running_o = 1'b1; | 
|  | // The first two clock cycles are used to write random data to accumulator and modulus. | 
|  | sec_wipe_acc_urnd_o   = (addr_cnt_q == 6'b000000); | 
|  | sec_wipe_mod_urnd_o   = (addr_cnt_q == 6'b000001); | 
|  | // Supress writes to the zero register and the call stack. | 
|  | sec_wipe_base_o       = (addr_cnt_q > 6'b000001); | 
|  | sec_wipe_base_urnd_o  = (addr_cnt_q > 6'b000001); | 
|  | if (addr_cnt_q == 6'b011111) begin | 
|  | state_d = OtbnStartStopSecureWipeAllZero; | 
|  | end | 
|  | end | 
|  | // Writing zeros to the CSRs and reset the stack. The other registers are intentionally not | 
|  | // overwritten with zero. | 
|  | OtbnStartStopSecureWipeAllZero: begin | 
|  | sec_wipe_zero_o       = 1'b1; | 
|  | allow_secure_wipe     = 1'b1; | 
|  | expect_secure_wipe    = 1'b1; | 
|  | secure_wipe_running_o = 1'b1; | 
|  |  | 
|  | // Leave this state after a single cycle, which is sufficient to reset the CSRs and the | 
|  | // stack. | 
|  | if (mubi4_test_false_strict(wipe_after_urnd_refresh_q)) begin | 
|  | // This is the first round of wiping with random numbers, refresh URND and do a second | 
|  | // round. | 
|  | state_d = OtbnStartStopStateUrndRefresh; | 
|  | wipe_after_urnd_refresh_d = MuBi4True; | 
|  | end else begin | 
|  | // This is the second round of wiping with random numbers, so the secure wipe is | 
|  | // complete. | 
|  | state_d = OtbnStartStopSecureWipeComplete; | 
|  | secure_wipe_ack_o = 1'b1; | 
|  | end | 
|  | end | 
|  | OtbnStartStopSecureWipeComplete: begin | 
|  | urnd_advance_o = 1'b1; | 
|  | rma_ack_d = rma_req_i; | 
|  | state_d = should_lock_d ? OtbnStartStopStateLocked : OtbnStartStopStateHalt; | 
|  | wipe_after_urnd_refresh_d = MuBi4False; | 
|  | end | 
|  | OtbnStartStopStateLocked: begin | 
|  | // SEC_CM: START_STOP_CTRL.FSM.GLOBAL_ESC | 
|  | // SEC_CM: START_STOP_CTRL.FSM.LOCAL_ESC | 
|  | // | 
|  | // Terminal state. This is either accessed by glitching state_q (and going through the | 
|  | // default case below) or by getting an escalation signal | 
|  | end | 
|  | default: begin | 
|  | // We should never get here. If we do (e.g. via a malicious glitch), error out immediately. | 
|  | state_error_d = 1'b1; | 
|  | rma_ack_d = MuBi4False; | 
|  | state_d = OtbnStartStopStateLocked; | 
|  | end | 
|  | endcase | 
|  |  | 
|  | if (urnd_reseed_ack_i && | 
|  | !(state_q inside {OtbnStartStopStateInitial, OtbnStartStopStateUrndRefresh})) begin | 
|  | // We should never receive an ACK from URND when we're not refreshing the URND. Signal an | 
|  | // error if we see a stray ACK and lock the FSM. | 
|  | spurious_urnd_ack_error = 1'b1; | 
|  | state_d                 = OtbnStartStopStateLocked; | 
|  | end | 
|  |  | 
|  | // If the MuBi signals take on invalid values, something bad is happening. Put them back to | 
|  | // a safe value (if possible) and signal an error. | 
|  | if (mubi4_test_invalid(escalate_en_i)) begin | 
|  | mubi_err_d = 1'b1; | 
|  | state_d = OtbnStartStopStateLocked; | 
|  | end | 
|  | if (mubi4_test_invalid(rma_req_i)) begin | 
|  | mubi_err_d = 1'b1; | 
|  | state_d = OtbnStartStopStateLocked; | 
|  | end | 
|  | if (mubi4_test_invalid(wipe_after_urnd_refresh_q)) begin | 
|  | wipe_after_urnd_refresh_d = MuBi4False; | 
|  | mubi_err_d = 1'b1; | 
|  | state_d = OtbnStartStopStateLocked; | 
|  | end | 
|  | if (mubi4_test_invalid(rma_ack_q)) begin | 
|  | rma_ack_d = MuBi4False; | 
|  | mubi_err_d = 1'b1; | 
|  | state_d = OtbnStartStopStateLocked; | 
|  | end | 
|  | end | 
|  |  | 
|  | // Latch initial secure wipe done. | 
|  | assign init_sec_wipe_done_d = (state_q == OtbnStartStopSecureWipeComplete) ? 1'b1 : // set | 
|  | init_sec_wipe_done_q; // keep | 
|  |  | 
|  | // Logic separate from main FSM code to avoid false combinational loop warning from verilator | 
|  | assign controller_start_o = | 
|  | // The controller start pulse is fired when finishing the initial URND reseed. | 
|  | ((state_q == OtbnStartStopStateUrndRefresh) & (urnd_reseed_ack_i | skip_reseed_q) & | 
|  | mubi4_test_false_strict(wipe_after_urnd_refresh_q)); | 
|  |  | 
|  | assign done_o = ((state_q == OtbnStartStopSecureWipeComplete && init_sec_wipe_done_q) || | 
|  | (stop && (state_q == OtbnStartStopStateUrndRefresh) && | 
|  | mubi4_test_false_strict(wipe_after_urnd_refresh_q)) || | 
|  | (spurious_urnd_ack_error && !(state_q inside {OtbnStartStopStateHalt, | 
|  | OtbnStartStopStateLocked}) && | 
|  | init_sec_wipe_done_q) || (mubi_err_d && !mubi_err_q)); | 
|  |  | 
|  | assign addr_cnt_d = addr_cnt_inc ? (addr_cnt_q + 6'd1) : 6'd0; | 
|  |  | 
|  | always_ff @(posedge clk_i or negedge rst_ni) begin | 
|  | if (!rst_ni) begin | 
|  | addr_cnt_q           <= 6'd0; | 
|  | init_sec_wipe_done_q <= 1'b0; | 
|  | end else begin | 
|  | addr_cnt_q           <= addr_cnt_d; | 
|  | init_sec_wipe_done_q <= init_sec_wipe_done_d; | 
|  | end | 
|  | end | 
|  |  | 
|  | prim_mubi4_sender #( | 
|  | .AsyncOn(1), | 
|  | .ResetValue(MuBi4False) | 
|  | ) u_wipe_after_urnd_refresh_flop ( | 
|  | .clk_i, | 
|  | .rst_ni, | 
|  | .mubi_i(wipe_after_urnd_refresh_d), | 
|  | .mubi_o(wipe_after_urnd_refresh_q) | 
|  | ); | 
|  |  | 
|  | // Clip the secure wipe address to [0..31].  This is safe because the wipe enable signals are | 
|  | // never set when the counter exceeds 5 bit, which we assert below. | 
|  | assign sec_wipe_addr_o = addr_cnt_q[4:0]; | 
|  | `ASSERT(NoSecWipeAbove32Bit_A, addr_cnt_q[5] |-> (!sec_wipe_wdr_o && !sec_wipe_acc_urnd_o)) | 
|  |  | 
|  | // A check for spurious or dropped secure wipe requests. | 
|  | // We only expect to start a secure wipe when running. | 
|  | assign spurious_secure_wipe_req = secure_wipe_req_i & ~allow_secure_wipe; | 
|  | // Once we've started a secure wipe, the controller should not drop the request until we tell it | 
|  | // we're done. This does not apply for the *initial* secure wipe, though, which is controlled by | 
|  | // this module rather than the controller. | 
|  | assign dropped_secure_wipe_req  = expect_secure_wipe & init_sec_wipe_done_d & ~secure_wipe_req_i; | 
|  |  | 
|  | // Delay the "glitch req/ack" error signal by a cycle. Otherwise, you end up with a combinatorial | 
|  | // loop through the escalation signal that our fatal_error_o causes otbn_core to pass to the | 
|  | // controller. | 
|  | assign secure_wipe_error_d = spurious_secure_wipe_req | dropped_secure_wipe_req; | 
|  | always_ff @(posedge clk_i or negedge rst_ni) begin | 
|  | if (!rst_ni) begin | 
|  | state_error_q       <= 1'b0; | 
|  | mubi_err_q          <= 1'b0; | 
|  | secure_wipe_error_q <= 1'b0; | 
|  | urnd_reseed_err_q   <= 1'b0; | 
|  | end else begin | 
|  | state_error_q       <= state_error_d; | 
|  | mubi_err_q          <= mubi_err_d; | 
|  | secure_wipe_error_q <= secure_wipe_error_d; | 
|  | urnd_reseed_err_q   <= urnd_reseed_err_d; | 
|  | end | 
|  | end | 
|  |  | 
|  | assign urnd_reseed_err_d = spurious_urnd_ack_error ? 1'b1 // set | 
|  | : urnd_reseed_err_q; // hold | 
|  | assign urnd_reseed_err_o = urnd_reseed_err_d; | 
|  |  | 
|  | assign fatal_error_o = urnd_reseed_err_o | state_error_d | secure_wipe_error_q | mubi_err_q; | 
|  |  | 
|  | assign rma_ack_o = rma_ack_q; | 
|  |  | 
|  | `ASSERT(StartStopStateValid_A, | 
|  | state_q inside {OtbnStartStopStateInitial, | 
|  | OtbnStartStopStateHalt, | 
|  | OtbnStartStopStateUrndRefresh, | 
|  | OtbnStartStopStateRunning, | 
|  | OtbnStartStopSecureWipeWdrUrnd, | 
|  | OtbnStartStopSecureWipeAccModBaseUrnd, | 
|  | OtbnStartStopSecureWipeAllZero, | 
|  | OtbnStartStopSecureWipeComplete, | 
|  | OtbnStartStopStateLocked}) | 
|  |  | 
|  | `ASSERT(StartSecureWipeImpliesRunning_A, | 
|  | $rose(secure_wipe_req_i) |-> (state_q == OtbnStartStopStateRunning)) | 
|  |  | 
|  | endmodule |