blob: 702951bb54986e2d6a0118985b5b305fb2313fa2 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// HMAC-SHA256
`include "prim_assert.sv"
module hmac
import hmac_pkg::*;
import hmac_reg_pkg::*;
#(
parameter logic [NumAlerts-1:0] AlertAsyncOn = {NumAlerts{1'b1}}
) (
input clk_i,
input rst_ni,
input tlul_pkg::tl_h2d_t tl_i,
output tlul_pkg::tl_d2h_t tl_o,
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,
output logic intr_hmac_done_o,
output logic intr_fifo_empty_o,
output logic intr_hmac_err_o,
output prim_mubi_pkg::mubi4_t idle_o
);
/////////////////////////
// Signal declarations //
/////////////////////////
hmac_reg2hw_t reg2hw;
hmac_hw2reg_t hw2reg;
tlul_pkg::tl_h2d_t tl_win_h2d;
tlul_pkg::tl_d2h_t tl_win_d2h;
logic [255:0] secret_key;
logic wipe_secret;
logic [31:0] wipe_v;
logic fifo_rvalid;
logic fifo_rready;
sha_fifo_t fifo_rdata;
logic fifo_wvalid, fifo_wready;
sha_fifo_t fifo_wdata;
logic fifo_full;
logic fifo_empty;
logic [4:0] fifo_depth;
logic msg_fifo_req;
logic msg_fifo_gnt;
logic msg_fifo_we;
logic [31:0] msg_fifo_wdata;
logic [31:0] msg_fifo_wmask;
logic [31:0] msg_fifo_rdata;
logic msg_fifo_rvalid;
logic [1:0] msg_fifo_rerror;
logic [31:0] msg_fifo_wdata_endian;
logic [31:0] msg_fifo_wmask_endian;
logic packer_ready;
logic packer_flush_done;
logic reg_fifo_wvalid;
sha_word_t reg_fifo_wdata;
sha_word_t reg_fifo_wmask;
logic hmac_fifo_wsel;
logic hmac_fifo_wvalid;
logic [2:0] hmac_fifo_wdata_sel;
logic shaf_rvalid;
sha_fifo_t shaf_rdata;
logic shaf_rready;
logic sha_en;
logic hmac_en;
logic endian_swap;
logic digest_swap;
logic reg_hash_start;
logic sha_hash_start;
logic hash_start; // Valid hash_start_signal
logic reg_hash_process;
logic sha_hash_process;
logic reg_hash_done;
logic sha_hash_done;
logic [63:0] message_length;
logic [63:0] sha_message_length;
err_code_e err_code;
logic err_valid;
sha_word_t [7:0] digest;
hmac_reg2hw_cfg_reg_t cfg_reg;
logic cfg_block; // Prevent changing config
logic msg_allowed; // MSG_FIFO from software is allowed
logic hmac_core_idle;
logic sha_core_idle;
///////////////////////
// Connect registers //
///////////////////////
assign hw2reg.status.fifo_full.d = fifo_full;
assign hw2reg.status.fifo_empty.d = fifo_empty;
assign hw2reg.status.fifo_depth.d = fifo_depth;
// secret key
assign wipe_secret = reg2hw.wipe_secret.qe;
assign wipe_v = reg2hw.wipe_secret.q;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
secret_key <= '0;
end else if (wipe_secret) begin
secret_key <= {8{wipe_v}};
end else if (!cfg_block) begin
// Allow updating secret key only when the engine is in Idle.
for (int i = 0; i < 8; i++) begin
if (reg2hw.key[7-i].qe) begin
secret_key[32*i+:32] <= reg2hw.key[7-i].q;
end
end
end
end
for (genvar i = 0; i < 8; i++) begin : gen_key_digest
assign hw2reg.key[7-i].d = '0;
// digest
assign hw2reg.digest[i].d = conv_endian(digest[i], digest_swap);
end
logic [3:0] unused_cfg_qe;
assign unused_cfg_qe = {cfg_reg.sha_en.qe, cfg_reg.hmac_en.qe,
cfg_reg.endian_swap.qe, cfg_reg.digest_swap.qe};
assign sha_en = cfg_reg.sha_en.q;
assign hmac_en = cfg_reg.hmac_en.q;
assign endian_swap = cfg_reg.endian_swap.q;
assign digest_swap = cfg_reg.digest_swap.q;
assign hw2reg.cfg.hmac_en.d = cfg_reg.hmac_en.q;
assign hw2reg.cfg.sha_en.d = cfg_reg.sha_en.q;
assign hw2reg.cfg.endian_swap.d = cfg_reg.endian_swap.q;
assign hw2reg.cfg.digest_swap.d = cfg_reg.digest_swap.q;
assign reg_hash_start = reg2hw.cmd.hash_start.qe & reg2hw.cmd.hash_start.q;
assign reg_hash_process = reg2hw.cmd.hash_process.qe & reg2hw.cmd.hash_process.q;
// Error code register
assign hw2reg.err_code.de = err_valid;
assign hw2reg.err_code.d = err_code;
/////////////////////
// Control signals //
/////////////////////
assign hash_start = reg_hash_start & sha_en & ~cfg_block;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
cfg_block <= '0;
end else if (hash_start) begin
cfg_block <= 1'b 1;
end else if (reg_hash_done) begin
cfg_block <= 1'b 0;
end
end
// Hold the configuration during the process
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
cfg_reg <= '{
endian_swap: '{
q: HMAC_CFG_ENDIAN_SWAP_RESVAL,
qe: 1'b0
},
default:'0
};
end else if (!cfg_block && reg2hw.cfg.hmac_en.qe) begin
cfg_reg <= reg2hw.cfg ;
end
end
// Open up the MSG_FIFO from the TL-UL port when it is ready
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
msg_allowed <= '0;
end else if (hash_start) begin
msg_allowed <= 1'b 1;
end else if (packer_flush_done) begin
msg_allowed <= 1'b 0;
end
end
////////////////
// Interrupts //
////////////////
logic fifo_empty_q, fifo_empty_event;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
fifo_empty_q <= '1; // By default, it is empty
end else if (!hmac_fifo_wsel) begin
fifo_empty_q <= fifo_empty;
end
end
assign fifo_empty_event = fifo_empty & ~fifo_empty_q;
logic [2:0] event_intr;
assign event_intr = {err_valid, fifo_empty_event, reg_hash_done};
// instantiate interrupt hardware primitive
prim_intr_hw #(.Width(1)) intr_hw_hmac_done (
.clk_i,
.rst_ni,
.event_intr_i (event_intr[0]),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.hmac_done.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.hmac_done.q),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.hmac_done.qe),
.reg2hw_intr_state_q_i (reg2hw.intr_state.hmac_done.q),
.hw2reg_intr_state_de_o (hw2reg.intr_state.hmac_done.de),
.hw2reg_intr_state_d_o (hw2reg.intr_state.hmac_done.d),
.intr_o (intr_hmac_done_o)
);
prim_intr_hw #(.Width(1)) intr_hw_fifo_empty (
.clk_i,
.rst_ni,
.event_intr_i (event_intr[1]),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.fifo_empty.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.fifo_empty.q),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.fifo_empty.qe),
.reg2hw_intr_state_q_i (reg2hw.intr_state.fifo_empty.q),
.hw2reg_intr_state_de_o (hw2reg.intr_state.fifo_empty.de),
.hw2reg_intr_state_d_o (hw2reg.intr_state.fifo_empty.d),
.intr_o (intr_fifo_empty_o)
);
prim_intr_hw #(.Width(1)) intr_hw_hmac_err (
.clk_i,
.rst_ni,
.event_intr_i (event_intr[2]),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.hmac_err.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.hmac_err.q),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.hmac_err.qe),
.reg2hw_intr_state_q_i (reg2hw.intr_state.hmac_err.q),
.hw2reg_intr_state_de_o (hw2reg.intr_state.hmac_err.de),
.hw2reg_intr_state_d_o (hw2reg.intr_state.hmac_err.d),
.intr_o (intr_hmac_err_o)
);
///////////////
// Instances //
///////////////
assign msg_fifo_rvalid = msg_fifo_req & ~msg_fifo_we;
assign msg_fifo_rdata = '1; // Return all F
assign msg_fifo_rerror = '1; // Return error for read access
assign msg_fifo_gnt = msg_fifo_req & ~hmac_fifo_wsel & packer_ready;
// FIFO control
sha_fifo_t reg_fifo_wentry;
assign reg_fifo_wentry.data = conv_endian(reg_fifo_wdata, 1'b1); // always convert
assign reg_fifo_wentry.mask = {reg_fifo_wmask[0], reg_fifo_wmask[8],
reg_fifo_wmask[16], reg_fifo_wmask[24]};
assign fifo_full = ~fifo_wready;
assign fifo_empty = ~fifo_rvalid;
assign fifo_wvalid = (hmac_fifo_wsel && fifo_wready) ? hmac_fifo_wvalid : reg_fifo_wvalid;
assign fifo_wdata = (hmac_fifo_wsel) ? '{data: digest[hmac_fifo_wdata_sel], mask: '1}
: reg_fifo_wentry;
prim_fifo_sync #(
.Width ($bits(sha_fifo_t)),
.Pass (1'b1),
.Depth (MsgFifoDepth)
) u_msg_fifo (
.clk_i,
.rst_ni,
.clr_i (1'b0),
.wvalid_i(fifo_wvalid & sha_en),
.wready_o(fifo_wready),
.wdata_i (fifo_wdata),
.depth_o (fifo_depth),
.full_o (),
.rvalid_o(fifo_rvalid),
.rready_i(fifo_rready),
.rdata_o (fifo_rdata),
.err_o ()
);
// TL ADAPTER SRAM
tlul_adapter_sram #(
.SramAw (9),
.SramDw (32),
.Outstanding (1),
.ByteAccess (1),
.ErrOnRead (1)
) u_tlul_adapter (
.clk_i,
.rst_ni,
.tl_i (tl_win_h2d),
.tl_o (tl_win_d2h),
.en_ifetch_i (prim_mubi_pkg::MuBi4False),
.req_o (msg_fifo_req ),
.req_type_o ( ),
.gnt_i (msg_fifo_gnt ),
.we_o (msg_fifo_we ),
.addr_o ( ), // Doesn't care the address other than sub-word
.wdata_o (msg_fifo_wdata ),
.wmask_o (msg_fifo_wmask ),
.intg_error_o( ),
.rdata_i (msg_fifo_rdata ),
.rvalid_i (msg_fifo_rvalid),
.rerror_i (msg_fifo_rerror)
);
// TL-UL to MSG_FIFO byte write handling
logic msg_write;
assign msg_write = msg_fifo_req & msg_fifo_we & ~hmac_fifo_wsel & msg_allowed;
logic [$clog2(32+1)-1:0] wmask_ones;
always_comb begin
wmask_ones = '0;
for (int i = 0 ; i < 32 ; i++) begin
wmask_ones = wmask_ones + msg_fifo_wmask[i];
end
end
// Calculate written message
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
message_length <= '0;
end else if (hash_start) begin
message_length <= '0;
end else if (msg_write && sha_en && packer_ready) begin
message_length <= message_length + 64'(wmask_ones);
end
end
assign hw2reg.msg_length_upper.de = 1'b1;
assign hw2reg.msg_length_upper.d = message_length[63:32];
assign hw2reg.msg_length_lower.de = 1'b1;
assign hw2reg.msg_length_lower.d = message_length[31:0];
// Convert endian here
// prim_packer always packs to the right, but SHA engine assumes incoming
// to be big-endian, [31:24] comes first. So, the data is reverted after
// prim_packer before the message fifo. here to reverse if not big-endian
// before pushing to the packer.
assign msg_fifo_wdata_endian = conv_endian(msg_fifo_wdata, endian_swap);
assign msg_fifo_wmask_endian = conv_endian(msg_fifo_wmask, endian_swap);
prim_packer #(
.InW (32),
.OutW (32),
.EnProtection (1'b 0)
) u_packer (
.clk_i,
.rst_ni,
.valid_i (msg_write & sha_en),
.data_i (msg_fifo_wdata_endian),
.mask_i (msg_fifo_wmask_endian),
.ready_o (packer_ready),
.valid_o (reg_fifo_wvalid),
.data_o (reg_fifo_wdata),
.mask_o (reg_fifo_wmask),
.ready_i (fifo_wready & ~hmac_fifo_wsel),
.flush_i (reg_hash_process),
.flush_done_o (packer_flush_done), // ignore at this moment
.err_o () // Not used
);
hmac_core u_hmac (
.clk_i,
.rst_ni,
.secret_key,
.wipe_secret,
.wipe_v,
.hmac_en,
.reg_hash_start (hash_start),
.reg_hash_process (packer_flush_done), // Trigger after all msg written
.hash_done (reg_hash_done),
.sha_hash_start,
.sha_hash_process,
.sha_hash_done,
.sha_rvalid (shaf_rvalid),
.sha_rdata (shaf_rdata),
.sha_rready (shaf_rready),
.fifo_rvalid,
.fifo_rdata,
.fifo_rready,
.fifo_wsel (hmac_fifo_wsel),
.fifo_wvalid (hmac_fifo_wvalid),
.fifo_wdata_sel (hmac_fifo_wdata_sel),
.fifo_wready,
.message_length,
.sha_message_length,
.idle (hmac_core_idle)
);
sha2 u_sha2 (
.clk_i,
.rst_ni,
.wipe_secret,
.wipe_v,
.fifo_rvalid (shaf_rvalid),
.fifo_rdata (shaf_rdata),
.fifo_rready (shaf_rready),
.sha_en,
.hash_start (sha_hash_start),
.hash_process (sha_hash_process),
.hash_done (sha_hash_done),
.message_length (sha_message_length),
.digest,
.idle (sha_core_idle)
);
// Register top
logic [NumAlerts-1:0] alert_test, alerts;
hmac_reg_top u_reg (
.clk_i,
.rst_ni,
.tl_i,
.tl_o,
.tl_win_o (tl_win_h2d),
.tl_win_i (tl_win_d2h),
.reg2hw,
.hw2reg,
// SEC_CM: BUS.INTEGRITY
.intg_err_o (alerts[0]),
.devmode_i (1'b1)
);
// Alerts
assign alert_test = {
reg2hw.alert_test.q &
reg2hw.alert_test.qe
};
localparam logic [NumAlerts-1:0] AlertIsFatal = {1'b1};
for (genvar i = 0; i < NumAlerts; i++) begin : gen_alert_tx
prim_alert_sender #(
.AsyncOn(AlertAsyncOn[i]),
.IsFatal(AlertIsFatal[i])
) u_prim_alert_sender (
.clk_i,
.rst_ni,
.alert_test_i ( alert_test[i] ),
.alert_req_i ( alerts[0] ),
.alert_ack_o ( ),
.alert_state_o ( ),
.alert_rx_i ( alert_rx_i[i] ),
.alert_tx_o ( alert_tx_o[i] )
);
end
/////////////////////////
// HMAC Error Handling //
/////////////////////////
logic hash_start_sha_disabled, update_seckey_inprocess;
logic hash_start_active; // `reg_hash_start` set when hash already in active
logic msg_push_not_allowed; // Message is received when `hash_start` isn't set
assign hash_start_sha_disabled = reg_hash_start & ~sha_en;
assign hash_start_active = reg_hash_start & cfg_block;
assign msg_push_not_allowed = msg_fifo_req & ~msg_allowed;
always_comb begin
update_seckey_inprocess = 1'b0;
if (cfg_block) begin
for (int i = 0 ; i < 8 ; i++) begin
if (reg2hw.key[i].qe) begin
update_seckey_inprocess = update_seckey_inprocess | 1'b1;
end
end
end else begin
update_seckey_inprocess = 1'b0;
end
end
// Update ERR_CODE register and interrupt only when no pending interrupt.
// This ensures only the first event of the series of events can be seen to sw.
// It is recommended that the software reads ERR_CODE register when interrupt
// is pending to avoid any race conditions.
assign err_valid = ~reg2hw.intr_state.hmac_err.q &
( hash_start_sha_disabled | update_seckey_inprocess
| hash_start_active | msg_push_not_allowed );
always_comb begin
err_code = NoError;
unique case (1'b1)
hash_start_sha_disabled: begin
err_code = SwHashStartWhenShaDisabled;
end
update_seckey_inprocess: begin
err_code = SwUpdateSecretKeyInProcess;
end
hash_start_active: begin
err_code = SwHashStartWhenActive;
end
msg_push_not_allowed: begin
err_code = SwPushMsgWhenDisallowed;
end
default: begin
err_code = NoError;
end
endcase
end
/////////////////////
// Unused Signals //
/////////////////////
logic unused_wmask;
assign unused_wmask = ^reg_fifo_wmask;
/////////////////////
// Idle output //
/////////////////////
// TBD this should be connected later
// Idle: AND condition of:
// - packer empty: Currently no way to guarantee the packer is empty.
// temporary, the logic uses packer output (reg_fifo_wvalid)
// - MSG_FIFO --> fifo_rvalid
// - HMAC_CORE --> hmac_core_idle
// - SHA2_CORE --> sha_core_idle
// - Clean interrupt status
// ICEBOX(#12958): Revise prim_packer and replace `reg_fifo_wvalid` to the
// empty status.
logic idle;
assign idle = !reg_fifo_wvalid && !fifo_rvalid
&& hmac_core_idle && sha_core_idle;
prim_mubi_pkg::mubi4_t idle_q, idle_d;
assign idle_d = prim_mubi_pkg::mubi4_bool_to_mubi(idle);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
idle_q <= prim_mubi_pkg::MuBi4False;
end else begin
idle_q <= idle_d;
end
end
assign idle_o = idle_q;
//////////////////////////////////////////////
// Assertions, Assumptions, and Coverpoints //
//////////////////////////////////////////////
`ifndef VERILATOR
`ifndef SYNTHESIS
// HMAC assumes TL-UL mask is byte-aligned.
property wmask_bytealign_p(wmask_byte, clk, rst_n);
@(posedge clk) disable iff (rst_n == 0)
msg_fifo_req & msg_fifo_we |-> wmask_byte inside {'0, '1};
endproperty
for (genvar i = 0 ; i < 4; i++) begin: gen_assert_wmask_bytealign
assert property (wmask_bytealign_p(msg_fifo_wmask[8*i+:8], clk_i, rst_ni));
end
// To pass FPV, this shouldn't add pragma translate_off even these two signals
// are used in Assertion only
logic in_process;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) in_process <= 1'b0;
else if (reg_hash_process) in_process <= 1'b1;
else if (reg_hash_done) in_process <= 1'b0;
end
logic initiated;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) initiated <= 1'b0;
else if (hash_start) initiated <= 1'b1;
else if (reg_hash_process) initiated <= 1'b0;
end
// the host doesn't write data after hash_process until hash_start.
// Same as "message_length shouldn't be changed between hash_process and done
`ASSERT(ValidWriteAssert, msg_fifo_req |-> !in_process)
// `hash_process` shall be toggle and paired with `hash_start`.
// Below condition is covered by the design (2020-02-19)
//`ASSERT(ValidHashStartAssert, hash_start |-> !initiated)
`ASSERT(ValidHashProcessAssert, reg_hash_process |-> initiated)
// between `hash_done` and `hash_start`, message FIFO should be empty
`ASSERT(MsgFifoEmptyWhenNoOpAssert,
!in_process && !initiated |-> $stable(message_length))
// hmac_en should be modified only when the logic is Idle
`ASSERT(ValidHmacEnConditionAssert,
hmac_en != $past(hmac_en) |-> !in_process && !initiated)
// All outputs should be known value after reset
`ASSERT_KNOWN(IntrHmacDoneOKnown, intr_hmac_done_o)
`ASSERT_KNOWN(IntrFifoEmptyOKnown, intr_fifo_empty_o)
`ASSERT_KNOWN(TlODValidKnown, tl_o.d_valid)
`ASSERT_KNOWN(TlOAReadyKnown, tl_o.a_ready)
`ASSERT_KNOWN(AlertKnownO_A, alert_tx_o)
`endif // SYNTHESIS
`endif // VERILATOR
// Alert assertions for reg_we onehot check
`ASSERT_PRIM_REG_WE_ONEHOT_ERROR_TRIGGER_ALERT(RegWeOnehotCheck_A, u_reg, alert_tx_o[0])
endmodule