blob: c201eb21ccfd9f66bd4f5f2fa6d256518b92d51f [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 comparator inside the ROM checker
//
// This module is in charge of comparing the digest that was computed over the ROM data with the
// expected digest stored in the top few words.
//
//
// TODO: Think properly about hardening here. A glitch that messes up the comparison isn't a
// show-stopper (because the attacker will still have the wrong CreatorRootKey in the key
// manager), but glitching the FSM our counter could probably confuse some of the other blocks
// that we communicate with.
`include "prim_assert.sv"
module rom_ctrl_compare #(
parameter int NumWords = 2
) (
input logic clk_i,
input logic rst_ni,
input logic start_i,
output logic done_o,
output logic good_o,
// CSR inputs for DIGEST and EXP_DIGEST. Ordered with word 0 as LSB.
input logic [NumWords*32-1:0] digest_i,
input logic [NumWords*32-1:0] exp_digest_i,
// To alert system
output logic alert_o
);
import prim_util_pkg::vbits;
`ASSERT_INIT(NumWordsPositive_A, 0 < NumWords)
localparam int AW = vbits(NumWords);
localparam int unsigned EndAddrInt = NumWords;
localparam int unsigned LastAddrInt = NumWords - 1;
// Note that if NumWords is a power of 2 then EndAddr will be zero. That's ok: we're just using a
// comparison with EndAddr to check that the address counter hasn't started wandering around when
// we're in the wrong state.
localparam bit [AW-1:0] EndAddr = EndAddrInt[AW-1:0];
localparam bit [AW-1:0] LastAddr = LastAddrInt[AW-1:0];
logic [AW-1:0] addr_q;
// This module must wait until triggered by a write to start_i. At that point, it cycles through
// the words of DIGEST and EXP_DIGEST, comparing them to one another and passing each digest word
// to the key manager. Finally, it gets to the Done state.
//
// States:
//
// Waiting
// Checking
// Done
//
// Encoding generated with:
// $ util/design/sparse-fsm-encode.py -d 3 -m 3 -n 5 -s 1 --language=sv
//
// Hamming distance histogram:
//
// 0: --
// 1: --
// 2: --
// 3: |||||||||||||||||||| (66.67%)
// 4: |||||||||| (33.33%)
// 5: --
//
// Minimum Hamming distance: 3
// Maximum Hamming distance: 4
// Minimum Hamming weight: 1
// Maximum Hamming weight: 3
//
typedef enum logic [4:0] {
Waiting = 5'b00100,
Checking = 5'b10010,
Done = 5'b11001
} state_e;
logic [4:0] state_q, state_d;
logic matches_q, matches_d;
logic fsm_alert;
prim_flop #(.Width(5), .ResetValue({Waiting}))
u_state_regs (
.clk_i (clk_i),
.rst_ni (rst_ni),
.d_i (state_d),
.q_o (state_q)
);
always_comb begin
state_d = state_q;
fsm_alert = 1'b0;
unique case (state_q)
Waiting: begin
if (start_i) state_d = Checking;
end
Checking: begin
if (addr_q == LastAddr) state_d = Done;
end
Done: begin
// Final state
end
default: fsm_alert = 1'b1;
endcase
end
// start_i should only be signalled when we're in the Waiting state
logic start_alert;
assign start_alert = start_i && (state_q != Waiting);
// addr_q should be zero when we're in the Waiting state
logic wait_addr_alert;
assign wait_addr_alert = (state_q == Waiting) && (addr_q != '0);
// addr_q should be EndAddr when we're in the Done state
logic done_addr_alert;
assign done_addr_alert = (state_q == Done) && (addr_q != EndAddr);
// Increment addr_q on each cycle when in Checking
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
addr_q <= '0;
matches_q <= 1'b1;
end else begin
if (state_q == Checking) begin
addr_q <= addr_q + AW'(1);
matches_q <= matches_d;
end
end
end
logic [AW+5-1:0] digest_idx;
logic [31:0] digest_word, exp_digest_word;
assign digest_idx = {addr_q, 5'd31};
assign digest_word = digest_i[digest_idx -: 32];
assign exp_digest_word = exp_digest_i[digest_idx -: 32];
assign matches_d = matches_q && (digest_word == exp_digest_word);
assign done_o = (state_q == Done);
assign good_o = matches_q;
assign alert_o = fsm_alert | start_alert | wait_addr_alert | done_addr_alert;
endmodule