blob: 86bca869fbdf9ef0ed7a8c217da1d1e5a7cce9e9 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// SRAM controller.
//
`include "prim_assert.sv"
module sram_ctrl
import sram_ctrl_pkg::*;
import sram_ctrl_reg_pkg::*;
#(
// Number of words stored in the SRAM.
parameter int MemSizeRam = 32'h1000,
// Enable asynchronous transitions on alerts.
parameter logic [NumAlerts-1:0] AlertAsyncOn = {NumAlerts{1'b1}},
// Enables the execute from SRAM feature.
parameter bit InstrExec = 1,
// Random netlist constants
parameter otp_ctrl_pkg::sram_key_t RndCnstSramKey = RndCnstSramKeyDefault,
parameter otp_ctrl_pkg::sram_nonce_t RndCnstSramNonce = RndCnstSramNonceDefault,
parameter lfsr_seed_t RndCnstLfsrSeed = RndCnstLfsrSeedDefault,
parameter lfsr_perm_t RndCnstLfsrPerm = RndCnstLfsrPermDefault
) (
// SRAM Clock
input logic clk_i,
input logic rst_ni,
// OTP Clock (for key interface)
input logic clk_otp_i,
input logic rst_otp_ni,
// Bus Interface (device) for SRAM
input tlul_pkg::tl_h2d_t ram_tl_i,
output tlul_pkg::tl_d2h_t ram_tl_o,
// Bus Interface (device) for CSRs
input tlul_pkg::tl_h2d_t regs_tl_i,
output tlul_pkg::tl_d2h_t regs_tl_o,
// Alert outputs.
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,
// Life-cycle escalation input (scraps the scrambling keys)
// SEC_CM: LC_ESCALATE_EN.INTERSIG.MUBI
input lc_ctrl_pkg::lc_tx_t lc_escalate_en_i,
// SEC_CM: LC_HW_DEBUG_EN.INTERSIG.MUBI
input lc_ctrl_pkg::lc_tx_t lc_hw_debug_en_i,
// Otp configuration for sram execution
// SEC_CM: EXEC.INTERSIG.MUBI
input prim_mubi_pkg::mubi8_t otp_en_sram_ifetch_i,
// Key request to OTP (running on clk_fixed)
// SEC_CM: SCRAMBLE.KEY.SIDELOAD
output otp_ctrl_pkg::sram_otp_key_req_t sram_otp_key_o,
input otp_ctrl_pkg::sram_otp_key_rsp_t sram_otp_key_i,
// config
input prim_ram_1p_pkg::ram_1p_cfg_t cfg_i
);
import lc_ctrl_pkg::lc_tx_t;
import lc_ctrl_pkg::lc_tx_test_true_loose;
import lc_ctrl_pkg::lc_tx_bool_to_lc_tx;
import lc_ctrl_pkg::lc_tx_or_hi;
import lc_ctrl_pkg::lc_tx_inv;
import lc_ctrl_pkg::lc_to_mubi4;
// This is later on pruned to the correct width at the SRAM wrapper interface.
parameter int unsigned Depth = MemSizeRam >> 2;
parameter int unsigned AddrWidth = prim_util_pkg::vbits(Depth);
`ASSERT_INIT(NonceWidthsLessThanSource_A, NonceWidth + LfsrWidth <= otp_ctrl_pkg::SramNonceWidth)
/////////////////////////////////////
// Anchor incoming seeds and constants
/////////////////////////////////////
localparam int TotalAnchorWidth = $bits(otp_ctrl_pkg::sram_key_t) +
$bits(otp_ctrl_pkg::sram_nonce_t);
otp_ctrl_pkg::sram_key_t cnst_sram_key;
otp_ctrl_pkg::sram_nonce_t cnst_sram_nonce;
prim_sec_anchor_buf #(
.Width(TotalAnchorWidth)
) u_seed_anchor (
.in_i({RndCnstSramKey,
RndCnstSramNonce}),
.out_o({cnst_sram_key,
cnst_sram_nonce})
);
//////////////////////////
// CSR Node and Mapping //
//////////////////////////
// We've got two bus interfaces + an lc_gate in this module,
// hence three integ failure sources.
logic [2:0] bus_integ_error;
sram_ctrl_regs_reg2hw_t reg2hw;
sram_ctrl_regs_hw2reg_t hw2reg;
// SEC_CM: CTRL.CONFIG.REGWEN
// SEC_CM: EXEC.CONFIG.REGWEN
sram_ctrl_regs_reg_top u_reg_regs (
.clk_i,
.rst_ni,
.tl_i (regs_tl_i),
.tl_o (regs_tl_o),
.reg2hw,
.hw2reg,
// SEC_CM: BUS.INTEGRITY
.intg_err_o(bus_integ_error[0]),
.devmode_i (1'b1)
);
// Key and attribute outputs to scrambling device
logic [otp_ctrl_pkg::SramKeyWidth-1:0] key_d, key_q;
logic [otp_ctrl_pkg::SramNonceWidth-1:0] nonce_d, nonce_q;
// tie-off unused nonce bits
if (otp_ctrl_pkg::SramNonceWidth > NonceWidth) begin : gen_nonce_tieoff
logic unused_nonce;
assign unused_nonce = ^nonce_q[otp_ctrl_pkg::SramNonceWidth-1:NonceWidth];
end
//////////////////
// Alert Sender //
//////////////////
logic alert_test;
assign alert_test = reg2hw.alert_test.q & reg2hw.alert_test.qe;
assign hw2reg.status.bus_integ_error.d = 1'b1;
assign hw2reg.status.bus_integ_error.de = |bus_integ_error;
logic init_error;
assign hw2reg.status.init_error.d = 1'b1;
assign hw2reg.status.init_error.de = init_error;
logic alert_req;
assign alert_req = (|bus_integ_error) | init_error;
prim_alert_sender #(
.AsyncOn(AlertAsyncOn[0]),
.IsFatal(1)
) u_prim_alert_sender_parity (
.clk_i,
.rst_ni,
.alert_test_i ( alert_test ),
.alert_req_i ( alert_req ),
.alert_ack_o ( ),
.alert_state_o ( ),
.alert_rx_i ( alert_rx_i[0] ),
.alert_tx_o ( alert_tx_o[0] )
);
/////////////////////////
// Escalation Triggers //
/////////////////////////
lc_tx_t [1:0] escalate_en;
prim_lc_sync #(
.NumCopies (2)
) u_prim_lc_sync (
.clk_i,
.rst_ni,
.lc_en_i (lc_escalate_en_i),
.lc_en_o (escalate_en)
);
// SEC_CM: KEY.GLOBAL_ESC
logic escalate;
assign escalate = lc_tx_test_true_loose(escalate_en[0]);
assign hw2reg.status.escalated.d = 1'b1;
assign hw2reg.status.escalated.de = escalate;
// SEC_CM: KEY.LOCAL_ESC
// Aggregate external and internal escalation sources.
// This is used in countermeasures further below (key reset and transaction blocking).
logic local_esc, local_esc_reg;
// This signal only aggregates registered escalation signals and is used for transaction
// blocking further below, which is on a timing-critical path.
assign local_esc_reg = reg2hw.status.escalated.q |
reg2hw.status.init_error.q |
reg2hw.status.bus_integ_error.q;
// This signal aggregates all escalation trigger signals, including the ones that are generated in
// the same cycle such as init_error and bus_integ_error. It is used for countermeasures that are
// not on the critical path (such as clearing the scrambling keys).
assign local_esc = escalate |
init_error |
(|bus_integ_error) |
local_esc_reg;
// Convert registered, local escalation sources to a multibit signal and combine this with
// the incoming escalation enable signal before feeding into the TL-UL gate further below.
lc_tx_t lc_tlul_gate_en;
assign lc_tlul_gate_en = lc_tx_inv(lc_tx_or_hi(escalate_en[1],
lc_tx_bool_to_lc_tx(local_esc_reg)));
///////////////////////
// HW Initialization //
///////////////////////
// A write to the init register reloads the LFSR seed, resets the init counter and
// sets init_q to flag a pending initialization request.
logic init_trig;
assign init_trig = reg2hw.ctrl.init.q & reg2hw.ctrl.init.qe;
logic init_d, init_q, init_done;
assign init_d = (init_done) ? 1'b0 :
(init_trig) ? 1'b1 : init_q;
always_ff @(posedge clk_i or negedge rst_ni) begin : p_init_reg
if(!rst_ni) begin
init_q <= 1'b0;
end else begin
init_q <= init_d;
end
end
// This waits until the scrambling keys are actually valid (this allows the SW to trigger
// key renewal and initialization at the same time).
logic init_req;
logic [AddrWidth-1:0] init_cnt;
logic key_req_pending_d, key_req_pending_q;
assign init_req = init_q & ~key_req_pending_q;
assign init_done = (init_cnt == AddrWidth'(Depth - 1)) & init_req;
// We employ two redundant counters to guard against FI attacks.
// If any of the two is glitched and the two counter states do not agree,
// we trigger an alert.
// SEC_CM: INIT.CTR.REDUN
prim_count #(
.Width(AddrWidth)
) u_prim_count (
.clk_i,
.rst_ni,
.clr_i(init_trig),
.set_i(1'b0),
.set_cnt_i('0),
.incr_en_i(init_req),
.decr_en_i(1'b0),
.step_i(AddrWidth'(1)),
.cnt_o(init_cnt),
.cnt_next_o(),
.err_o(init_error)
);
// Clear this bit on local escalation.
assign hw2reg.status.init_done.d = init_done & ~init_trig & ~local_esc;
assign hw2reg.status.init_done.de = init_done | init_trig | local_esc;
////////////////////////////
// Scrambling Key Request //
////////////////////////////
// The scrambling key and nonce have to be requested from the OTP controller via a req/ack
// protocol. Since the OTP controller works in a different clock domain, we have to synchronize
// the req/ack protocol as described in more details here:
// https://docs.opentitan.org/hw/ip/otp_ctrl/doc/index.html#interfaces-to-sram-and-otbn-scramblers
logic key_req, key_ack;
assign key_req = reg2hw.ctrl.renew_scr_key.q & reg2hw.ctrl.renew_scr_key.qe;
assign key_req_pending_d = (key_req) ? 1'b1 :
(key_ack) ? 1'b0 : key_req_pending_q;
// Clear this bit on local escalation.
assign hw2reg.status.scr_key_valid.d = key_ack & ~key_req & ~local_esc;
assign hw2reg.status.scr_key_valid.de = key_req | key_ack | local_esc;
// Clear this bit on local escalation.
logic key_seed_valid;
assign hw2reg.status.scr_key_seed_valid.d = key_seed_valid & ~local_esc;
assign hw2reg.status.scr_key_seed_valid.de = key_ack | local_esc;
always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
if (!rst_ni) begin
key_req_pending_q <= 1'b0;
// reset case does not use buffered values as the
// reset value will be directly encoded into flop types
key_q <= RndCnstSramKey;
nonce_q <= RndCnstSramNonce;
end else begin
key_req_pending_q <= key_req_pending_d;
if (key_ack) begin
key_q <= key_d;
nonce_q <= nonce_d;
end
// This scraps the keys.
// SEC_CM: KEY.GLOBAL_ESC
// SEC_CM: KEY.LOCAL_ESC
if (local_esc) begin
key_q <= cnst_sram_key;
nonce_q <= cnst_sram_nonce;
end
end
end
prim_sync_reqack_data #(
.Width($bits(otp_ctrl_pkg::sram_otp_key_rsp_t)-1),
.DataSrc2Dst(1'b0)
) u_prim_sync_reqack_data (
.clk_src_i ( clk_i ),
.rst_src_ni ( rst_ni ),
.clk_dst_i ( clk_otp_i ),
.rst_dst_ni ( rst_otp_ni ),
.req_chk_i ( 1'b1 ),
.src_req_i ( key_req_pending_q ),
.src_ack_o ( key_ack ),
.dst_req_o ( sram_otp_key_o.req ),
.dst_ack_i ( sram_otp_key_i.ack ),
.data_i ( {sram_otp_key_i.key,
sram_otp_key_i.nonce,
sram_otp_key_i.seed_valid} ),
.data_o ( {key_d,
nonce_d,
key_seed_valid} )
);
logic unused_csr_sigs;
assign unused_csr_sigs = ^{reg2hw.status.init_done.q,
reg2hw.status.scr_key_seed_valid.q};
////////////////////
// SRAM Execution //
////////////////////
import prim_mubi_pkg::mubi4_t;
import prim_mubi_pkg::MuBi4True;
import prim_mubi_pkg::MuBi4False;
import prim_mubi_pkg::mubi8_test_true_strict;
mubi4_t en_ifetch;
if (InstrExec) begin : gen_instr_ctrl
mubi4_t lc_ifetch_en;
mubi4_t reg_ifetch_en;
// SEC_CM: INSTR.BUS.LC_GATED
assign lc_ifetch_en = lc_to_mubi4(lc_hw_debug_en_i);
// SEC_CM: EXEC.CONFIG.MUBI
assign reg_ifetch_en = mubi4_t'(reg2hw.exec.q);
// SEC_CM: EXEC.INTERSIG.MUBI
assign en_ifetch = (mubi8_test_true_strict(otp_en_sram_ifetch_i)) ? reg_ifetch_en :
lc_ifetch_en;
end else begin : gen_tieoff
assign en_ifetch = MuBi4False;
// tie off unused signals
logic unused_sigs;
assign unused_sigs = ^{lc_hw_debug_en_i,
reg2hw.exec.q,
otp_en_sram_ifetch_i};
end
/////////////////////////
// Initialization LFSR //
/////////////////////////
logic [LfsrWidth-1:0] lfsr_out;
prim_lfsr #(
.LfsrDw ( LfsrWidth ),
.EntropyDw ( LfsrWidth ),
.StateOutDw ( LfsrWidth ),
.DefaultSeed ( RndCnstLfsrSeed ),
.StatePermEn ( 1'b1 ),
.StatePerm ( RndCnstLfsrPerm )
) u_lfsr (
.clk_i,
.rst_ni,
.lfsr_en_i(init_req),
.seed_en_i(init_trig),
.seed_i(nonce_q[NonceWidth +: LfsrWidth]),
.entropy_i('0),
.state_o(lfsr_out)
);
// Compute the correct integrity alongside for the pseudo-random initialization values.
logic [DataWidth - 1 :0] lfsr_out_integ;
tlul_data_integ_enc u_tlul_data_integ_enc (
.data_i(lfsr_out),
.data_intg_o(lfsr_out_integ)
);
////////////////////////////
// SRAM TL-UL Access Gate //
////////////////////////////
logic tl_gate_resp_pending;
tlul_pkg::tl_h2d_t ram_tl_in_gated;
tlul_pkg::tl_d2h_t ram_tl_out_gated;
// SEC_CM: RAM_TL_LC_GATE.FSM.SPARSE
tlul_lc_gate #(
.NumGatesPerDirection(2)
) u_tlul_lc_gate (
.clk_i,
.rst_ni,
.tl_h2d_i(ram_tl_i),
.tl_d2h_o(ram_tl_o),
.tl_h2d_o(ram_tl_in_gated),
.tl_d2h_i(ram_tl_out_gated),
.flush_req_i('0),
.flush_ack_o(),
.resp_pending_o(tl_gate_resp_pending),
.lc_en_i (lc_tlul_gate_en),
.err_o (bus_integ_error[2])
);
/////////////////////////////////
// SRAM with scrambling device //
/////////////////////////////////
logic tlul_req, tlul_gnt, tlul_we;
logic [AddrWidth-1:0] tlul_addr;
logic [DataWidth-1:0] tlul_wdata, tlul_wmask;
logic sram_intg_error, sram_req, sram_gnt, sram_we, sram_rvalid;
logic [AddrWidth-1:0] sram_addr;
logic [DataWidth-1:0] sram_wdata, sram_wmask, sram_rdata;
tlul_adapter_sram #(
.SramAw(AddrWidth),
.SramDw(DataWidth - tlul_pkg::DataIntgWidth),
.Outstanding(2),
.ByteAccess(1),
.CmdIntgCheck(1),
.EnableRspIntgGen(1),
.EnableDataIntgGen(0),
.EnableDataIntgPt(1), // SEC_CM: MEM.INTEGRITY
.SecFifoPtr (1) // SEC_CM: TLUL_FIFO.CTR.REDUN
) u_tlul_adapter_sram (
.clk_i,
.rst_ni,
.tl_i (ram_tl_in_gated),
.tl_o (ram_tl_out_gated),
.en_ifetch_i (en_ifetch),
.req_o (tlul_req),
.req_type_o (),
.gnt_i (tlul_gnt),
.we_o (tlul_we),
.addr_o (tlul_addr),
.wdata_o (tlul_wdata),
.wmask_o (tlul_wmask),
// SEC_CM: BUS.INTEGRITY
.intg_error_o(bus_integ_error[1]),
.rdata_i (sram_rdata),
.rvalid_i (sram_rvalid),
.rerror_i ('0)
);
// Interposing mux logic for initialization with pseudo random data.
assign sram_req = tlul_req | init_req;
assign tlul_gnt = sram_gnt & ~init_req;
assign sram_we = tlul_we | init_req;
assign sram_intg_error = |bus_integ_error[2:1] & ~init_req;
assign sram_addr = (init_req) ? init_cnt : tlul_addr;
assign sram_wdata = (init_req) ? lfsr_out_integ : tlul_wdata;
assign sram_wmask = (init_req) ? {DataWidth{1'b1}} : tlul_wmask;
// The SRAM scrambling wrapper will not accept any transactions while the
// key req is pending or if we have escalated. Note that we're not using
// the scr_key_valid CSR here, such that the SRAM can be used right after
// reset, where the keys are reset to the default netlist constant.
//
// If we have escalated, but there is a pending request in the TL gate, we
// may have a pending read-modify-write transaction in the SRAM adapter. In
// that case we let a write proceed, since the TL gate won't accept any new
// transactions and the SRAM keys have been clobbered already.
logic key_valid;
assign key_valid = (key_req_pending_q) ? 1'b0 :
(reg2hw.status.escalated.q) ? (tl_gate_resp_pending & tlul_we) : 1'b1;
// SEC_CM: MEM.SCRAMBLE, ADDR.SCRAMBLE
prim_ram_1p_scr #(
.Width(DataWidth),
.Depth(Depth),
.EnableParity(0),
.DataBitsPerMask(DataWidth),
.DiffWidth(DataWidth)
) u_prim_ram_1p_scr (
.clk_i,
.rst_ni,
.key_valid_i (key_valid),
.key_i (key_q),
.nonce_i (nonce_q[NonceWidth-1:0]),
.req_i (sram_req),
.intg_error_i(sram_intg_error),
.gnt_o (sram_gnt),
.write_i (sram_we),
.addr_i (sram_addr),
.wdata_i (sram_wdata),
.wmask_i (sram_wmask),
.rdata_o (sram_rdata),
.rvalid_o (sram_rvalid),
.rerror_o ( ),
.raddr_o ( ),
.cfg_i
);
////////////////
// Assertions //
////////////////
`ASSERT_KNOWN(RegsTlOutKnown_A, regs_tl_o)
`ASSERT_KNOWN(RamTlOutKnown_A, ram_tl_o.d_valid)
`ASSERT_KNOWN_IF(RamTlOutPayLoadKnown_A, ram_tl_o, ram_tl_o.d_valid)
`ASSERT_KNOWN(AlertOutKnown_A, alert_tx_o)
`ASSERT_KNOWN(SramOtpKeyKnown_A, sram_otp_key_o)
// Alert assertions for redundant counters.
`ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CntCheck_A,
u_prim_count, alert_tx_o[0])
`ASSERT_PRIM_FSM_ERROR_TRIGGER_ERR(LcGateFsmCheck_A,
u_tlul_lc_gate.u_state_regs, alert_tx_o[0])
// Alert assertions for reg_we onehot check.
`ASSERT_PRIM_REG_WE_ONEHOT_ERROR_TRIGGER_ALERT(RegWeOnehotCheck_A,
u_reg_regs, alert_tx_o[0])
// Alert assertions for redundant counters.
`ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(FifoWptrCheck_A,
u_tlul_adapter_sram.u_rspfifo.gen_normal_fifo.u_fifo_cnt.gen_secure_ptrs.u_wptr,
alert_tx_o[0])
`ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(FifoRptrCheck_A,
u_tlul_adapter_sram.u_rspfifo.gen_normal_fifo.u_fifo_cnt.gen_secure_ptrs.u_rptr,
alert_tx_o[0])
endmodule : sram_ctrl