blob: 31d6839485e64d457a6c4dd095e6695a59398d37 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
/**
* 32b General Purpose Register File (GPRs) with integrity code detecting triple bit errors.
*
* This wraps two implementations, one for FPGA (otbn_rf_base_fpga)
* implementation the other for ASIC (otbn_rf_base_ff).
*
* Both reads and writes use a 2 signal protocol: An _en signal indicates intent to do
* a read or write operation, a _commit signals the operation should proceed. A _commit without _en
* is permissible and means no operation is performed.
*
* This is used to prevent combinational loops in the error handling logic in the controller.
*
* Integrity protection uses an inverted (39, 32) Hsaio code providing a Hamming distance of 4.
*
* `wr_data_no_intg_i` supplies data that requires integrity calulation and `wr_data_intg_i`
* supplies data that comes with integrity. `wr_data_intg_sel_i` is asserted to select the data with
* integrity for the write, otherwise integrity is calculated separately from `wr_data_i`.
*
* Features:
* - 2 read ports
* - 1 write port
* - special purpose stack on a single register (localparam `CallStackRegIndex`)
* for use as a call stack
* - triple error detection
*/
module otbn_rf_base
import otbn_pkg::*;
#(
// Register file implementation selection, see otbn_pkg.sv.
parameter regfile_e RegFile = RegFileFF
)(
input logic clk_i,
input logic rst_ni,
input logic state_reset_i,
input logic sec_wipe_stack_reset_i,
input logic [4:0] wr_addr_i,
input logic wr_en_i,
input logic [31:0] wr_data_no_intg_i,
input logic [BaseIntgWidth-1:0] wr_data_intg_i,
input logic wr_data_intg_sel_i,
input logic wr_commit_i,
input logic [4:0] rd_addr_a_i,
input logic rd_en_a_i,
output logic [BaseIntgWidth-1:0] rd_data_a_intg_o,
input logic [4:0] rd_addr_b_i,
input logic rd_en_b_i,
output logic [BaseIntgWidth-1:0] rd_data_b_intg_o,
input logic rd_commit_i,
output logic call_stack_sw_err_o,
output logic call_stack_hw_err_o,
output logic intg_err_o,
output logic spurious_we_err_o
);
localparam int unsigned CallStackRegIndex = 1;
localparam int unsigned CallStackDepth = 8;
logic [BaseIntgWidth-1:0] wr_data_intg_mux_out, wr_data_intg_calc;
logic [BaseIntgWidth-1:0] rd_data_a_raw_intg, rd_data_b_raw_intg;
logic [1:0] rd_data_a_err, rd_data_b_err;
// The stack implementation is shared between FF and FPGA implementations,
// actual register register file differs between FF and FPGA implementations.
// Pass through signals to chosen register file, diverting any reads/writes to
// register CallStatckRegIndex to the stack.
logic wr_en_masked;
logic pop_stack_a;
logic pop_stack_b;
logic pop_stack_reqd;
logic pop_stack;
logic pop_stack_a_err;
logic pop_stack_b_err;
logic push_stack_reqd;
logic push_stack;
logic push_stack_err;
logic stack_full;
logic [BaseIntgWidth-1:0] stack_data_intg;
logic stack_data_valid;
logic state_reset;
assign state_reset = state_reset_i | sec_wipe_stack_reset_i;
assign pop_stack_a = rd_en_a_i & (rd_addr_a_i == CallStackRegIndex[4:0]);
assign pop_stack_b = rd_en_b_i & (rd_addr_b_i == CallStackRegIndex[4:0]);
// pop_stack_reqd indicates a call stack pop is requested and pop_stack commands it to happen.
assign pop_stack_reqd = (pop_stack_a | pop_stack_b);
assign pop_stack = rd_commit_i & pop_stack_reqd;
// Separate error signals for call stack pop for a and b read ports are required to determine if
// an integrity error is valid or not.
assign pop_stack_a_err = pop_stack_a & ~stack_data_valid;
assign pop_stack_b_err = pop_stack_b & ~stack_data_valid;
// push_stack_reqd indicates a call stack push is requested and push_stack commands it to happen.
assign push_stack_reqd = wr_en_i & (wr_addr_i == CallStackRegIndex[4:0]);
assign push_stack = wr_commit_i & push_stack_reqd;
// Simultaneous push and pop doesn't cause an error when the stack is full (pop ordered before
// push).
assign push_stack_err = push_stack_reqd & stack_full & ~pop_stack_reqd;
assign call_stack_sw_err_o = pop_stack_a_err | pop_stack_b_err | push_stack_err;
// Prevent any write to the stack register from going to the register file,
// all other committed writes are passed straight through
assign wr_en_masked = wr_en_i & wr_commit_i & ~push_stack;
// SEC_CM: CALL_STACK.ADDR.INTEGRITY
// Ignore read data from the register file if reading from the stack register,
// otherwise pass data through from register file.
assign rd_data_a_intg_o = pop_stack_a ? stack_data_intg : rd_data_a_raw_intg;
assign rd_data_b_intg_o = pop_stack_b ? stack_data_intg : rd_data_b_raw_intg;
prim_secded_inv_39_32_enc u_wr_data_intg_enc (
.data_i(wr_data_no_intg_i),
.data_o(wr_data_intg_calc)
);
// New data can have its integrity from an external source or the integrity can be calculated here
assign wr_data_intg_mux_out = wr_data_intg_sel_i ? wr_data_intg_i : wr_data_intg_calc;
otbn_stack #(
// SEC_CM: CALL_STACK.ADDR.INTEGRITY
.StackWidth(39),
.StackDepth(CallStackDepth)
) u_call_stack (
.clk_i,
.rst_ni,
.full_o (stack_full),
.cnt_err_o (call_stack_hw_err_o),
.clear_i (state_reset),
.push_i (push_stack),
.push_data_i (wr_data_intg_mux_out),
.pop_i (pop_stack),
.top_data_o (stack_data_intg),
.top_valid_o (stack_data_valid),
.stack_wr_idx_o(),
.stack_write_o (),
.stack_rd_idx_o(),
.stack_read_o (),
.next_top_data_o (),
.next_top_valid_o()
);
if (RegFile == RegFileFF) begin : gen_rf_base_ff
otbn_rf_base_ff #(
.WordZeroVal(prim_secded_pkg::SecdedInv3932ZeroWord)
) u_otbn_rf_base_inner (
.clk_i,
.rst_ni,
.wr_addr_i,
.wr_en_i (wr_en_masked),
.wr_data_i(wr_data_intg_mux_out),
.rd_addr_a_i,
.rd_data_a_o(rd_data_a_raw_intg),
.rd_addr_b_i,
.rd_data_b_o(rd_data_b_raw_intg),
.we_err_o(spurious_we_err_o)
);
end else if (RegFile == RegFileFPGA) begin : gen_rf_base_fpga
otbn_rf_base_fpga #(
.WordZeroVal(prim_secded_pkg::SecdedInv3932ZeroWord)
) u_otbn_rf_base_inner (
.clk_i,
.rst_ni,
.wr_addr_i,
.wr_en_i (wr_en_masked),
.wr_data_i(wr_data_intg_mux_out),
.rd_addr_a_i,
.rd_data_a_o(rd_data_a_raw_intg),
.rd_addr_b_i,
.rd_data_b_o(rd_data_b_raw_intg),
.we_err_o(spurious_we_err_o)
);
end
// SEC_CM: RF_BASE.DATA_REG_SW.INTEGRITY
// Integrity decoders used to detect errors only, corrections (`syndrome_o`/`d_o`) are ignored
prim_secded_inv_39_32_dec u_rd_data_a_intg_dec (
.data_i (rd_data_a_intg_o),
.data_o (),
.syndrome_o(),
.err_o (rd_data_a_err)
);
prim_secded_inv_39_32_dec u_rd_data_b_intg_dec (
.data_i (rd_data_b_intg_o),
.data_o (),
.syndrome_o(),
.err_o (rd_data_b_err)
);
// Suppress integrity error where the relevant read port saw a call stack pop error (so both
// integrity and data are invalid).
assign intg_err_o = (|rd_data_a_err & rd_en_a_i & ~pop_stack_a_err) |
(|rd_data_b_err & rd_en_b_i & ~pop_stack_b_err);
// Make sure we're not outputting X. This indicates that something went wrong during the initial
// secure wipe.
`ASSERT(OtbnRfBaseRdAKnown, rd_en_a_i && !pop_stack_a |-> !$isunknown(rd_data_a_raw_intg))
`ASSERT(OtbnRfBaseRdBKnown, rd_en_b_i && !pop_stack_b |-> !$isunknown(rd_data_b_raw_intg))
endmodule