blob: 5cf3f697792e82848aa7fb3900d792a31ae80776 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// The ROM checker FSM module
//
`include "prim_assert.sv"
module rom_ctrl_fsm
import prim_mubi_pkg::mubi4_t;
import prim_util_pkg::vbits;
import rom_ctrl_pkg::*;
#(
parameter int RomDepth = 16,
parameter int TopCount = 8
) (
input logic clk_i,
input logic rst_ni,
// CSR inputs for DIGEST and EXP_DIGEST. To make the indexing look nicer, these are ordered so
// that DIGEST_0 is the bottom 32 bits (they get reversed while we're shuffling around the wires
// in rom_ctrl).
input logic [TopCount*32-1:0] digest_i,
input logic [TopCount*32-1:0] exp_digest_i,
// CSR outputs for DIGEST and EXP_DIGEST. Ordered with word 0 as LSB.
output logic [TopCount*32-1:0] digest_o,
output logic digest_vld_o,
output logic [31:0] exp_digest_o,
output logic exp_digest_vld_o,
output logic [vbits(TopCount)-1:0] exp_digest_idx_o,
// To power manager and key manager
output pwrmgr_data_t pwrmgr_data_o,
output keymgr_data_t keymgr_data_o,
// To KMAC (ROM data)
input logic kmac_rom_rdy_i,
output logic kmac_rom_vld_o,
output logic kmac_rom_last_o,
// To KMAC (digest data)
input logic kmac_done_i,
input logic [TopCount*32-1:0] kmac_digest_i,
input logic kmac_err_i,
// To ROM mux
output mubi4_t rom_select_bus_o,
output logic [vbits(RomDepth)-1:0] rom_addr_o,
output logic rom_req_o,
// Raw bits from ROM
input logic [31:0] rom_data_i,
// To alert system
output logic alert_o
);
import prim_mubi_pkg::mubi4_test_true_loose;
import prim_mubi_pkg::MuBi4False, prim_mubi_pkg::MuBi4True;
localparam int AW = vbits(RomDepth);
localparam int TAW = vbits(TopCount);
localparam int unsigned TopStartAddrInt = RomDepth - TopCount;
localparam bit [AW-1:0] TopStartAddr = TopStartAddrInt[AW-1:0];
// The counter / address generator
logic counter_done;
logic [AW-1:0] counter_read_addr;
logic counter_read_req;
logic [AW-1:0] counter_data_addr;
logic counter_data_rdy;
logic counter_lnt;
rom_ctrl_counter #(
.RomDepth (RomDepth),
.RomTopCount (TopCount)
) u_counter (
.clk_i (clk_i),
.rst_ni (rst_ni),
.done_o (counter_done),
.read_addr_o (counter_read_addr),
.read_req_o (counter_read_req),
.data_addr_o (counter_data_addr),
.data_rdy_i (counter_data_rdy),
.data_last_nontop_o (counter_lnt)
);
// The compare block (responsible for comparing CSR data and forwarding it to the key manager)
logic start_checker_q;
logic checker_done, checker_alert;
mubi4_t checker_good;
rom_ctrl_compare #(
.NumWords (TopCount)
) u_compare (
.clk_i (clk_i),
.rst_ni (rst_ni),
.start_i (start_checker_q),
.done_o (checker_done),
.good_o (checker_good),
.digest_i (digest_i),
.exp_digest_i (exp_digest_i),
.alert_o (checker_alert)
);
// Main FSM
//
// There are the following logical states
//
// ReadingLow: We're reading the low part of ROM and passing it to KMAC
// ReadingHigh: We're reading the high part of ROM and waiting for KMAC
// RomAhead: We've finished reading the high part of ROM, but are still waiting for KMAC
// KmacAhead: KMAC is done, but we're still reading the high part of ROM
// Checking: We are comparing DIGEST and EXP_DIGEST and sending data to keymgr
// Done: Terminal state
// Invalid: Terminal and invalid state (only reachable by a glitch)
//
// The FSM is linear, except for the branch where reading the high part of ROM races with getting
// the result back from KMAC.
//
// digraph fsm {
// ReadingLow -> ReadingHigh;
// ReadingHigh -> RomAhead;
// ReadingHigh -> KmacAhead;
// RomAhead -> Checking;
// KmacAhead -> Checking;
// Checking -> Done;
// Done [peripheries=2];
// }
// SEC_CM: FSM.SPARSE
// SEC_CM: INTERSIG.MUBI
fsm_state_e state_d, state_q;
logic fsm_alert;
`PRIM_FLOP_SPARSE_FSM(u_state_regs, state_d, state_q, fsm_state_e, ReadingLow)
always_comb begin
state_d = state_q;
fsm_alert = 1'b0;
unique case (state_q)
ReadingLow: begin
// Switch to ReadingHigh when counter_lnt is true and kmac_rom_rdy_i & kmac_rom_vld_o
// (implying that the transaction went through)
if (counter_lnt && kmac_rom_rdy_i && kmac_rom_vld_o) begin
state_d = ReadingHigh;
end
end
ReadingHigh: begin
unique case ({kmac_done_i, counter_done})
2'b01: state_d = RomAhead;
2'b10: state_d = kmac_err_i ? Invalid : KmacAhead;
2'b11: state_d = kmac_err_i ? Invalid : Checking;
default: ; // No change
endcase
end
RomAhead: begin
if (kmac_done_i) state_d = kmac_err_i ? Invalid : Checking;
end
KmacAhead: begin
if (counter_done) state_d = Checking;
end
Checking: begin
if (checker_done) state_d = Done;
end
Done: begin
// Final state
end
default: begin
// An invalid state (includes the explicit Invalid state)
fsm_alert = 1'b1;
state_d = Invalid;
end
endcase
// Consistency checks for done signals.
//
// If checker_done is high in a state other than Checking or Done then something has gone wrong
// and we ran the check early. Similarly, counter_done should only be high after we've left
// ReadingLow. Finally, kmac_done_i should only be high in ReadingHigh or RomAhead. If any of
// these consistency requirements don't hold, jump to the Invalid state. This will also raise an
// alert on the following cycle.
//
// SEC_CM: CHECKER.CTRL_FLOW.CONSISTENCY
if ((checker_done && !(state_q inside {Checking, Done})) ||
(counter_done && state_q == ReadingLow) ||
(kmac_done_i && !(state_q inside {ReadingHigh, RomAhead}))) begin
state_d = Invalid;
end
// Jump to an invalid state if sending out an alert for any other reason
//
// SEC_CM: CHECKER.FSM.LOCAL_ESC
if (alert_o) begin
state_d = Invalid;
end
end
// The in_state_done signal is supposed to be true iff we're in FSM state Done. Grabbing just the
// bottom 4 bits of state_q is equivalent to "mubi4_bool_to_mubi(state_q == Done)" except that it
// doesn't have a 1-bit signal on the way.
logic [9:0] state_q_bits;
logic unused_state_q_top_bits;
assign state_q_bits = {state_q};
assign unused_state_q_top_bits = ^state_q_bits[9:4];
mubi4_t in_state_done;
assign in_state_done = mubi4_t'(state_q_bits[3:0]);
// Route digest signals coming back from KMAC straight to the CSRs
assign digest_o = kmac_digest_i;
assign digest_vld_o = kmac_done_i;
// Snoop on ROM reads to populate EXP_DIGEST, one word at a time
logic reading_top;
logic [AW-1:0] rel_addr_wide;
logic [TAW-1:0] rel_addr;
assign reading_top = (state_q == ReadingHigh || state_q == KmacAhead) & ~counter_done;
assign rel_addr_wide = counter_data_addr - TopStartAddr;
assign rel_addr = rel_addr_wide[TAW-1:0];
// The top bits of rel_addr_wide should always be zero if we're reading the top bits (because TAW
// bits should be enough to encode the difference between counter_data_addr and TopStartAddr)
`ASSERT(RelAddrWide_A, exp_digest_vld_o |-> ~|rel_addr_wide[AW-1:TAW])
logic unused_top_rel_addr_wide;
assign unused_top_rel_addr_wide = |rel_addr_wide[AW-1:TAW];
assign exp_digest_o = rom_data_i;
assign exp_digest_vld_o = reading_top;
assign exp_digest_idx_o = rel_addr;
// The 'done' signal for pwrmgr is asserted once we get into the Done state. The 'good' signal
// compes directly from the checker.
assign pwrmgr_data_o = '{done: in_state_done, good: checker_good};
// Pass the digest all-at-once to the keymgr. The loose check means that glitches will add
// spurious edges to the valid signal that can be caught at the other end.
assign keymgr_data_o = '{data: digest_i, valid: mubi4_test_true_loose(in_state_done)};
// KMAC rom data interface
logic kmac_rom_vld_d, kmac_rom_vld_q;
always_comb begin
// There will be valid data to pass to KMAC on each cycle after a counter request has gone out
// when we were in state ReadingLow. That data goes out (causing us to drop the valid signal) if
// KMAC was ready. Note that this formulation allows kmac_rom_vld_q to be high even if we're not
// in the ReadingLow state: if something goes wrong and we get faulted into Invalid then we'll
// still correctly send the end of the KMAC transaction.
kmac_rom_vld_d = kmac_rom_vld_q;
if (kmac_rom_rdy_i) begin
kmac_rom_vld_d = 0;
end
if (counter_read_req && state_q == ReadingLow && !counter_lnt) begin
kmac_rom_vld_d = 1;
end
end
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
kmac_rom_vld_q <= 0;
end else begin
kmac_rom_vld_q <= kmac_rom_vld_d;
end
end
assign counter_data_rdy = kmac_rom_rdy_i | (state_q inside {ReadingHigh, KmacAhead});
assign kmac_rom_vld_o = kmac_rom_vld_q;
assign kmac_rom_last_o = counter_lnt;
// The "last" flag is signalled when we're reading the last word in the first part of the ROM. As
// a quick consistency check, this should only happen when the "valid" flag is also high.
`ASSERT(LastImpliesValid_A, kmac_rom_last_o |-> kmac_rom_vld_o,
clk_i, !rst_ni || (state_q == Invalid))
// Start the checker when transitioning into the "Checking" state
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
start_checker_q <= 1'b0;
end else begin
start_checker_q <= (state_q != Checking) && (state_d == Checking);
end
end
// The counter is supposed to run from zero up to the top of memory and then tell us that it's
// done with the counter_done signal. We would like to be sure that no-one can fiddle with the
// counter address once the hash has been computed (if they could subvert the mux as well, this
// would allow them to generate a useful wrong address for a fetch). Fortunately, doing so would
// cause the counter_done signal to drop again and we *know* that it should stay high when our FSM
// is in the Done state.
//
// SEC_CM: CHECKER.CTR.CONSISTENCY
logic unexpected_counter_change;
assign unexpected_counter_change = mubi4_test_true_loose(in_state_done) & !counter_done;
// We keep control of the ROM mux from reset until we're done.
assign rom_select_bus_o = in_state_done;
assign rom_addr_o = counter_read_addr;
assign rom_req_o = counter_read_req;
assign alert_o = fsm_alert | checker_alert | unexpected_counter_change;
endmodule