blob: 834f3b61aa1c67e17259932d2bae9c32b55696f9 [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"
/**
* OpenTitan Big Number Accelerator (OTBN)
*/
module otbn
import prim_alert_pkg::*;
import otbn_pkg::*;
import otbn_reg_pkg::*;
#(
parameter bit Stub = 1'b0,
parameter regfile_e RegFile = RegFileFF,
parameter logic [NumAlerts-1:0] AlertAsyncOn = {NumAlerts{1'b1}},
// Default seed and permutation for URND LFSR
parameter urnd_lfsr_seed_t RndCnstUrndLfsrSeed = RndCnstUrndLfsrSeedDefault,
parameter urnd_chunk_lfsr_perm_t RndCnstUrndChunkLfsrPerm = RndCnstUrndChunkLfsrPermDefault,
// Default seed and nonce for scrambling
parameter otp_ctrl_pkg::otbn_key_t RndCnstOtbnKey = RndCnstOtbnKeyDefault,
parameter otp_ctrl_pkg::otbn_nonce_t RndCnstOtbnNonce = RndCnstOtbnNonceDefault
) (
input clk_i,
input rst_ni,
input tlul_pkg::tl_h2d_t tl_i,
output tlul_pkg::tl_d2h_t tl_o,
// Inter-module signals
output logic idle_o,
output logic idle_otp_o,
// Interrupts
output logic intr_done_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,
// Lifecycle interface
input lc_ctrl_pkg::lc_tx_t lc_escalate_en_i,
// Memory configuration
input prim_ram_1p_pkg::ram_1p_cfg_t ram_cfg_i,
// EDN clock and interface
input clk_edn_i,
input rst_edn_ni,
output edn_pkg::edn_req_t edn_rnd_o,
input edn_pkg::edn_rsp_t edn_rnd_i,
output edn_pkg::edn_req_t edn_urnd_o,
input edn_pkg::edn_rsp_t edn_urnd_i,
// Key request to OTP (running on clk_fixed)
input clk_otp_i,
input rst_otp_ni,
output otp_ctrl_pkg::otbn_otp_key_req_t otbn_otp_key_o,
input otp_ctrl_pkg::otbn_otp_key_rsp_t otbn_otp_key_i
);
import prim_util_pkg::vbits;
logic rst_n;
// hold module in reset permanently when stubbing
if (Stub) begin : gen_stub_otbn
assign rst_n = 1'b0;
end else begin : gen_real_otbn
assign rst_n = rst_ni;
end
// The OTBN_*_SIZE parameters are auto-generated by regtool and come from the
// bus window sizes; they are given in bytes and must be powers of two.
localparam int ImemSizeByte = int'(otbn_reg_pkg::OTBN_IMEM_SIZE);
localparam int DmemSizeByte = int'(otbn_reg_pkg::OTBN_DMEM_SIZE);
localparam int ImemAddrWidth = vbits(ImemSizeByte);
localparam int DmemAddrWidth = vbits(DmemSizeByte);
`ASSERT_INIT(ImemSizePowerOfTwo, 2**ImemAddrWidth == ImemSizeByte)
`ASSERT_INIT(DmemSizePowerOfTwo, 2**DmemAddrWidth == DmemSizeByte)
logic start_d, start_q;
logic busy_execute_d, busy_execute_q;
logic done;
logic illegal_bus_access_d, illegal_bus_access_q;
err_bits_t err_bits;
logic [ImemAddrWidth-1:0] start_addr;
otbn_reg2hw_t reg2hw;
otbn_hw2reg_t hw2reg;
// Bus device windows, as specified in otbn.hjson
typedef enum logic {
TlWinImem = 1'b0,
TlWinDmem = 1'b1
} tl_win_e;
tlul_pkg::tl_h2d_t tl_win_h2d [2];
tlul_pkg::tl_d2h_t tl_win_d2h [2];
// Inter-module signals ======================================================
// TODO: Use STATUS == IDLE here.
assign idle_o = ~busy_execute_q;
// TODO: These two signals aren't technically in the same clock domain. Sort out how we do the
// signalling properly.
assign idle_otp_o = idle_o;
// Lifecycle ==================================================================
lc_ctrl_pkg::lc_tx_t lc_escalate_en;
prim_lc_sync #(
.NumCopies(1)
) u_lc_escalate_en_sync (
.clk_i,
.rst_ni,
.lc_en_i(lc_escalate_en_i),
.lc_en_o(lc_escalate_en)
);
// Reduce the life cycle escalation signal to a single bit to be used within this cycle.
logic lifecycle_escalation;
assign lifecycle_escalation = lc_escalate_en != lc_ctrl_pkg::Off;
// Interrupts ================================================================
prim_intr_hw #(
.Width(1)
) u_intr_hw_done (
.clk_i,
.rst_ni (rst_n),
.event_intr_i (done),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.q),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.qe),
.reg2hw_intr_state_q_i (reg2hw.intr_state.q),
.hw2reg_intr_state_de_o (hw2reg.intr_state.de),
.hw2reg_intr_state_d_o (hw2reg.intr_state.d),
.intr_o (intr_done_o)
);
// Instruction Memory (IMEM) =================================================
localparam int ImemSizeWords = ImemSizeByte / 4;
localparam int ImemIndexWidth = vbits(ImemSizeWords);
// Access select to IMEM: core (1), or bus (0)
logic imem_access_core;
logic imem_req;
logic imem_write;
logic [ImemIndexWidth-1:0] imem_index;
logic [38:0] imem_wdata;
logic [38:0] imem_wmask;
logic [38:0] imem_rdata;
logic imem_rvalid;
logic [1:0] imem_rerror_vec;
logic imem_rerror;
logic imem_illegal_bus_access;
logic imem_req_core;
logic imem_write_core;
logic [ImemIndexWidth-1:0] imem_index_core;
logic [31:0] imem_wdata_core;
logic [31:0] imem_rdata_core;
logic imem_rvalid_core;
logic imem_rerror_core;
logic imem_req_bus;
logic imem_dummy_response_q, imem_dummy_response_d;
logic imem_write_bus;
logic [ImemIndexWidth-1:0] imem_index_bus;
logic [38:0] imem_wdata_bus;
logic [38:0] imem_wmask_bus;
logic [38:0] imem_rdata_bus;
logic imem_rvalid_bus;
logic [1:0] imem_rerror_bus;
logic imem_bus_intg_violation;
logic [ImemAddrWidth-1:0] imem_addr_core;
assign imem_index_core = imem_addr_core[ImemAddrWidth-1:2];
logic [1:0] unused_imem_addr_core_wordbits;
assign unused_imem_addr_core_wordbits = imem_addr_core[1:0];
otp_ctrl_pkg::otbn_key_t otbn_imem_scramble_key;
otbn_imem_nonce_t otbn_imem_scramble_nonce;
logic otbn_imem_scramble_valid;
logic unused_otbn_imem_scramble_key_seed_valid;
otp_ctrl_pkg::otbn_key_t otbn_dmem_scramble_key;
otbn_dmem_nonce_t otbn_dmem_scramble_nonce;
logic otbn_dmem_scramble_valid;
logic unused_otbn_dmem_scramble_key_seed_valid;
otbn_scramble_ctrl #(
.RndCnstOtbnKey (RndCnstOtbnKey),
.RndCnstOtbnNonce (RndCnstOtbnNonce)
) u_otbn_scramble_ctrl (
.clk_i,
.rst_ni,
.clk_otp_i,
.rst_otp_ni,
.otbn_otp_key_o,
.otbn_otp_key_i,
.otbn_dmem_scramble_key_o (otbn_dmem_scramble_key ),
.otbn_dmem_scramble_nonce_o (otbn_dmem_scramble_nonce ),
.otbn_dmem_scramble_valid_o (otbn_dmem_scramble_valid ),
.otbn_dmem_scramble_key_seed_valid_o (unused_otbn_dmem_scramble_key_seed_valid),
.otbn_imem_scramble_key_o (otbn_imem_scramble_key ),
.otbn_imem_scramble_nonce_o (otbn_imem_scramble_nonce ),
.otbn_imem_scramble_valid_o (otbn_imem_scramble_valid ),
.otbn_imem_scramble_key_seed_valid_o (unused_otbn_imem_scramble_key_seed_valid),
.otbn_dmem_scramble_new_req_i (1'b0),
.otbn_imem_scramble_new_req_i (1'b0)
);
prim_ram_1p_scr #(
.Width (39),
.Depth (ImemSizeWords),
.DataBitsPerMask (39),
.EnableParity (0),
.DiffWidth (39)
) u_imem (
.clk_i,
.rst_ni (rst_n),
.key_valid_i (otbn_imem_scramble_valid),
.key_i (otbn_imem_scramble_key),
.nonce_i (otbn_imem_scramble_nonce),
.req_i (imem_req),
// TODO: Deal with grant signal, can we safely ignore? Does OTBN need refactoring to deal with
// no grant? If exposed to Ibex will result in long stall if there's no valid key, may not be
// the behaviour we want, read error instead?
.gnt_o (),
.write_i (imem_write),
.addr_i (imem_index),
.wdata_i (imem_wdata),
.wmask_i (imem_wmask),
.intg_error_i(1'b0),
.rdata_o (imem_rdata),
.rvalid_o (imem_rvalid),
.raddr_o (),
.rerror_o (),
.cfg_i (ram_cfg_i)
);
// Separate check for imem read data integrity outside of `u_imem` as `prim_ram_1p_adv` doesn't
// have functionality for only integrity checking, just fully integrated ECC.
prim_secded_39_32_dec u_imem_intg_check (
.data_i (imem_rdata),
.data_o (),
.syndrome_o (),
.err_o (imem_rerror_vec)
);
// imem_rerror is only reported for reads from OTBN. For Ibex reads integrity checking on TL
// responses will serve the same purpose.
// imem_rerror_vec is 2 bits wide and is used to report ECC errors. Bit 1 is set if there's an
// uncorrectable error and bit 0 is set if there's a correctable error. However, we're treating
// all errors as fatal, so OR the two signals together.
assign imem_rerror = |imem_rerror_vec & imem_rvalid & imem_access_core;
// IMEM access from main TL-UL bus
logic imem_gnt_bus;
// Always grant to bus accesses, when OTBN is running a dummy response is returned
assign imem_gnt_bus = imem_req_bus;
tlul_adapter_sram #(
.SramAw (ImemIndexWidth),
.SramDw (32),
.Outstanding (1),
.ByteAccess (0),
.ErrOnRead (0),
.EnableDataIntgPt (1)
) u_tlul_adapter_sram_imem (
.clk_i,
.rst_ni (rst_n ),
.tl_i (tl_win_h2d[TlWinImem] ),
.tl_o (tl_win_d2h[TlWinImem] ),
.en_ifetch_i (tlul_pkg::InstrDis ),
.req_o (imem_req_bus ),
.req_type_o ( ),
.gnt_i (imem_gnt_bus ),
.we_o (imem_write_bus ),
.addr_o (imem_index_bus ),
.wdata_o (imem_wdata_bus ),
.wmask_o (imem_wmask_bus ),
.intg_error_o(imem_bus_intg_violation),
.rdata_i (imem_rdata_bus ),
.rvalid_i (imem_rvalid_bus ),
.rerror_i (imem_rerror_bus )
);
// Mux core and bus access into IMEM
assign imem_access_core = busy_execute_q | start_q;
assign imem_req = imem_access_core ? imem_req_core : imem_req_bus;
assign imem_write = imem_access_core ? imem_write_core : imem_write_bus;
assign imem_index = imem_access_core ? imem_index_core : imem_index_bus;
assign imem_wdata = imem_access_core ? 39'(imem_wdata_core) : imem_wdata_bus;
assign imem_illegal_bus_access = imem_req_bus & imem_access_core;
assign imem_dummy_response_d = imem_illegal_bus_access;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
imem_dummy_response_q <= 1'b0;
end else begin
imem_dummy_response_q <= imem_dummy_response_d;
end
end
// The instruction memory only supports 32b word writes, so we hardcode its
// wmask here.
//
// Since this could cause confusion if the bus tried to do a partial write
// (which wasn't caught in the TLUL adapter for some reason), we assert that
// the wmask signal from the bus is indeed '1 when it requests a write. We
// don't have the corresponding check for writes from the core because the
// core cannot perform writes (and has no imem_wmask_o port).
assign imem_wmask = imem_access_core ? '1 : imem_wmask_bus;
`ASSERT(ImemWmaskBusIsFullWord_A,
imem_req_bus && imem_write_bus |-> imem_wmask_bus == '1)
// Explicitly tie off bus interface during core operation to avoid leaking
// the currently executed instruction from IMEM through the bus
// unintentionally.
assign imem_rdata_bus = !imem_access_core && !illegal_bus_access_q ? imem_rdata : 39'b0;
assign imem_rdata_core = imem_rdata[31:0];
// When an illegal bus access is seen, always return a dummy response the follow cycle.
assign imem_rvalid_bus = (~imem_access_core & imem_rvalid) | imem_dummy_response_q;
assign imem_rvalid_core = imem_access_core ? imem_rvalid : 1'b0;
// imem_rerror_bus is passed to a TLUL adapter to report read errors back to the TL interface.
// We've squashed together the 2 bits from ECC into a single (uncorrectable) error, but the TLUL
// adapter expects the original ECC format. Send imem_rerror as bit 1, signalling an
// uncorrectable error.
//
// The mux ensures that imem_rerror doesn't appear on the bus (possibly leaking information) when
// the core is operating. Since rerror depends on rvalid, we could avoid this mux. However that
// seems a bit fragile, so we err on the side of caution.
assign imem_rerror_bus = !imem_access_core ? {imem_rerror, 1'b0} : 2'b00;
assign imem_rerror_core = imem_rerror;
// Data Memory (DMEM) ========================================================
localparam int DmemSizeWords = DmemSizeByte / (WLEN / 8);
localparam int DmemIndexWidth = vbits(DmemSizeWords);
// Access select to DMEM: core (1), or bus (0)
logic dmem_access_core;
logic dmem_req;
logic dmem_write;
logic [DmemIndexWidth-1:0] dmem_index;
logic [ExtWLEN-1:0] dmem_wdata;
logic [ExtWLEN-1:0] dmem_wmask;
logic [ExtWLEN-1:0] dmem_rdata;
logic dmem_rvalid;
logic [BaseWordsPerWLEN*2-1:0] dmem_rerror_vec;
logic dmem_rerror;
logic dmem_illegal_bus_access;
logic dmem_req_core;
logic dmem_write_core;
logic [DmemIndexWidth-1:0] dmem_index_core;
logic [ExtWLEN-1:0] dmem_wdata_core;
logic [ExtWLEN-1:0] dmem_wmask_core;
logic [BaseWordsPerWLEN-1:0] dmem_rmask_core_q, dmem_rmask_core_d;
logic [ExtWLEN-1:0] dmem_rdata_core;
logic dmem_rvalid_core;
logic dmem_rerror_core;
logic dmem_req_bus;
logic dmem_dummy_response_q, dmem_dummy_response_d;
logic dmem_write_bus;
logic [DmemIndexWidth-1:0] dmem_index_bus;
logic [ExtWLEN-1:0] dmem_wdata_bus;
logic [ExtWLEN-1:0] dmem_wmask_bus;
logic [ExtWLEN-1:0] dmem_rdata_bus;
logic dmem_rvalid_bus;
logic [1:0] dmem_rerror_bus;
logic dmem_bus_intg_violation;
logic [DmemAddrWidth-1:0] dmem_addr_core;
assign dmem_index_core = dmem_addr_core[DmemAddrWidth-1:DmemAddrWidth-DmemIndexWidth];
logic unused_dmem_addr_core_wordbits;
assign unused_dmem_addr_core_wordbits = ^dmem_addr_core[DmemAddrWidth-DmemIndexWidth-1:0];
prim_ram_1p_scr #(
.Width (ExtWLEN),
.Depth (DmemSizeWords),
.DataBitsPerMask (39),
.EnableParity (0),
.DiffWidth (39),
.ReplicateKeyStream (1)
) u_dmem (
.clk_i,
.rst_ni (rst_n),
.key_valid_i (otbn_dmem_scramble_valid),
.key_i (otbn_dmem_scramble_key),
.nonce_i (otbn_dmem_scramble_nonce),
.req_i (dmem_req),
// TODO: Deal with grant signal, can we safely ignore? Does OTBN need refactoring to deal with
// no grant? If exposed to Ibex will result in long stall if there's no valid key, may not be
// the behaviour we want, read error instead?
.gnt_o (),
.write_i (dmem_write),
.addr_i (dmem_index),
.wdata_i (dmem_wdata),
.wmask_i (dmem_wmask),
.intg_error_i(1'b0),
.rdata_o (dmem_rdata),
.rvalid_o (dmem_rvalid),
.raddr_o (),
.rerror_o (),
.cfg_i (ram_cfg_i)
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
dmem_rmask_core_q <= '0;
end else begin
if (dmem_req_core) begin
dmem_rmask_core_q <= dmem_rmask_core_d;
end
end
end
for (genvar i_word = 0; i_word < BaseWordsPerWLEN; ++i_word) begin : g_dmem_intg_check
logic [1:0] dmem_rerror_raw;
// Separate check for dmem read data integrity outside of `u_dmem` as `prim_ram_1p_adv` doesn't
// have functionality for only integrity checking, just fully integrated ECC. Integrity bits are
// implemented on a 32-bit granule so separate checks are required for each.
prim_secded_39_32_dec u_dmem_intg_check (
.data_i (dmem_rdata[i_word*39 +: 39]),
.data_o (),
.syndrome_o (),
.err_o (dmem_rerror_raw)
);
// Only report an error where the word was actually accessed. Otherwise uninitialised memory
// that OTBN isn't using will cause false errors. dmem_rerror is only reported for reads from
// OTBN. For Ibex reads integrity checking on TL responses will serve the same purpose.
assign dmem_rerror_vec[i_word*2 +: 2] = dmem_rerror_raw &
{2{dmem_rmask_core_q[i_word] & dmem_rvalid & dmem_access_core}};
end
// Combine uncorrectable / correctable errors. See note above definition of imem_rerror for
// details.
assign dmem_rerror = |dmem_rerror_vec;
// DMEM access from main TL-UL bus
logic dmem_gnt_bus;
// Always grant to bus accesses, when OTBN is running a dummy response is returned
assign dmem_gnt_bus = dmem_req_bus;
tlul_adapter_sram #(
.SramAw (DmemIndexWidth),
.SramDw (WLEN),
.Outstanding (1),
.ByteAccess (0),
.ErrOnRead (0),
.EnableDataIntgPt (1)
) u_tlul_adapter_sram_dmem (
.clk_i,
.rst_ni (rst_n ),
.tl_i (tl_win_h2d[TlWinDmem] ),
.tl_o (tl_win_d2h[TlWinDmem] ),
.en_ifetch_i (tlul_pkg::InstrDis ),
.req_o (dmem_req_bus ),
.req_type_o ( ),
.gnt_i (dmem_gnt_bus ),
.we_o (dmem_write_bus ),
.addr_o (dmem_index_bus ),
.wdata_o (dmem_wdata_bus ),
.wmask_o (dmem_wmask_bus ),
.intg_error_o(dmem_bus_intg_violation),
.rdata_i (dmem_rdata_bus ),
.rvalid_i (dmem_rvalid_bus ),
.rerror_i (dmem_rerror_bus )
);
// Mux core and bus access into dmem
assign dmem_access_core = busy_execute_q;
assign dmem_req = dmem_access_core ? dmem_req_core : dmem_req_bus;
assign dmem_write = dmem_access_core ? dmem_write_core : dmem_write_bus;
assign dmem_wmask = dmem_access_core ? dmem_wmask_core : dmem_wmask_bus;
assign dmem_index = dmem_access_core ? dmem_index_core : dmem_index_bus;
assign dmem_wdata = dmem_access_core ? dmem_wdata_core : dmem_wdata_bus;
assign dmem_illegal_bus_access = dmem_req_bus & dmem_access_core;
assign dmem_dummy_response_d = dmem_illegal_bus_access;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
dmem_dummy_response_q <= 1'b0;
end else begin
dmem_dummy_response_q <= dmem_dummy_response_d;
end
end
// Explicitly tie off bus interface during core operation to avoid leaking
// DMEM data through the bus unintentionally. Once an illegal bus access is seen always return
// 0 data.
assign dmem_rdata_bus = !dmem_access_core && !illegal_bus_access_q ? dmem_rdata : '0;
assign dmem_rdata_core = dmem_rdata;
// When an illegal bus access is seen, always return a dummy response the follow cycle.
assign dmem_rvalid_bus = (~dmem_access_core & dmem_rvalid) | dmem_dummy_response_q;
assign dmem_rvalid_core = dmem_access_core ? dmem_rvalid : 1'b0;
// Expand the error signal to 2 bits and mask when the core has access. See note above
// imem_rerror_bus for details.
assign dmem_rerror_bus = !dmem_access_core ? {dmem_rerror, 1'b0} : 2'b00;
assign dmem_rerror_core = dmem_rerror;
// The top bits of DMEM rdata aren't currently used (they will eventually be used for integrity
// checks within the core)
logic unused_dmem_top_rdata;
assign unused_dmem_top_rdata = &{1'b0, dmem_rdata[ExtWLEN-1:WLEN]};
// Registers =================================================================
logic reg_bus_intg_violation;
otbn_reg_top u_reg (
.clk_i,
.rst_ni (rst_n),
.tl_i,
.tl_o,
.tl_win_o (tl_win_h2d),
.tl_win_i (tl_win_d2h),
.reg2hw,
.hw2reg,
.intg_err_o(reg_bus_intg_violation),
.devmode_i (1'b1)
);
logic bus_intg_violation;
assign bus_intg_violation = (imem_bus_intg_violation | dmem_bus_intg_violation |
reg_bus_intg_violation);
// CMD register
// start is flopped to avoid long timing paths from the TL fabric into OTBN internals.
assign start_d = reg2hw.cmd.qe & (reg2hw.cmd.q == CmdExecute);
assign illegal_bus_access_d = dmem_illegal_bus_access | imem_illegal_bus_access;
// Flop `illegal_bus_access_q` so we know an illegal bus access has happened and to break a timing
// path from the TL interface into the OTBN core.
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
start_q <= 1'b0;
illegal_bus_access_q <= 1'b0;
end else begin
start_q <= start_d;
illegal_bus_access_q <= illegal_bus_access_d;
end
end
// STATUS register
always_comb begin
unique case (1'b1)
busy_execute_q: hw2reg.status.d = StatusBusyExecute;
// TODO: Add other busy flags, and assert onehot encoding.
default: hw2reg.status.d = StatusIdle;
endcase
end
// ERR_BITS register
// The error bits for an OTBN operation get stored on the cycle that done is
// asserted. Software is expected to read them out before starting the next operation.
assign hw2reg.err_bits.bad_data_addr.de = done;
assign hw2reg.err_bits.bad_data_addr.d = err_bits.bad_data_addr;
assign hw2reg.err_bits.bad_insn_addr.de = done;
assign hw2reg.err_bits.bad_insn_addr.d = err_bits.bad_insn_addr;
assign hw2reg.err_bits.call_stack.de = done;
assign hw2reg.err_bits.call_stack.d = err_bits.call_stack;
assign hw2reg.err_bits.illegal_insn.de = done;
assign hw2reg.err_bits.illegal_insn.d = err_bits.illegal_insn;
assign hw2reg.err_bits.loop.de = done;
assign hw2reg.err_bits.loop.d = err_bits.loop;
assign hw2reg.err_bits.imem_intg_violation.de = done;
assign hw2reg.err_bits.imem_intg_violation.d = err_bits.imem_intg_violation;
assign hw2reg.err_bits.dmem_intg_violation.de = done;
assign hw2reg.err_bits.dmem_intg_violation.d = err_bits.dmem_intg_violation;
assign hw2reg.err_bits.reg_intg_violation.de = done;
assign hw2reg.err_bits.reg_intg_violation.d = err_bits.reg_intg_violation;
assign hw2reg.err_bits.illegal_bus_access.de = done;
assign hw2reg.err_bits.illegal_bus_access.d = err_bits.illegal_bus_access;
assign hw2reg.err_bits.lifecycle_escalation.de = done;
assign hw2reg.err_bits.lifecycle_escalation.d = err_bits.lifecycle_escalation;
// START_ADDR register
assign start_addr = reg2hw.start_addr.q[ImemAddrWidth-1:0];
logic [top_pkg::TL_DW-ImemAddrWidth-1:0] unused_start_addr_bits;
assign unused_start_addr_bits = reg2hw.start_addr.q[top_pkg::TL_DW-1:ImemAddrWidth];
// FATAL_ALERT_CAUSE register. The .de and .d values are equal for each bit, so that it can only
// be set, not cleared.
assign hw2reg.fatal_alert_cause.bus_intg_violation.de = bus_intg_violation;
assign hw2reg.fatal_alert_cause.bus_intg_violation.d = bus_intg_violation;
assign hw2reg.fatal_alert_cause.imem_intg_violation.de = imem_rerror;
assign hw2reg.fatal_alert_cause.imem_intg_violation.d = imem_rerror;
assign hw2reg.fatal_alert_cause.dmem_intg_violation.de = dmem_rerror;
assign hw2reg.fatal_alert_cause.dmem_intg_violation.d = dmem_rerror;
// TODO: Register file errors
assign hw2reg.fatal_alert_cause.reg_intg_violation.de = 0;
assign hw2reg.fatal_alert_cause.reg_intg_violation.d = 0;
assign hw2reg.fatal_alert_cause.illegal_bus_access.de = illegal_bus_access_d;
assign hw2reg.fatal_alert_cause.illegal_bus_access.d = illegal_bus_access_d;
assign hw2reg.fatal_alert_cause.lifecycle_escalation.de = lifecycle_escalation;
assign hw2reg.fatal_alert_cause.lifecycle_escalation.d = lifecycle_escalation;
// INSN_CNT register
logic [31:0] insn_cnt;
assign hw2reg.insn_cnt.d = insn_cnt;
// Alerts ====================================================================
logic [NumAlerts-1:0] alert_test;
assign alert_test[AlertFatal] = reg2hw.alert_test.fatal.q &
reg2hw.alert_test.fatal.qe;
assign alert_test[AlertRecov] = reg2hw.alert_test.recov.q &
reg2hw.alert_test.recov.qe;
logic [NumAlerts-1:0] alerts;
assign alerts[AlertFatal] = illegal_bus_access_d |
bus_intg_violation |
imem_rerror |
dmem_rerror;
assign alerts[AlertRecov] = 1'b0; // TODO: Implement
for (genvar i = 0; i < NumAlerts; i++) begin: gen_alert_tx
prim_alert_sender #(
.AsyncOn(AlertAsyncOn[i]),
.IsFatal(i == AlertFatal)
) u_prim_alert_sender (
.clk_i,
.rst_ni ( rst_n ),
.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
// EDN Connections ============================================================
logic edn_rnd_req, edn_rnd_ack;
logic [EdnDataWidth-1:0] edn_rnd_data;
logic edn_urnd_req, edn_urnd_ack;
logic [EdnDataWidth-1:0] edn_urnd_data;
// These synchronize the data coming from EDN and stack the 32 bit EDN words to achieve an
// internal entropy width of 256 bit.
prim_edn_req #(
.OutWidth(EdnDataWidth)
) u_prim_edn_rnd_req (
.clk_i,
.rst_ni ( rst_n ),
.req_chk_i ( 1'b1 ),
.req_i ( edn_rnd_req ),
.ack_o ( edn_rnd_ack ),
.data_o ( edn_rnd_data ),
.fips_o ( ), // unused
.clk_edn_i,
.rst_edn_ni,
.edn_o ( edn_rnd_o ),
.edn_i ( edn_rnd_i )
);
prim_edn_req #(
.OutWidth(EdnDataWidth)
) u_prim_edn_urnd_req (
.clk_i,
.rst_ni ( rst_n ),
.req_chk_i ( 1'b1 ),
.req_i ( edn_urnd_req ),
.ack_o ( edn_urnd_ack ),
.data_o ( edn_urnd_data ),
.fips_o ( ), // unused
.clk_edn_i,
.rst_edn_ni,
.edn_o ( edn_urnd_o ),
.edn_i ( edn_urnd_i )
);
// OTBN Core =================================================================
always_ff @(posedge clk_i or negedge rst_n) begin
if (!rst_n) begin
busy_execute_q <= 1'b0;
end else begin
busy_execute_q <= busy_execute_d;
end
end
assign busy_execute_d = (busy_execute_q | start_d) & ~done;
`ifdef OTBN_BUILD_MODEL
// Build both model and RTL implementation into the design, and switch at runtime through a
// plusarg.
// Set the plusarg +OTBN_USE_MODEL=1 to use the model (ISS) instead of the RTL implementation.
bit otbn_use_model;
initial begin
$value$plusargs("OTBN_USE_MODEL=%d", otbn_use_model);
end
// Mux between model and RTL implementation at runtime.
logic done_model, done_rtl;
logic start_model, start_rtl;
err_bits_t err_bits_model, err_bits_rtl;
logic [31:0] insn_cnt_model, insn_cnt_rtl;
logic edn_rnd_data_valid;
logic edn_urnd_data_valid;
logic [255:0] edn_rnd_data_model;
assign done = otbn_use_model ? done_model : done_rtl;
assign err_bits = otbn_use_model ? err_bits_model : err_bits_rtl;
assign insn_cnt = otbn_use_model ? insn_cnt_model : insn_cnt_rtl;
assign start_model = start_q & otbn_use_model;
assign start_rtl = start_q & ~otbn_use_model;
// Model (Instruction Set Simulator)
// In model only runs, leave valid signals high and supply constant RND data for EDN which will
// allow the model to continue without RND/URND related stalls.
// TODO: Implement proper EDN requests in model only runs
localparam logic [WLEN-1:0] ModelOnlyEdnVal = {{(WLEN / 4){4'h9}}};
assign edn_rnd_data_valid = otbn_use_model ? 1'b1 : edn_rnd_req & edn_rnd_ack;
assign edn_urnd_data_valid = otbn_use_model ? 1'b1 : edn_urnd_req & edn_urnd_ack;
assign edn_rnd_data_model = otbn_use_model ? ModelOnlyEdnVal : edn_rnd_data;
otbn_core_model #(
.DmemSizeByte(DmemSizeByte),
.ImemSizeByte(ImemSizeByte),
.MemScope(".."),
.DesignScope("")
) u_otbn_core_model (
.clk_i,
.rst_ni (rst_n),
.start_i (start_model),
.done_o (done_model),
.err_bits_o (err_bits_model),
.start_addr_i (start_addr),
.edn_rnd_data_valid_i ( edn_rnd_data_valid ),
.edn_rnd_data_i ( edn_rnd_data ),
.edn_urnd_data_valid_i ( edn_urnd_data_valid ),
.insn_cnt_o (insn_cnt_model),
.err_o ()
);
// RTL implementation
otbn_core #(
.RegFile(RegFile),
.DmemSizeByte(DmemSizeByte),
.ImemSizeByte(ImemSizeByte),
.RndCnstUrndLfsrSeed(RndCnstUrndLfsrSeed),
.RndCnstUrndChunkLfsrPerm(RndCnstUrndChunkLfsrPerm)
) u_otbn_core (
.clk_i,
.rst_ni (rst_n),
.start_i (start_rtl),
.done_o (done_rtl),
.err_bits_o (err_bits_rtl),
.start_addr_i (start_addr),
.imem_req_o (imem_req_core),
.imem_addr_o (imem_addr_core),
.imem_wdata_o (imem_wdata_core),
.imem_rdata_i (imem_rdata_core),
.imem_rvalid_i (imem_rvalid_core),
.imem_rerror_i (imem_rerror_core),
.dmem_req_o (dmem_req_core),
.dmem_write_o (dmem_write_core),
.dmem_addr_o (dmem_addr_core),
.dmem_wdata_o (dmem_wdata_core),
.dmem_wmask_o (dmem_wmask_core),
.dmem_rmask_o (dmem_rmask_core_d),
.dmem_rdata_i (dmem_rdata_core),
.dmem_rvalid_i (dmem_rvalid_core),
.dmem_rerror_i (dmem_rerror_core),
.edn_rnd_req_o (edn_rnd_req),
.edn_rnd_ack_i (edn_rnd_ack),
.edn_rnd_data_i (edn_rnd_data),
.edn_urnd_req_o (edn_urnd_req),
.edn_urnd_ack_i (edn_urnd_ack),
.edn_urnd_data_i (edn_urnd_data),
.insn_cnt_o (insn_cnt_rtl),
.illegal_bus_access_i (illegal_bus_access_q),
.lifecycle_escalation_i (lifecycle_escalation)
);
`else
otbn_core #(
.RegFile(RegFile),
.DmemSizeByte(DmemSizeByte),
.ImemSizeByte(ImemSizeByte),
.RndCnstUrndLfsrSeed(RndCnstUrndLfsrSeed),
.RndCnstUrndChunkLfsrPerm(RndCnstUrndChunkLfsrPerm)
) u_otbn_core (
.clk_i,
.rst_ni (rst_n),
.start_i (start_q),
.done_o (done),
.err_bits_o (err_bits),
.start_addr_i (start_addr),
.imem_req_o (imem_req_core),
.imem_addr_o (imem_addr_core),
.imem_wdata_o (imem_wdata_core),
.imem_rdata_i (imem_rdata_core),
.imem_rvalid_i (imem_rvalid_core),
.imem_rerror_i (imem_rerror_core),
.dmem_req_o (dmem_req_core),
.dmem_write_o (dmem_write_core),
.dmem_addr_o (dmem_addr_core),
.dmem_wdata_o (dmem_wdata_core),
.dmem_wmask_o (dmem_wmask_core),
.dmem_rmask_o (dmem_rmask_core_d),
.dmem_rdata_i (dmem_rdata_core),
.dmem_rvalid_i (dmem_rvalid_core),
.dmem_rerror_i (dmem_rerror_core),
.edn_rnd_req_o (edn_rnd_req),
.edn_rnd_ack_i (edn_rnd_ack),
.edn_rnd_data_i (edn_rnd_data),
.edn_urnd_req_o (edn_urnd_req),
.edn_urnd_ack_i (edn_urnd_ack),
.edn_urnd_data_i (edn_urnd_data),
.insn_cnt_o (insn_cnt),
.illegal_bus_access_i (illegal_bus_access_q),
.lifecycle_escalation_i (lifecycle_escalation)
);
`endif
// The core can never signal a write to IMEM
assign imem_write_core = 1'b0;
// Asserts ===================================================================
// All outputs should be known value after reset
`ASSERT_KNOWN(TlODValidKnown_A, tl_o.d_valid)
`ASSERT_KNOWN(TlOAReadyKnown_A, tl_o.a_ready)
`ASSERT_KNOWN(IdleOKnown_A, idle_o)
`ASSERT_KNOWN(IdleOtpOKnown_A, idle_otp_o)
`ASSERT_KNOWN(IntrDoneOKnown_A, intr_done_o)
`ASSERT_KNOWN(AlertTxOKnown_A, alert_tx_o)
`ASSERT_KNOWN(EdnRndOKnown_A, edn_rnd_o)
`ASSERT_KNOWN(EdnUrndOKnown_A, edn_urnd_o)
endmodule