| // 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) |
| input lc_ctrl_pkg::lc_tx_t lc_escalate_en_i, |
| input lc_ctrl_pkg::lc_tx_t lc_hw_debug_en_i, |
| // Otp configuration for sram execution |
| input otp_ctrl_pkg::otp_en_t otp_en_sram_ifetch_i, |
| // Key request to OTP (running on clk_fixed) |
| 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 |
| ); |
| |
| // 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) |
| |
| ////////////////////////// |
| // CSR Node and Mapping // |
| ////////////////////////// |
| |
| sram_ctrl_regs_reg2hw_t reg2hw; |
| sram_ctrl_regs_hw2reg_t hw2reg; |
| |
| sram_ctrl_regs_reg_top u_reg_regs ( |
| .clk_i, |
| .rst_ni, |
| .tl_i (regs_tl_i), |
| .tl_o (regs_tl_o), |
| .reg2hw, |
| .hw2reg, |
| .intg_err_o(), |
| .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; |
| |
| logic bus_integ_error; |
| 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; |
| |
| 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 ( bus_integ_error | init_error ), |
| .alert_ack_o ( ), |
| .alert_state_o ( ), |
| .alert_rx_i ( alert_rx_i[0] ), |
| .alert_tx_o ( alert_tx_o[0] ) |
| ); |
| |
| ///////////////////////// |
| // Escalation Triggers // |
| ///////////////////////// |
| |
| lc_ctrl_pkg::lc_tx_t escalate_en; |
| prim_lc_sync #( |
| .NumCopies (1) |
| ) u_prim_lc_sync ( |
| .clk_i, |
| .rst_ni, |
| .lc_en_i (lc_escalate_en_i), |
| .lc_en_o (escalate_en) |
| ); |
| |
| logic escalate; |
| assign escalate = (escalate_en != lc_ctrl_pkg::Off); |
| assign hw2reg.status.escalated.d = 1'b1; |
| assign hw2reg.status.escalated.de = escalate; |
| |
| // Aggregate external and internal escalation sources. This is used on countermeasures further |
| // below (key reset, transaction blocking and scrambling nonce reversal). |
| logic local_esc; |
| assign local_esc = escalate | |
| init_error | |
| bus_integ_error | |
| reg2hw.status.escalated.q | |
| reg2hw.status.init_error.q | |
| reg2hw.status.bus_integ_error.q; |
| |
| /////////////////////// |
| // 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; |
| |
| // 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. |
| logic [1:0] init_req, init_done, init_q; |
| logic [1:0][AddrWidth-1:0] init_cnt_q; |
| for (genvar k = 0; k < 2; k++) begin : gen_double_cnt |
| |
| // These size_only buffers are instantiated in order to prevent |
| // optimization / merging of the two counters. |
| logic init_trig_buf; |
| prim_buf u_prim_buf_trig ( |
| .in_i(init_trig), |
| .out_o(init_trig_buf) |
| ); |
| |
| // This waits until the scrambling keys are actually valid (this allows the SW to trigger |
| // key renewal and initialization at the same time). |
| assign init_req[k] = init_q[k] & reg2hw.status.scr_key_valid.q; |
| assign init_done[k] = (init_cnt_q[k] == AddrWidth'(Depth - 1)) & init_req[k]; |
| |
| logic init_d; |
| assign init_d = (init_done[k]) ? 1'b0 : |
| (init_trig_buf) ? 1'b1 : init_q[k]; |
| |
| logic [AddrWidth-1:0] init_cnt_d; |
| assign init_cnt_d = (init_trig_buf) ? '0 : |
| (init_req[k]) ? init_cnt_q[k] + 1'b1 : init_cnt_q[k]; |
| |
| prim_flop #( |
| .Width(1+AddrWidth) |
| ) u_prim_flop_cnt ( |
| .clk_i, |
| .rst_ni, |
| .d_i({init_d, init_cnt_d}), |
| .q_o({init_q[k], init_cnt_q[k]}) |
| ); |
| end |
| |
| // Clear this bit on local escalation. |
| assign hw2reg.status.init_done.d = init_done[0] & ~init_trig & ~local_esc; |
| assign hw2reg.status.init_done.de = init_done[0] | init_trig | local_esc; |
| |
| // Check whether counter is glitched into an invalid state |
| assign init_error = {init_q[0], init_cnt_q[0]} != |
| {init_q[1], init_cnt_q[1]}; |
| |
| //////////////////////////// |
| // 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; |
| logic key_req_pending_d, key_req_pending_q; |
| 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; |
| |
| // 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 key_valid_q here, such that the SRAM can be used |
| // right after reset, where the keys are reset to the default netlist constant. |
| logic key_valid; |
| assign key_valid = ~(key_req_pending_q | reg2hw.status.escalated.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; |
| 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. |
| if (local_esc) begin |
| key_q <= RndCnstSramKey; |
| nonce_q <= RndCnstSramNonce; |
| 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 // |
| //////////////////// |
| |
| tlul_pkg::tl_instr_en_e en_ifetch; |
| if (InstrExec) begin : gen_instr_ctrl |
| tlul_pkg::tl_instr_en_e lc_ifetch_en; |
| tlul_pkg::tl_instr_en_e reg_ifetch_en; |
| assign lc_ifetch_en = (lc_hw_debug_en_i == lc_ctrl_pkg::On) ? tlul_pkg::InstrEn : |
| tlul_pkg::InstrDis; |
| assign reg_ifetch_en = tlul_pkg::tl_instr_en_e'(reg2hw.exec.q); |
| assign en_ifetch = (otp_en_sram_ifetch_i == otp_ctrl_pkg::Enabled) ? reg_ifetch_en : |
| lc_ifetch_en; |
| end else begin : gen_tieoff |
| assign en_ifetch = tlul_pkg::InstrDis; |
| |
| // 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[0]), |
| .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 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) |
| ) u_tlul_adapter_sram ( |
| .clk_i, |
| .rst_ni, |
| .tl_i (ram_tl_i), |
| .tl_o (ram_tl_o), |
| .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), |
| .intg_error_o(bus_integ_error), |
| .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[0]; |
| assign tlul_gnt = sram_gnt & ~init_req[0]; |
| assign sram_we = tlul_we | init_req[0]; |
| assign sram_intg_error = local_esc & ~init_req[0]; |
| assign sram_addr = (init_req[0]) ? init_cnt_q[0] : tlul_addr; |
| assign sram_wdata = (init_req[0]) ? lfsr_out_integ : tlul_wdata; |
| assign sram_wmask = (init_req[0]) ? {DataWidth{1'b1}} : tlul_wmask; |
| |
| 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) |
| `ASSERT_KNOWN(AlertOutKnown_A, alert_tx_o) |
| `ASSERT_KNOWN(SramOtpKeyKnown_A, sram_otp_key_o) |
| |
| endmodule : sram_ctrl |