blob: 475b57ac397cdb4998da238eeeb5aced6fe0e46f [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Key manager interface to kmac
//
`include "prim_assert.sv"
module keymgr_kmac_if import keymgr_pkg::*;(
input clk_i,
input rst_ni,
// data input interfaces
input [AdvDataWidth-1:0] adv_data_i,
input [IdDataWidth-1:0] id_data_i,
input [GenDataWidth-1:0] gen_data_i,
input [3:0] inputs_invalid_i,
output logic inputs_invalid_o,
// keymgr control to select appropriate inputs
input adv_en_i,
input id_en_i,
input gen_en_i,
output logic done_o,
output logic [Shares-1:0][kmac_pkg::AppDigestW-1:0] data_o,
// actual connection to kmac
output kmac_pkg::app_req_t kmac_data_o,
input kmac_pkg::app_rsp_t kmac_data_i,
// entropy input
output logic prng_en_o,
input [Shares-1:0][RandWidth-1:0] entropy_i,
// error outputs
output logic fsm_error_o,
output logic kmac_error_o,
output logic kmac_done_error_o,
output logic cmd_error_o
);
// Encoding generated with:
// $ ./util/design/sparse-fsm-encode.py -d 5 -m 6 -n 10 \
// -s 2292624416 --language=sv
//
// Hamming distance histogram:
//
// 0: --
// 1: --
// 2: --
// 3: --
// 4: --
// 5: |||||||||||||||||||| (46.67%)
// 6: ||||||||||||||||| (40.00%)
// 7: ||||| (13.33%)
// 8: --
// 9: --
// 10: --
//
// Minimum Hamming distance: 5
// Maximum Hamming distance: 7
// Minimum Hamming weight: 2
// Maximum Hamming weight: 9
//
localparam int StateWidth = 10;
typedef enum logic [StateWidth-1:0] {
StIdle = 10'b1110100010,
StTx = 10'b0010011011,
StTxLast = 10'b0101000000,
StOpWait = 10'b1000101001,
StClean = 10'b1111111101,
StError = 10'b0011101110
} data_state_e;
localparam int AdvRem = AdvDataWidth % KmacDataIfWidth;
localparam int IdRem = IdDataWidth % KmacDataIfWidth;
localparam int GenRem = GenDataWidth % KmacDataIfWidth;
// the remainder must be in number of bytes
`ASSERT_INIT(AdvRemBytes_A, AdvRem % 8 == 0)
`ASSERT_INIT(IdRemBytes_A, IdRem % 8 == 0)
`ASSERT_INIT(GenRemBytes_A, GenRem % 8 == 0)
// Number of kmac transactions required
localparam int AdvRounds = (AdvDataWidth + KmacDataIfWidth - 1) / KmacDataIfWidth;
localparam int IdRounds = (IdDataWidth + KmacDataIfWidth - 1) / KmacDataIfWidth;
localparam int GenRounds = (GenDataWidth + KmacDataIfWidth - 1) / KmacDataIfWidth;
localparam int MaxRounds = KDFMaxWidth / KmacDataIfWidth;
// calculated parameters for number of roudns and interface width
localparam int CntWidth = $clog2(MaxRounds);
localparam int IfBytes = KmacDataIfWidth / 8;
localparam int DecoyCopies = KmacDataIfWidth / RandWidth;
localparam int DecoyOutputCopies = (kmac_pkg::AppDigestW / RandWidth) * Shares;
localparam int unsigned LastAdvRoundInt = AdvRounds - 1;
localparam int unsigned LastIdRoundInt = IdRounds - 1;
localparam int unsigned LastGenRoundInt = GenRounds - 1;
localparam bit [CntWidth-1:0] LastAdvRound = LastAdvRoundInt[CntWidth-1:0];
localparam bit [CntWidth-1:0] LastIdRound = LastIdRoundInt[CntWidth-1:0];
localparam bit [CntWidth-1:0] LastGenRound = LastGenRoundInt[CntWidth-1:0];
// byte mask for the last transfer
localparam logic [IfBytes-1:0] AdvByteMask = (AdvRem > 0) ? (2**(AdvRem/8)-1) : {IfBytes{1'b1}};
localparam logic [IfBytes-1:0] IdByteMask = (IdRem > 0) ? (2**(IdRem/8)-1) : {IfBytes{1'b1}};
localparam logic [IfBytes-1:0] GenByteMask = (GenRem > 0) ? (2**(GenRem/8)-1) : {IfBytes{1'b1}};
logic [MaxRounds-1:0][KmacDataIfWidth-1:0] adv_data;
logic [MaxRounds-1:0][KmacDataIfWidth-1:0] id_data;
logic [MaxRounds-1:0][KmacDataIfWidth-1:0] gen_data;
logic [CntWidth-1:0] cnt;
logic [CntWidth-1:0] rounds;
logic [KmacDataIfWidth-1:0] decoy_data;
logic valid;
logic last;
logic [IfBytes-1:0] strb;
logic cnt_clr, cnt_set, cnt_en;
logic start;
logic [3:0] inputs_invalid_d, inputs_invalid_q;
logic clr_err;
logic kmac_done_vld;
logic cmd_chk;
data_state_e state_q, state_d;
// 0 pad to the appropriate width
// this is basically for scenarios where *DataWidth % KmacDataIfWidth != 0
assign adv_data = KDFMaxWidth'(adv_data_i);
assign id_data = KDFMaxWidth'(id_data_i);
assign gen_data = KDFMaxWidth'(gen_data_i);
assign start = adv_en_i | id_en_i | gen_en_i;
logic cnt_err;
// SEC_CM: KMAC_IF.CTR.REDUN
prim_count #(
.Width(CntWidth),
.ResetValue({CntWidth{1'b1}})
) u_cnt (
.clk_i,
.rst_ni,
.clr_i(cnt_clr),
.set_i(cnt_set),
.set_cnt_i(rounds),
.incr_en_i(1'b0),
.decr_en_i(cnt_en),
.step_i(CntWidth'(1'b1)),
.cnt_o(cnt),
.cnt_next_o(),
.err_o(cnt_err)
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
inputs_invalid_q <= '0;
end else begin
inputs_invalid_q <= inputs_invalid_d;
end
end
// SEC_CM: KMAC_IF.FSM.SPARSE
`PRIM_FLOP_SPARSE_FSM(u_state_regs, state_d, state_q, data_state_e, StIdle)
always_comb begin
cnt_clr = 1'b0;
cnt_set = 1'b0;
cnt_en = 1'b0;
valid = 1'b0;
last = 1'b0;
strb = '0;
done_o = 1'b0;
state_d = state_q;
rounds = '0;
clr_err = '0;
fsm_error_o = '0;
kmac_error_o = '0;
kmac_done_vld = '0;
cmd_chk = 1'b1;
unique case (state_q)
StIdle: begin
// if for some reason multiple bits are set, adv_en has priority
// as the current key state will be destroyed
// cross check for commands once transaction begins
cmd_chk = '0;
if (start) begin
cnt_set = 1'b1;
if (adv_en_i) begin
rounds = LastAdvRound;
end else if (id_en_i) begin
rounds = LastIdRound;
end else if (gen_en_i) begin
rounds = LastGenRound;
end
// in case we are sending only 1 entry
state_d = (rounds == 0) ? StTxLast : StTx;
end
end
StTx: begin
valid = 1'b1;
strb = {IfBytes{1'b1}};
// transaction accepted
if (kmac_data_i.ready) begin
cnt_en = 1'b1;
// second to last beat
if (cnt == CntWidth'(1'b1)) begin
state_d = StTxLast;
end
end
end
StTxLast: begin
valid = 1'b1;
last = 1'b1;
if (adv_en_i) begin
strb = AdvByteMask;
end else if (id_en_i) begin
strb = IdByteMask;
end else if (gen_en_i) begin
strb = GenByteMask;
end
// transaction accepted
cnt_clr = kmac_data_i.ready;
state_d = kmac_data_i.ready ? StOpWait : StTxLast;
end
StOpWait: begin
kmac_done_vld = 1'b1;
if (kmac_data_i.done) begin
kmac_error_o = kmac_data_i.error;
done_o = 1'b1;
state_d = StClean;
end
end
StClean: begin
cmd_chk = '0;
done_o = 1'b1;
// wait for control side to ack done by waiting start de-assertion
if (!start) begin
done_o = 1'b0;
clr_err = 1'b1;
state_d = StIdle;
end
end
// trigger error
default: begin
// This state is terminal
done_o = 1'b1;
fsm_error_o = 1'b1;
end
endcase // unique case (state_q)
// unconditional error transitions
// counter errors may disturb the fsm flow and are
// treated like fsm errors
if (cnt_err) begin
state_d = StError;
fsm_error_o = 1;
done_o = 1'b1;
end
end
// when transaction is not complete, populate the data with random
assign data_o = start && done_o ?
{kmac_data_i.digest_share1,
kmac_data_i.digest_share0} :
{DecoyOutputCopies{entropy_i[0]}};
// The input invalid check is done whenever transactions are ongoing with kmac
// once set, it cannot be unset until transactions are fully complete
always_comb begin
inputs_invalid_d = inputs_invalid_q;
if (clr_err) begin
inputs_invalid_d = '0;
end else if (valid) begin
inputs_invalid_d[OpAdvance] = adv_en_i & (inputs_invalid_i[OpAdvance] |
inputs_invalid_q[OpAdvance]);
inputs_invalid_d[OpGenId] = id_en_i & (inputs_invalid_i[OpGenId] |
inputs_invalid_q[OpGenId]);
inputs_invalid_d[OpGenSwOut] = gen_en_i & (inputs_invalid_i[OpGenSwOut]|
inputs_invalid_q[OpGenSwOut]);
inputs_invalid_d[OpGenHwOut] = gen_en_i & (inputs_invalid_i[OpGenHwOut]|
inputs_invalid_q[OpGenHwOut]);
end
end
// immediately assert errors
assign inputs_invalid_o = |inputs_invalid_d;
logic [CntWidth-1:0] adv_sel, id_sel, gen_sel;
assign adv_sel = LastAdvRound - cnt;
assign id_sel = LastIdRound - cnt;
assign gen_sel = LastGenRound - cnt;
// The count is maintained as a downcount
// so a subtract is necessary to send the right byte
// alternatively we can also reverse the order of the input
assign decoy_data = {DecoyCopies{entropy_i[1]}};
always_comb begin
kmac_data_o.data = decoy_data;
if (|cmd_error_o || inputs_invalid_o || fsm_error_o) begin
kmac_data_o.data = decoy_data;
end else if (valid && adv_en_i) begin
kmac_data_o.data = adv_data[adv_sel];
end else if (valid && id_en_i) begin
kmac_data_o.data = id_data[id_sel];
end else if (valid && gen_en_i) begin
kmac_data_o.data = gen_data[gen_sel];
end
end
assign kmac_data_o.valid = valid;
assign kmac_data_o.last = last;
assign kmac_data_o.strb = strb;
// kmac done is asserted outside of expected window
// SEC_CM: KMAC_IF_DONE.CTRL.CONSISTENCY
logic kmac_done_err_q, kmac_done_err_d;
assign kmac_done_err_d = ~kmac_done_vld & kmac_data_i.done |
kmac_done_err_q;
assign kmac_done_error_o = kmac_done_err_q;
// the enables must be 1 hot
logic [2:0] enables_d, enables_q, enables_sub;
assign enables_d = {adv_en_i, id_en_i, gen_en_i};
assign enables_sub = enables_d - 1'b1;
// cross check to ensure the one-hot command that kicked off
// the transaction remains consistent throughout.
logic cmd_consty_err_q, cmd_consty_err_d;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
enables_q <= '0;
end else if (cnt_set) begin
enables_q <= enables_d;
end
end
assign cmd_consty_err_d = (cmd_chk & (enables_q != enables_d)) |
cmd_consty_err_q;
// if a one hot error occurs, latch onto it permanently
// SEC_CM: KMAC_IF_CMD.CTRL.CONSISTENCY
logic one_hot_err_q, one_hot_err_d;
assign one_hot_err_d = |(enables_d & enables_sub) |
one_hot_err_q;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
one_hot_err_q <= '0;
kmac_done_err_q <= '0;
cmd_consty_err_q <= '0;
end else begin
one_hot_err_q <= one_hot_err_d;
kmac_done_err_q <= kmac_done_err_d;
cmd_consty_err_q <= cmd_consty_err_d;
end
end
// command error occurs if kmac errors or if the command itself is invalid
assign cmd_error_o = one_hot_err_q | cmd_consty_err_q;
// request entropy to churn whenever a transaction is accepted
assign prng_en_o = kmac_data_o.valid & kmac_data_i.ready;
// as long as we are transmitting, the strobe should never be 0.
`ASSERT(LastStrb_A, valid |-> strb != '0)
endmodule // keymgr_kmac_if