blob: 49c428347190067c25cc9aecaf425337bdbbf991 [file] [log] [blame]
// 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