blob: 747703bc5b953b57d0d0ee783e25361caa8d8a2e [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
`include "prim_assert.sv"
module rom_ctrl
import rom_ctrl_reg_pkg::NumAlerts;
import prim_rom_pkg::rom_cfg_t;
#(
parameter BootRomInitFile = "",
parameter logic [NumAlerts-1:0] AlertAsyncOn = {NumAlerts{1'b1}},
parameter bit [63:0] RndCnstScrNonce = '0,
parameter bit [127:0] RndCnstScrKey = '0,
// Disable all (de)scrambling operation. This disables both the scrambling block and the boot-time
// checker. Don't use this in a real chip, but it's handy for small FPGA targets where we don't
// want to spend area on unused scrambling.
parameter bit SecDisableScrambling = 1'b0
) (
input clk_i,
input rst_ni,
// ROM configuration parameters
input rom_cfg_t rom_cfg_i,
input tlul_pkg::tl_h2d_t rom_tl_i,
output tlul_pkg::tl_d2h_t rom_tl_o,
input tlul_pkg::tl_h2d_t regs_tl_i,
output tlul_pkg::tl_d2h_t regs_tl_o,
// Alerts
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,
// Connections to other blocks
output rom_ctrl_pkg::pwrmgr_data_t pwrmgr_data_o,
output rom_ctrl_pkg::keymgr_data_t keymgr_data_o,
input kmac_pkg::app_rsp_t kmac_data_i,
output kmac_pkg::app_req_t kmac_data_o
);
import rom_ctrl_pkg::*;
import rom_ctrl_reg_pkg::*;
import prim_mubi_pkg::mubi4_t, prim_mubi_pkg::MuBi4True;
import prim_util_pkg::vbits;
`define CLK_WAIT_BOUNDS ##[MIN_CLK_WAIT_CYCLES:MAX_CLK_WAIT_CYCLES]
// ROM_CTRL_ROM_SIZE is auto-generated by regtool and comes from the bus window size, measured in
// bytes of content (i.e. 4 times the number of 32 bit words).
localparam int unsigned RomSizeByte = ROM_CTRL_ROM_SIZE;
localparam int unsigned RomSizeWords = RomSizeByte >> 2;
localparam int unsigned RomIndexWidth = vbits(RomSizeWords);
// DataWidth is normally 39, representing 32 bits of actual data plus 7 ECC check bits. If
// scrambling is disabled ("insecure mode"), we store a raw 32-bit image and generate ECC check
// bits on the fly.
localparam int unsigned DataWidth = SecDisableScrambling ? 32 : 39;
mubi4_t rom_select_bus;
logic [RomIndexWidth-1:0] rom_rom_index, rom_prince_index;
logic rom_req;
logic [DataWidth-1:0] rom_scr_rdata;
logic [DataWidth-1:0] rom_clr_rdata;
logic rom_rvalid;
logic [RomIndexWidth-1:0] bus_rom_rom_index, bus_rom_prince_index;
logic bus_rom_req;
logic bus_rom_gnt;
logic [DataWidth-1:0] bus_rom_rdata;
logic bus_rom_rvalid, bus_rom_rvalid_raw;
logic [RomIndexWidth-1:0] checker_rom_index;
logic checker_rom_req;
logic [DataWidth-1:0] checker_rom_rdata;
logic internal_alert;
// Pack / unpack kmac connection data ========================================
logic [63:0] kmac_rom_data;
logic kmac_rom_rdy;
logic kmac_rom_vld;
logic kmac_rom_last;
logic kmac_done;
logic [255:0] kmac_digest;
logic kmac_err;
if (!SecDisableScrambling) begin : gen_kmac_scramble_enabled
// The usual situation, with scrambling enabled. Collect up output signals for kmac and split up
// the input struct into separate signals.
// Neglecting any first / last block effects, and assuming that ROM_CTRL can always fill the
// KMAC message FIFO while a KMAC round is running, the total processing time for a 32kB ROM is
// calculated as follows:
//
// (Padding Overhead) x (ROM Size) / (Block Size) x (Block Processing Time + KMAC Absorb Time)
//
// ROM_CTRL can only read out one 32 or 39 bit (with ECC) word per cycle, so if we were to zero
// pad this to align with the 64bit KMAC interface, the padding overhead would amount to 2x
// in this equation:
//
// 2 x 32 kByte / (1600 bit - 2x 256bit) x (96 cycles + (1600 bit - 2x 256bit) / 64bit)) =
// 2 x 32 x 1024 x 8bit / 1088bit x (96 cycles + 17 cycles) =
// 2 x 262144 bit / 1088 bit x 113 cycles =
// 2 x 27226.35 cycles
//
// Luckily, the KMAC interface allows to transmit data with a byte enable mask, and only the
// enabled bytes will be packed into the message FIFO. Assuming that the processing is the
// bottleneck, we can thus reduce the overhead of 2x in that equation to 1x or 5/8x if we only
// set 4 or 5 byte enables (4 for 32bit, 5 for 39bit)!
localparam int NumBytes = (DataWidth + 7) / 8;
// SEC_CM: MEM.DIGEST
assign kmac_data_o = '{valid: kmac_rom_vld,
data: kmac_rom_data,
strb: kmac_pkg::MsgStrbW'({NumBytes{1'b1}}),
last: kmac_rom_last};
assign kmac_rom_rdy = kmac_data_i.ready;
assign kmac_done = kmac_data_i.done;
assign kmac_digest = kmac_data_i.digest_share0[255:0] ^ kmac_data_i.digest_share1[255:0];
assign kmac_err = kmac_data_i.error;
logic unused_kmac_digest;
assign unused_kmac_digest = ^{
kmac_data_i.digest_share0[kmac_pkg::AppDigestW-1:256],
kmac_data_i.digest_share1[kmac_pkg::AppDigestW-1:256]
};
end : gen_kmac_scramble_enabled
else begin : gen_kmac_scramble_disabled
// Scrambling is disabled. Stub out all KMAC connections and waive the ignored signals.
assign kmac_data_o = '0;
assign kmac_rom_rdy = 1'b0;
assign kmac_done = 1'b0;
assign kmac_digest = '0;
assign kmac_err = 1'b0;
logic unused_kmac_inputs;
assign unused_kmac_inputs = ^{kmac_data_i};
logic unused_kmac_outputs;
assign unused_kmac_outputs = ^{kmac_rom_vld, kmac_rom_data, kmac_rom_last};
end : gen_kmac_scramble_disabled
// TL interface ==============================================================
tlul_pkg::tl_h2d_t tl_rom_h2d_upstream, tl_rom_h2d_downstream;
tlul_pkg::tl_d2h_t tl_rom_d2h;
logic rom_reg_integrity_error;
rom_ctrl_rom_reg_top u_rom_top (
.clk_i,
.rst_ni,
.tl_i (rom_tl_i),
.tl_o (rom_tl_o),
.tl_win_o (tl_rom_h2d_upstream),
.tl_win_i (tl_rom_d2h),
.intg_err_o (rom_reg_integrity_error), // SEC_CM: BUS.INTEGRITY
.devmode_i (1'b1)
);
// This buffer ensures that when we calculate bus_rom_prince_index by snooping on
// tl_rom_h2d_upstream, we get a value that's buffered from the thing that goes into both the ECC
// check and the addr_o output of u_tl_adapter_rom. That way, an injected 1- or 2-bit fault that
// affects bus_rom_prince_index must either affect the ECC check (causing it to fail) OR it cannot
// affect bus_rom_rom_index (so the address-tweakable scrambling will mean the read probably gets
// garbage).
//
// SEC_CM: CTRL.REDUN
prim_buf #(
.Width($bits(tlul_pkg::tl_h2d_t))
) u_tl_rom_h2d_buf (
.in_i (tl_rom_h2d_upstream),
.out_o (tl_rom_h2d_downstream)
);
// Bus -> ROM adapter ========================================================
logic rom_integrity_error;
tlul_adapter_sram #(
.SramAw(RomIndexWidth),
.SramDw(32),
.Outstanding(2),
.ByteAccess(0),
.ErrOnWrite(1),
.CmdIntgCheck(1),
.EnableRspIntgGen(1),
.EnableDataIntgGen(SecDisableScrambling),
.EnableDataIntgPt(!SecDisableScrambling), // SEC_CM: BUS.INTEGRITY
.SecFifoPtr (1) // SEC_CM: TLUL_FIFO.CTR.REDUN
) u_tl_adapter_rom (
.clk_i,
.rst_ni,
.tl_i (tl_rom_h2d_downstream),
.tl_o (tl_rom_d2h),
.en_ifetch_i (prim_mubi_pkg::MuBi4True),
.req_o (bus_rom_req),
.req_type_o (),
.gnt_i (bus_rom_gnt),
.we_o (),
.addr_o (bus_rom_rom_index),
.wdata_o (),
.wmask_o (),
.intg_error_o (rom_integrity_error),
.rdata_i (bus_rom_rdata),
.rvalid_i (bus_rom_rvalid),
.rerror_i (2'b00)
);
// Snoop on the "upstream" TL transaction to infer the address to pass to the PRINCE cipher.
assign bus_rom_prince_index = (tl_rom_h2d_upstream.a_valid ?
tl_rom_h2d_upstream.a_address[2 +: RomIndexWidth] :
'0);
// Unless there has been an injected fault, bus_rom_prince_index and bus_rom_rom_index should have
// the same value.
`ASSERT(BusRomIndicesMatch_A, bus_rom_prince_index == bus_rom_rom_index)
// The mux ===================================================================
logic mux_alert;
rom_ctrl_mux #(
.AW (RomIndexWidth),
.DW (DataWidth)
) u_mux (
.clk_i,
.rst_ni,
.sel_bus_i (rom_select_bus),
.bus_rom_addr_i (bus_rom_rom_index),
.bus_prince_addr_i (bus_rom_prince_index),
.bus_req_i (bus_rom_req),
.bus_gnt_o (bus_rom_gnt),
.bus_rdata_o (bus_rom_rdata),
.bus_rvalid_o (bus_rom_rvalid_raw),
.chk_addr_i (checker_rom_index),
.chk_req_i (checker_rom_req),
.chk_rdata_o (checker_rom_rdata),
.rom_rom_addr_o (rom_rom_index),
.rom_prince_addr_o (rom_prince_index),
.rom_req_o (rom_req),
.rom_scr_rdata_i (rom_scr_rdata),
.rom_clr_rdata_i (rom_clr_rdata),
.rom_rvalid_i (rom_rvalid),
.alert_o (mux_alert)
);
// Squash all responses from the ROM to the bus if there's an internal integrity error from the
// checker FSM or the mux. This avoids having to handle awkward corner cases in the mux: if
// something looks bad, we'll complain and hang the bus transaction.
//
// Note that the two signals that go into internal_alert are both sticky. The mux explicitly
// latches its alert_o output and the checker FSM jumps to an invalid scrap state when it sees an
// error which, in turn, sets checker_alert.
//
// SEC_CM: BUS.LOCAL_ESC
assign bus_rom_rvalid = bus_rom_rvalid_raw & !internal_alert;
// The ROM itself ============================================================
if (!SecDisableScrambling) begin : gen_rom_scramble_enabled
// SEC_CM: MEM.SCRAMBLE
rom_ctrl_scrambled_rom #(
.MemInitFile (BootRomInitFile),
.Width (DataWidth),
.Depth (RomSizeWords),
.ScrNonce (RndCnstScrNonce),
.ScrKey (RndCnstScrKey)
) u_rom (
.clk_i,
.rst_ni,
.req_i (rom_req),
.rom_addr_i (rom_rom_index),
.prince_addr_i (rom_prince_index),
.rvalid_o (rom_rvalid),
.scr_rdata_o (rom_scr_rdata),
.clr_rdata_o (rom_clr_rdata),
.cfg_i (rom_cfg_i)
);
end : gen_rom_scramble_enabled
else begin : gen_rom_scramble_disabled
// If scrambling is disabled then instantiate a normal ROM primitive (no PRINCE cipher etc.).
// Note that this "raw memory" doesn't have ECC bits either.
prim_rom_adv #(
.Width (DataWidth),
.Depth (RomSizeWords),
.MemInitFile (BootRomInitFile)
) u_rom (
.clk_i,
.rst_ni,
.req_i (rom_req),
.addr_i (rom_rom_index),
.rvalid_o (rom_rvalid),
.rdata_o (rom_scr_rdata),
.cfg_i (rom_cfg_i)
);
// There's no scrambling, so "scrambled" and "clear" rdata are equal.
assign rom_clr_rdata = rom_scr_rdata;
// Since we're not generating a keystream, we don't use the rom_prince_index at all
logic unused_prince_index;
assign unused_prince_index = ^rom_prince_index;
end : gen_rom_scramble_disabled
// Zero expand checker rdata to pass to KMAC
assign kmac_rom_data = {{64-DataWidth{1'b0}}, checker_rom_rdata};
// Register block ============================================================
rom_ctrl_regs_reg2hw_t reg2hw;
rom_ctrl_regs_hw2reg_t hw2reg;
logic reg_integrity_error;
rom_ctrl_regs_reg_top u_reg_regs (
.clk_i,
.rst_ni,
.tl_i (regs_tl_i),
.tl_o (regs_tl_o),
.reg2hw (reg2hw),
.hw2reg (hw2reg),
.intg_err_o (reg_integrity_error), // SEC_CM: BUS.INTEGRITY
.devmode_i (1'b1)
);
// The checker FSM ===========================================================
logic [255:0] digest_q, exp_digest_q;
logic [255:0] digest_d;
logic digest_de;
logic [31:0] exp_digest_word_d;
logic exp_digest_de;
logic [2:0] exp_digest_idx;
logic checker_alert;
if (!SecDisableScrambling) begin : gen_fsm_scramble_enabled
rom_ctrl_fsm #(
.RomDepth (RomSizeWords),
.TopCount (8)
) u_checker_fsm (
.clk_i,
.rst_ni,
.digest_i (digest_q),
.exp_digest_i (exp_digest_q),
.digest_o (digest_d),
.digest_vld_o (digest_de),
.exp_digest_o (exp_digest_word_d),
.exp_digest_vld_o (exp_digest_de),
.exp_digest_idx_o (exp_digest_idx),
.pwrmgr_data_o (pwrmgr_data_o),
.keymgr_data_o (keymgr_data_o),
.kmac_rom_rdy_i (kmac_rom_rdy),
.kmac_rom_vld_o (kmac_rom_vld),
.kmac_rom_last_o (kmac_rom_last),
.kmac_done_i (kmac_done),
.kmac_digest_i (kmac_digest),
.kmac_err_i (kmac_err),
.rom_select_bus_o (rom_select_bus),
.rom_addr_o (checker_rom_index),
.rom_req_o (checker_rom_req),
.rom_data_i (checker_rom_rdata[31:0]),
.alert_o (checker_alert)
);
end : gen_fsm_scramble_enabled
else begin : gen_fsm_scramble_disabled
// If scrambling is disabled, there's no checker FSM.
assign digest_d = '0;
assign digest_de = 1'b0;
assign exp_digest_word_d = '0;
assign exp_digest_de = 1'b0;
assign exp_digest_idx = '0;
assign pwrmgr_data_o = PWRMGR_DATA_DEFAULT;
// Send something other than '1 or '0 because the key manager has an "all ones" and an "all
// zeros" check.
assign keymgr_data_o = '{data: {128{2'b10}}, valid: 1'b1};
assign kmac_rom_vld = 1'b0;
assign kmac_rom_last = 1'b0;
// Always grant access to the bus. Setting this to a constant should mean the mux gets
// synthesized away completely.
assign rom_select_bus = MuBi4True;
assign checker_rom_index = '0;
assign checker_rom_req = 1'b0;
assign checker_alert = 1'b0;
logic unused_fsm_inputs;
assign unused_fsm_inputs = ^{kmac_rom_rdy, kmac_done, kmac_digest, digest_q, exp_digest_q};
end : gen_fsm_scramble_disabled
// Register data =============================================================
// DIGEST and EXP_DIGEST registers
// Repack signals to convert between the view expected by rom_ctrl_reg_pkg for CSRs and the view
// expected by rom_ctrl_fsm. Register 0 of a multi-reg appears as the low bits of the packed data.
for (genvar i = 0; i < 8; i++) begin: gen_csr_digest
localparam int unsigned TopBitInt = 32 * i + 31;
localparam bit [7:0] TopBit = TopBitInt[7:0];
assign hw2reg.digest[i].d = digest_d[TopBit -: 32];
assign hw2reg.digest[i].de = digest_de;
assign hw2reg.exp_digest[i].d = exp_digest_word_d;
assign hw2reg.exp_digest[i].de = exp_digest_de && (i[2:0] == exp_digest_idx);
assign digest_q[TopBit -: 32] = reg2hw.digest[i].q;
assign exp_digest_q[TopBit -: 32] = reg2hw.exp_digest[i].q;
end
logic bus_integrity_error;
assign bus_integrity_error = rom_reg_integrity_error | rom_integrity_error | reg_integrity_error;
assign internal_alert = checker_alert | mux_alert;
// FATAL_ALERT_CAUSE register
assign hw2reg.fatal_alert_cause.checker_error.d = internal_alert;
assign hw2reg.fatal_alert_cause.checker_error.de = internal_alert;
assign hw2reg.fatal_alert_cause.integrity_error.d = bus_integrity_error;
assign hw2reg.fatal_alert_cause.integrity_error.de = bus_integrity_error;
// Alert generation ==========================================================
logic [NumAlerts-1:0] alert_test;
assign alert_test[AlertFatal] = reg2hw.alert_test.q &
reg2hw.alert_test.qe;
logic [NumAlerts-1:0] alerts;
assign alerts[AlertFatal] = bus_integrity_error | checker_alert | mux_alert;
for (genvar i = 0; i < NumAlerts; i++) begin: gen_alert_tx
prim_alert_sender #(
.AsyncOn(AlertAsyncOn[i]),
.IsFatal(i == AlertFatal)
) u_alert_sender (
.clk_i,
.rst_ni,
.alert_test_i ( alert_test[i] ),
.alert_req_i ( alerts[i] ),
.alert_ack_o ( ),
.alert_state_o ( ),
.alert_rx_i ( alert_rx_i[i] ),
.alert_tx_o ( alert_tx_o[i] )
);
end
// Asserts ===================================================================
//
// "ROM" TL interface: The d_valid and a_ready signals should be unconditionally defined. The
// other signals in rom_tl_o (which are the other D channel signals) should be defined if d_valid.
`ASSERT_KNOWN(RomTlODValidKnown_A, rom_tl_o.d_valid)
`ASSERT_KNOWN(RomTlOAReadyKnown_A, rom_tl_o.a_ready)
`ASSERT_KNOWN_IF(RomTlODDataKnown_A, rom_tl_o, rom_tl_o.d_valid)
// "regs" TL interface: The d_valid and a_ready signals should be unconditionally defined. The
// other signals in rom_tl_o (which are the other D channel signals) should be defined if d_valid.
`ASSERT_KNOWN(RegsTlODValidKnown_A, regs_tl_o.d_valid)
`ASSERT_KNOWN(RegsTlOAReadyKnown_A, regs_tl_o.a_ready)
`ASSERT_KNOWN_IF(RegsTlODDataKnown_A, regs_tl_o, regs_tl_o.d_valid)
// The assert_tx_o output should have a known value when out of reset
`ASSERT_KNOWN(AlertTxOKnown_A, alert_tx_o)
// Assertions to check that we've wired up our alert bits correctly
if (!SecDisableScrambling) begin : gen_asserts_with_scrambling
`ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(CompareFsmAlert_A,
gen_fsm_scramble_enabled.
u_checker_fsm.u_compare.u_state_regs,
alert_tx_o[AlertFatal])
`ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(CheckerFsmAlert_A,
gen_fsm_scramble_enabled.
u_checker_fsm.u_state_regs,
alert_tx_o[AlertFatal])
`ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CompareAddrCtrCheck_A,
gen_fsm_scramble_enabled.
u_checker_fsm.u_compare.u_prim_count_addr,
alert_tx_o[AlertFatal])
end
// The pwrmgr_data_o output (the "done" and "good" signals) should have a known value when out of
// reset. (In theory, the "good" signal could be unknown when !done, but the stronger and simpler
// assertion is also true, so we use that)
`ASSERT_KNOWN(PwrmgrDataOKnown_A, pwrmgr_data_o)
// The valid signal for keymgr_data_o should always be known when out of reset. The rest of the
// struct (a data signal) should be known whenever the valid signal is true.
`ASSERT_KNOWN(KeymgrDataOValidKnown_A, keymgr_data_o.valid)
`ASSERT_KNOWN_IF(KeymgrDataODataKnown_A, keymgr_data_o, keymgr_data_o.valid)
// The valid signal for kmac_data_o should always be known when out of reset. The rest of the
// struct (data, strb and last) should be known whenever the valid signal is true.
`ASSERT_KNOWN(KmacDataOValidKnown_A, kmac_data_o.valid)
`ASSERT_KNOWN_IF(KmacDataODataKnown_A, kmac_data_o, kmac_data_o.valid)
// Check that pwrmgr_data_o.good is stable when kmac_data_o.valid is asserted
`ASSERT(StabilityChkKmac_A, kmac_data_o.valid && $past(kmac_data_o.valid)
|-> $stable(pwrmgr_data_o.good))
// Check that pwrmgr_data_o.good is stable when keymgr_data_o.valid is asserted
`ASSERT(StabilityChkkeymgr_A, keymgr_data_o.valid && $past(keymgr_data_o.valid)
|-> $stable(pwrmgr_data_o.good))
// Check that pwrmgr_data_o.done is never de-asserted once asserted
`ASSERT(PwrmgrDataChk_A, $rose(pwrmgr_data_o.done == prim_mubi_pkg::MuBi4True) |->
always !$fell(pwrmgr_data_o.done == prim_mubi_pkg::MuBi4True),
clk_i, !rst_ni || internal_alert)
// Check that keymgr_data_o.valid is never de-asserted once asserted
`ASSERT(KeymgrValidChk_A, $rose(keymgr_data_o.valid) |-> always !$fell(keymgr_data_o.valid),
clk_i, !rst_ni || internal_alert)
// Check that rom_tl_o.d_valid is not asserted unless pwrmgr_data_o.done is asseterd.
// This check ensures that all tl accesses are blocked until rom check is completed. You might
// think we could check for a_ready, but that doesn't work because the TL to SRAM adapter has a
// 1-entry cache that accepts the transaction (but doesn't reply)
`ASSERT(TlAccessChk_A,
(pwrmgr_data_o.done == prim_mubi_pkg::MuBi4False) |->
(!rom_tl_o.d_valid || (rom_tl_o.d_valid && rom_tl_o.d_error)))
// Check that whenever there is an alert triggered and FSM state is Invalid, there is no response
// to read requests.
if (!SecDisableScrambling) begin : gen_fsm_scramble_enabled_asserts
`ASSERT(BusLocalEscChk_A,
(gen_fsm_scramble_enabled.u_checker_fsm.state_d == rom_ctrl_pkg::Invalid)
|-> always(!bus_rom_rvalid))
end
// Alert assertions for reg_we onehot check
`ASSERT_PRIM_REG_WE_ONEHOT_ERROR_TRIGGER_ALERT(RegWeOnehotCheck_A,
u_reg_regs, alert_tx_o[AlertFatal])
// Alert assertions for redundant counters.
`ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(FifoWptrCheck_A,
u_tl_adapter_rom.u_rspfifo.gen_normal_fifo.u_fifo_cnt.gen_secure_ptrs.u_wptr,
alert_tx_o[AlertFatal])
`ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(FifoRptrCheck_A,
u_tl_adapter_rom.u_rspfifo.gen_normal_fifo.u_fifo_cnt.gen_secure_ptrs.u_rptr,
alert_tx_o[AlertFatal])
endmodule