blob: 773aa38f224873a98a0ad2d89823a9d0050c1f03 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
module otbn_top_sim (
input IO_CLK,
input IO_RST_N
);
import otbn_pkg::*;
import edn_pkg::*;
import keymgr_pkg::otbn_key_req_t;
// Size of the instruction memory, in bytes
parameter int ImemSizeByte = otbn_reg_pkg::OTBN_IMEM_SIZE;
// Size of the data memory, in bytes
parameter int DmemSizeByte = otbn_reg_pkg::OTBN_DMEM_SIZE + otbn_pkg::DmemScratchSizeByte;
localparam int ImemAddrWidth = prim_util_pkg::vbits(ImemSizeByte);
localparam int DmemAddrWidth = prim_util_pkg::vbits(DmemSizeByte);
// Fixed key and nonce for scrambling in verilator environment
localparam logic [127:0] TestScrambleKey = 128'h48ecf6c738f0f108a5b08620695ffd4d;
localparam logic [63:0] TestScrambleNonce = 64'hf88c2578fa4cd123;
logic otbn_done, otbn_done_r, otbn_done_rr;
core_err_bits_t core_err_bits;
err_bits_t otbn_err_bits, otbn_err_bits_r, otbn_err_bits_rr;
logic otbn_start;
// Intialise otbn_start_done to 1 so that we only signal otbn_start after we have seen a reset. If
// you don't do this, we start OTBN before the reset, which can generate confusing trace messages.
logic otbn_start_done = 1'b1;
// Instruction memory (IMEM) signals
logic imem_req;
logic [ImemAddrWidth-1:0] imem_addr;
logic [38:0] imem_rdata;
logic imem_rvalid;
// Data memory (DMEM) signals
logic dmem_req;
logic dmem_write;
logic [DmemAddrWidth-1:0] dmem_addr;
logic [ExtWLEN-1:0] dmem_wdata;
logic [ExtWLEN-1:0] dmem_wmask;
logic [ExtWLEN-1:0] dmem_rdata;
logic dmem_rvalid;
logic dmem_rerror;
// Entropy Distribution Network (EDN)
logic edn_rnd_req, edn_urnd_req;
logic edn_rnd_ack, edn_urnd_ack;
logic [EdnDataWidth-1:0] edn_rnd_data, edn_urnd_data;
logic edn_rnd_data_valid;
logic edn_urnd_data_valid;
// Instruction counter (feeds into otbn.INSN_CNT in full block)
logic [31:0] insn_cnt;
logic [1:0][SideloadKeyWidth-1:0] sideload_key_shares;
assign sideload_key_shares[0] = {12{32'hDEADBEEF}};
assign sideload_key_shares[1] = {12{32'hBAADF00D}};
otbn_key_req_t keymgr_key;
assign keymgr_key.key[0] = sideload_key_shares[0];
assign keymgr_key.key[1] = sideload_key_shares[1];
assign keymgr_key.valid = 1'b1;
logic secure_wipe_running;
otbn_core #(
.ImemSizeByte ( ImemSizeByte ),
.DmemSizeByte ( DmemSizeByte ),
.SecMuteUrnd ( 1'b0 ),
.SecSkipUrndReseedAtStart ( 1'b0 )
) u_otbn_core (
.clk_i ( IO_CLK ),
.rst_ni ( IO_RST_N ),
.start_i ( otbn_start ),
.done_o ( otbn_done ),
.locking_o ( ),
.secure_wipe_running_o ( secure_wipe_running ),
.err_bits_o ( core_err_bits ),
.recoverable_err_o ( ),
.imem_req_o ( imem_req ),
.imem_addr_o ( imem_addr ),
.imem_rdata_i ( imem_rdata ),
.imem_rvalid_i ( imem_rvalid ),
.dmem_req_o ( dmem_req ),
.dmem_write_o ( dmem_write ),
.dmem_addr_o ( dmem_addr ),
.dmem_wdata_o ( dmem_wdata ),
.dmem_wmask_o ( dmem_wmask ),
.dmem_rmask_o ( ),
.dmem_rdata_i ( dmem_rdata ),
.dmem_rvalid_i ( dmem_rvalid ),
.dmem_rerror_i ( dmem_rerror ),
.edn_rnd_req_o ( edn_rnd_req ),
.edn_rnd_ack_i ( edn_rnd_ack ),
.edn_rnd_data_i ( edn_rnd_data ),
.edn_rnd_fips_i ( 1'b1 ),
.edn_rnd_err_i ( 1'b0 ),
.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 ),
.insn_cnt_clear_i ( 1'b0 ),
.mems_sec_wipe_o ( ),
.dmem_sec_wipe_urnd_key_o ( ),
.imem_sec_wipe_urnd_key_o ( ),
.req_sec_wipe_urnd_keys_i ( 1'b0 ),
.escalate_en_i ( prim_mubi_pkg::MuBi4False ),
.rma_req_i ( prim_mubi_pkg::MuBi4False ),
.rma_ack_o ( ),
.software_errs_fatal_i ( 1'b0 ),
.sideload_key_shares_i ( sideload_key_shares ),
.sideload_key_shares_valid_i ( 2'b11 )
);
// The values returned by the mock EDN must match those set in `standalonesim.py`.
localparam logic [1:0][WLEN-1:0] FixedEdnVals = {{4{64'hCCCC_CCCC_BBBB_BBBB}},
{4{64'hAAAA_AAAA_9999_9999}}};
edn_req_t rnd_req;
edn_rsp_t rnd_rsp;
assign rnd_req.edn_req = edn_rnd_req;
otbn_mock_edn #(
.Width ( WLEN ),
.FixedEdnVals ( FixedEdnVals )
) u_mock_rnd_edn(
.clk_i ( IO_CLK ),
.rst_ni ( IO_RST_N ),
.edn_req_i ( rnd_req ),
.edn_rsp_o ( rnd_rsp ),
.edn_data_o ( edn_rnd_data ),
.edn_ack_o ( edn_rnd_ack )
);
assign edn_rnd_data_valid = edn_rnd_req & edn_rnd_ack;
edn_req_t urnd_req;
edn_rsp_t urnd_rsp;
assign urnd_req.edn_req = edn_urnd_req;
otbn_mock_edn #(
.Width ( WLEN ),
.FixedEdnVals ( FixedEdnVals )
) u_mock_urnd_edn(
.clk_i ( IO_CLK ),
.rst_ni ( IO_RST_N ),
.edn_req_i ( urnd_req ),
.edn_rsp_o ( urnd_rsp ),
.edn_ack_o ( edn_urnd_ack ),
.edn_data_o ( edn_urnd_data )
);
assign edn_urnd_data_valid = edn_urnd_req & edn_urnd_ack;
bind otbn_core otbn_trace_if #(.ImemAddrWidth, .DmemAddrWidth) i_otbn_trace_if (.*);
bind otbn_core otbn_tracer u_otbn_tracer(.*, .otbn_trace(i_otbn_trace_if));
assign u_otbn_core.i_otbn_trace_if.scramble_state_err_i = '0;
assign u_otbn_core.i_otbn_trace_if.ext_mubi_err_i = '0;
assign u_otbn_core.i_otbn_trace_if.missed_gnt_i = '0;
// Convert from core_err_bits_t to err_bits_t
assign otbn_err_bits = '{
fatal_software: core_err_bits.fatal_software,
lifecycle_escalation: 0,
illegal_bus_access: 0,
bad_internal_state: core_err_bits.bad_internal_state,
bus_intg_violation: 0,
reg_intg_violation: core_err_bits.reg_intg_violation,
dmem_intg_violation: core_err_bits.dmem_intg_violation,
imem_intg_violation: core_err_bits.imem_intg_violation,
rnd_fips_chk_fail: core_err_bits.rnd_fips_chk_fail,
rnd_rep_chk_fail: core_err_bits.rnd_rep_chk_fail,
key_invalid: core_err_bits.key_invalid,
loop: core_err_bits.loop,
illegal_insn: core_err_bits.illegal_insn,
call_stack: core_err_bits.call_stack,
bad_insn_addr: core_err_bits.bad_insn_addr,
bad_data_addr: core_err_bits.bad_data_addr
};
// Track when OTBN is done with its initial secure wipe of the internal state. We use this to
// wait for the OTBN core to complete the initial secure wipe before we send it the start signal.
// Also keep a delayed copy of the done signal. This is necessary to align with the status of
// OTBN and the model, which lags one cycle behind the completion of the OTBN core.
logic init_sec_wipe_done_q, init_sec_wipe_done_qq;
always_ff @(posedge IO_CLK, negedge IO_RST_N) begin
if (!IO_RST_N) begin
init_sec_wipe_done_q <= 1'b0;
init_sec_wipe_done_qq <= 1'b0;
end else begin
init_sec_wipe_done_qq <= init_sec_wipe_done_q;
if (!secure_wipe_running) init_sec_wipe_done_q <= 1'b1;
end
end
// Pulse otbn_start for 1 cycle after the initial secure wipe is done.
// Flop `done_o` from otbn_core to match up with model done signal.
always @(posedge IO_CLK or negedge IO_RST_N) begin
if (!IO_RST_N) begin
otbn_start <= 1'b0;
otbn_start_done <= 1'b0;
otbn_done_r <= 1'b0;
otbn_done_rr <= 1'b0;
otbn_err_bits_r <= '0;
otbn_err_bits_rr <= '0;
end else begin
if (!otbn_start_done && init_sec_wipe_done_q) begin
otbn_start <= 1'b1;
otbn_start_done <= 1'b1;
end else if (otbn_start) begin
otbn_start <= 1'b0;
end
otbn_done_r <= otbn_done;
otbn_done_rr <= otbn_done_r;
otbn_err_bits_r <= otbn_err_bits;
otbn_err_bits_rr <= otbn_err_bits_r;
end
end
localparam int DmemSizeWords = DmemSizeByte / (WLEN / 8);
localparam int DmemIndexWidth = prim_util_pkg::vbits(DmemSizeWords);
logic [DmemIndexWidth-1:0] dmem_index;
logic [DmemAddrWidth-DmemIndexWidth-1:0] unused_dmem_addr;
assign dmem_index = dmem_addr[DmemAddrWidth-1:DmemAddrWidth-DmemIndexWidth];
assign unused_dmem_addr = dmem_addr[DmemAddrWidth-DmemIndexWidth-1:0];
prim_ram_1p_scr #(
.Width ( ExtWLEN ),
.Depth ( DmemSizeWords ),
.DataBitsPerMask ( 39 ),
.EnableParity ( 0 ),
.ReplicateKeyStream ( 1 )
) u_dmem (
.clk_i ( IO_CLK ),
.rst_ni ( IO_RST_N ),
.key_valid_i ( 1'b1 ),
.key_i ( TestScrambleKey ),
.nonce_i ( TestScrambleNonce ),
.req_i ( dmem_req ),
.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 ( '0 )
);
// No integrity errors in Verilator testbench
assign dmem_rerror = 1'b0;
localparam int ImemSizeWords = ImemSizeByte / 4;
localparam int ImemIndexWidth = prim_util_pkg::vbits(ImemSizeWords);
logic [ImemIndexWidth-1:0] imem_index;
logic [1:0] unused_imem_addr;
assign imem_index = imem_addr[ImemAddrWidth-1:2];
assign unused_imem_addr = imem_addr[1:0];
prim_ram_1p_scr #(
.Width ( 39 ),
.Depth ( ImemSizeWords ),
.DataBitsPerMask ( 39 ),
.EnableParity ( 0 )
) u_imem (
.clk_i ( IO_CLK ),
.rst_ni ( IO_RST_N ),
.key_valid_i ( 1'b1 ),
.key_i ( TestScrambleKey ),
.nonce_i ( TestScrambleNonce ),
.req_i ( imem_req ),
.gnt_o ( ),
.write_i ( 1'b0 ),
.addr_i ( imem_index ),
.wdata_i ( '0 ),
.wmask_i ( '0 ),
.intg_error_i ( 1'b0 ),
.rdata_o ( imem_rdata ),
.rvalid_o ( imem_rvalid ),
.raddr_o ( ),
.rerror_o ( ),
.cfg_i ( '0 )
);
// When OTBN is done let a few more cycles run then finish simulation
logic [1:0] finish_counter;
always @(posedge IO_CLK or negedge IO_RST_N) begin
if (!IO_RST_N) begin
finish_counter <= 2'd0;
end else begin
if (otbn_done_r) begin
finish_counter <= 2'd1;
end
if (finish_counter != 0) begin
finish_counter <= finish_counter + 2'd1;
end
if (finish_counter == 2'd3) begin
$finish;
end
end
end
// The model
//
// This runs in parallel with the real core above, with consistency checks between the two.
localparam string DesignScope = "..u_otbn_core";
err_bits_t otbn_model_err_bits;
bit [31:0] otbn_model_insn_cnt;
bit otbn_model_done_rr;
bit otbn_model_err;
otbn_core_model #(
.MemScope ( ".." ),
.DesignScope ( DesignScope )
) u_otbn_core_model (
.clk_i ( IO_CLK ),
.clk_edn_i ( IO_CLK ),
.rst_ni ( IO_RST_N ),
.rst_edn_ni ( IO_RST_N ),
.cmd_i ( otbn_pkg::CmdExecute ),
.cmd_en_i ( otbn_start ),
.lc_escalate_en_i ( lc_ctrl_pkg::Off ),
.lc_rma_req_i ( lc_ctrl_pkg::Off ),
.err_bits_o ( otbn_model_err_bits ),
.edn_rnd_i ( rnd_rsp ),
.edn_rnd_o ( ),
.edn_rnd_cdc_done_i ( edn_rnd_data_valid ),
.edn_urnd_i ( urnd_rsp ),
.edn_urnd_o ( ),
.edn_urnd_cdc_done_i ( edn_urnd_data_valid ),
.init_sec_wipe_done_i ( init_sec_wipe_done_qq ),
.otp_key_cdc_done_i ( 1'b0 ),
.status_o ( ),
.insn_cnt_o ( otbn_model_insn_cnt ),
.keymgr_key_i ( keymgr_key),
.done_rr_o ( otbn_model_done_rr ),
.err_o ( otbn_model_err )
);
bit done_mismatch_latched, err_bits_mismatch_latched, cnt_mismatch_latched;
bit model_err_latched, loop_warp_model_err;
always_ff @(posedge IO_CLK or negedge IO_RST_N) begin
if (!IO_RST_N) begin
done_mismatch_latched <= 1'b0;
err_bits_mismatch_latched <= 1'b0;
cnt_mismatch_latched <= 1'b0;
model_err_latched <= 1'b0;
end else begin
// Check that the 'done_o' output from the RTL matches the 'done_rr_o' output from the model
// (with two cycles' delay).
if (otbn_done_rr && !otbn_model_done_rr) begin
$display("ERROR: At time %0t, RTL done on previous cycle, but model still busy.", $time);
done_mismatch_latched <= 1'b1;
end
if (otbn_model_done_rr && !otbn_done_rr) begin
$display("ERROR: At time %0t, model finished, but RTL not done in time.", $time);
done_mismatch_latched <= 1'b1;
end
if (otbn_model_done_rr && otbn_done_rr) begin
if (otbn_err_bits_rr != otbn_model_err_bits) begin
$display("ERROR: At time %0t, otbn_err_bits != otbn_model_err_bits (0x%0x != 0x%0x).",
$time, otbn_err_bits_rr, otbn_model_err_bits);
err_bits_mismatch_latched <= 1'b1;
end
end
if (insn_cnt != otbn_model_insn_cnt) begin
if (!cnt_mismatch_latched) begin
$display("ERROR: At time %0t, insn_cnt != otbn_model_insn_cnt (0x%0x != 0x%0x).",
$time, insn_cnt, otbn_model_insn_cnt);
end
cnt_mismatch_latched <= 1'b1;
end
model_err_latched <= model_err_latched | otbn_model_err | loop_warp_model_err;
end
end
bit err_latched;
assign err_latched = model_err_latched | done_mismatch_latched | err_bits_mismatch_latched;
int bad_cycles;
always_ff @(negedge IO_CLK or negedge IO_RST_N) begin
if (!IO_RST_N) begin
bad_cycles <= 0;
end else begin
if (err_latched) begin
bad_cycles <= bad_cycles + 1;
end
if (bad_cycles >= 3) begin
$error("Mismatch or model error (see message above)");
end
end
end
// Defined in otbn_top_sim.cc
import "DPI-C" context function int OtbnTopInstallLoopWarps();
import "DPI-C" context function void OtbnTopApplyLoopWarp();
import "DPI-C" context function void OtbnTopDumpState();
bit warps_installed;
always_ff @(negedge IO_CLK or negedge IO_RST_N) begin
if (!IO_RST_N) begin
warps_installed <= 1'b0;
end else begin
if (!warps_installed) begin
if (OtbnTopInstallLoopWarps() != 0) begin
$display("ERROR: At time %0t, OtbnTopInstallLoopWarps() failed.", $time);
loop_warp_model_err <= 1'b1;
end
end
warps_installed <= 1'b1;
end
end
always_ff @(posedge IO_CLK or negedge IO_RST_N) begin
if (IO_RST_N) begin
OtbnTopApplyLoopWarp();
end
end
always_ff @(negedge IO_CLK or negedge IO_RST_N) begin
if (IO_RST_N && u_otbn_core.u_otbn_controller.start_secure_wipe) begin
// When OTBN starts a secure wipe this indicates the program has either terminated (executed
// 'ecall') or hit an error, either way the execution is done. The state must be dumped as the
// secure wipe is started so we can dump the final execution state not the all zeros state
// that will be present once a secure wipe is finished.
OtbnTopDumpState();
end
end
export "DPI-C" function otbn_base_call_stack_get_size;
function automatic int unsigned otbn_base_call_stack_get_size();
// Explicit zero extension required because Verilator (tested with v4.216) otherwise raises
// a `WIDTH` warning (which is promoted to an error).
return {{(32-$bits(u_otbn_core.u_otbn_rf_base.u_call_stack.stack_wr_ptr)){1'b0}},
u_otbn_core.u_otbn_rf_base.u_call_stack.stack_wr_ptr};
endfunction
export "DPI-C" function otbn_base_call_stack_get_element;
function automatic int unsigned otbn_base_call_stack_get_element(int index);
return u_otbn_core.u_otbn_rf_base.u_call_stack.stack_storage[index][31:0];
endfunction
export "DPI-C" function otbn_base_reg_get;
function automatic int unsigned otbn_base_reg_get(int index);
return u_otbn_core.u_otbn_rf_base.gen_rf_base_ff.u_otbn_rf_base_inner.rf_reg[index][31:0];
endfunction
export "DPI-C" function otbn_bignum_reg_get;
function automatic int unsigned otbn_bignum_reg_get(int index, int word);
return u_otbn_core.u_otbn_rf_bignum.gen_rf_bignum_ff.u_otbn_rf_bignum_inner.rf[index][word*39+:32];
endfunction
export "DPI-C" function otbn_err_get;
function automatic bit otbn_err_get();
return err_latched;
endfunction
endmodule