blob: 264489be75833196bb5a881280b2215a1866cab5 [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 tb;
// dep packages (test)
import uvm_pkg::*;
import dv_utils_pkg::*;
import mem_bkdr_util_pkg::mem_bkdr_util;
import otbn_env_pkg::*;
import otbn_test_pkg::*;
import otbn_pkg::NGpr, otbn_pkg::NWdr;
// dep packages (rtl)
import otbn_reg_pkg::*;
import edn_pkg::*;
import otp_ctrl_pkg::*;
import keymgr_pkg::*;
// macro includes
`include "uvm_macros.svh"
`include "dv_macros.svh"
wire clk, rst_n;
wire intr_done;
wire [NUM_MAX_INTERRUPTS-1:0] interrupts;
// interfaces
clk_rst_if clk_rst_if (.clk(clk), .rst_n(rst_n));
tl_if tl_if (.clk(clk), .rst_n(rst_n));
otbn_escalate_if escalate_if (.clk_i (clk), .rst_ni (rst_n));
pins_if #(NUM_MAX_INTERRUPTS) intr_if (interrupts);
assign interrupts[0] = {intr_done};
otbn_key_req_t sideload_key;
key_sideload_if#(keymgr_pkg::otbn_key_req_t) keymgr_if (
.clk_i (clk),
.rst_ni (rst_n),
.sideload_key (sideload_key)
);
otbn_model_if #(
.ImemSizeByte (ImemSizeByte)
) model_if (
.clk_i (clk),
.rst_ni (rst_n),
.keymgr_key_i (sideload_key)
);
// OTP Interface related connections
localparam logic [127:0] TestScrambleKey = 128'h48ecf6c738f0f108a5b08620695ffd4d;
localparam logic [63:0] TestScrambleNonce = 64'hf88c2578fa4cd123;
otbn_otp_key_req_t otp_key_req;
otbn_otp_key_rsp_t otp_key_rsp;
otp_ctrl_pkg::otbn_key_t key;
otp_ctrl_pkg::otbn_nonce_t nonce;
wire seed_valid;
wire otp_rst_n = rst_n;
wire otp_clk;
clk_rst_if otp_clk_rst_if(.clk(otp_clk), .rst_n(otp_rst_n));
// Initiate push pull interface for the OTP<->OTBN connections
push_pull_if #(
.DeviceDataWidth(KEY_RSP_DATA_SIZE)
) otp_key_if (
.clk(otp_clk),
.rst_n(otp_rst_n)
);
// OTP Key interface assignments
assign otp_key_if.req = otp_key_req.req;
assign otp_key_rsp.ack = otp_key_if.ack;
assign otp_key_rsp.key = key;
assign otp_key_rsp.nonce = nonce;
assign otp_key_rsp.seed_valid = seed_valid;
// key, nonce, seed_valid all driven by push_pull Device interface
assign {key, nonce, seed_valid} = otp_key_if.d_data;
// edn_clk, edn_rst_n and edn_if is defined and driven in below macro
`DV_EDN_IF_CONNECT
`DV_ALERT_IF_CONNECT()
// dut
otbn # (
.RndCnstOtbnKey(TestScrambleKey),
.RndCnstOtbnNonce(TestScrambleNonce)
) dut (
.clk_i (clk),
.rst_ni(rst_n),
.tl_i(tl_if.h2d),
.tl_o(tl_if.d2h),
// Correct behaviour of the idle output is ensured by the bound-in otbn_idle_checker instance.
.idle_o(),
.intr_done_o(intr_done),
.alert_rx_i(alert_rx),
.alert_tx_o(alert_tx),
.lc_escalate_en_i(escalate_if.enable),
.lc_rma_req_i (escalate_if.req),
.lc_rma_ack_o (escalate_if.ack),
.ram_cfg_i('0),
.clk_edn_i (edn_clk),
.rst_edn_ni(edn_rst_n),
.edn_rnd_o (edn_if[RndEdnIdx].req),
.edn_rnd_i ({edn_if[RndEdnIdx].ack, edn_if[RndEdnIdx].d_data}),
.edn_urnd_o(edn_if[UrndEdnIdx].req),
.edn_urnd_i({edn_if[UrndEdnIdx].ack, edn_if[UrndEdnIdx].d_data}),
.clk_otp_i (otp_clk),
.rst_otp_ni (otp_rst_n),
.otbn_otp_key_o(otp_key_req),
.otbn_otp_key_i(otp_key_rsp),
.keymgr_key_i(sideload_key)
);
bind dut.u_otbn_core otbn_trace_if #(
.ImemAddrWidth (ImemAddrWidth),
.DmemAddrWidth (DmemAddrWidth)
) i_otbn_trace_if (.*);
assign dut.u_otbn_core.i_otbn_trace_if.scramble_state_err_i = dut.otbn_scramble_state_error;
assign dut.u_otbn_core.i_otbn_trace_if.ext_mubi_err_i = dut.mubi_err;
assign dut.u_otbn_core.i_otbn_trace_if.missed_gnt_i.imem_gnt_missed_err = dut.imem_missed_gnt;
assign dut.u_otbn_core.i_otbn_trace_if.missed_gnt_i.dmem_gnt_missed_err = dut.dmem_missed_gnt;
bind dut.u_otbn_core otbn_tracer u_otbn_tracer(.*, .otbn_trace(i_otbn_trace_if));
bind dut.u_otbn_core.u_otbn_controller.u_otbn_loop_controller
otbn_loop_if i_otbn_loop_if (
.clk_i,
.rst_ni,
// The insn_addr_i signal in the loop controller is of width ImemAddrWidth. We expand it to a
// 32-bit address here to avoid having to parameterise the type of the interface.
.insn_addr_i (32'(insn_addr_i)),
.at_current_loop_end_insn,
.current_loop_valid,
.loop_stack_full,
.current_loop_finish,
.next_loop_valid,
.loop_start_req_i,
.loop_start_commit_i,
.loop_iterations_i,
.otbn_stall_i,
// These addresses are start/end addresses for entries in the loop stack. As with insn_addr_i,
// we expand them to 32 bits. Also the loop stack entries have a type that's not exposed
// outside of the loop controller module so we need to extract the fields here.
.current_loop_start (32'(current_loop.loop_addr_info.loop_start)),
.current_loop_end (32'(current_loop.loop_addr_info.loop_end)),
.next_loop_end (32'(prefetch_loop_end_addr_o)),
// These counts are used by the loop warping code.
.current_loop_d_iterations (prefetch_loop_iterations_o),
.current_loop_q_iterations (current_loop.loop_iterations),
.loop_stack_rd_idx
);
bind dut.u_otbn_core.u_otbn_alu_bignum otbn_alu_bignum_if i_otbn_alu_bignum_if (.*);
bind dut.u_otbn_core.u_otbn_controller otbn_controller_if i_otbn_controller_if (.*);
bind dut.u_otbn_core.u_otbn_mac_bignum otbn_mac_bignum_if i_otbn_mac_bignum_if (.*);
bind dut.u_otbn_core.u_otbn_rf_base otbn_rf_base_if i_otbn_rf_base_if (.*);
bind dut.u_otbn_core.u_otbn_rnd otbn_rnd_if i_otbn_rnd_if (.*);
// OTBN model, wrapping an ISS.
//
// Note that we pull the "start" signal out of the DUT. This is because it's much more difficult
// to grab the decoded signal from TL transactions on the cycle it happens. We have an explicit
// check in the scoreboard to ensure that this gets asserted at the time we expect (to spot any
// decoding errors).
assign model_if.cmd_q = dut.reg2hw.cmd.q;
assign model_if.cmd_qe = dut.reg2hw.cmd.qe;
// Valid signals below are set when DUT finishes processing incoming 32b packages and constructs
// 256b EDN data. Model checks if the processing of the packages are done in maximum of 5 cycles
logic edn_rnd_cdc_done, edn_rnd_req_model;
logic edn_urnd_cdc_done, edn_urnd_req_model;
logic otp_key_cdc_done;
assign edn_rnd_cdc_done = dut.edn_rnd_req & dut.edn_rnd_ack;
assign edn_urnd_cdc_done = dut.edn_urnd_req & dut.edn_urnd_ack;
assign otp_key_cdc_done = dut.u_otbn_scramble_ctrl.otp_key_ack;
bit [31:0] model_insn_cnt;
otbn_core_model #(
.MemScope ("..dut"),
.DesignScope ("..dut.u_otbn_core")
) u_model (
.clk_i (model_if.clk_i),
.clk_edn_i (edn_clk),
.rst_ni (model_if.rst_ni),
.rst_edn_ni(edn_rst_n),
.cmd_i (model_if.cmd_q),
.cmd_en_i(model_if.cmd_qe),
.lc_escalate_en_i(escalate_if.enable),
.lc_rma_req_i (escalate_if.req),
.err_bits_o(model_if.err_bits),
.edn_rnd_i ({edn_if[RndEdnIdx].ack, edn_if[RndEdnIdx].d_data}),
.edn_rnd_o (edn_rnd_req_model),
.edn_rnd_cdc_done_i(edn_rnd_cdc_done),
.edn_urnd_i ({edn_if[UrndEdnIdx].ack, edn_if[UrndEdnIdx].d_data}),
.edn_urnd_o (edn_urnd_req_model),
.edn_urnd_cdc_done_i(edn_urnd_cdc_done),
.init_sec_wipe_done_i(dut.u_otbn_core.i_otbn_trace_if.initial_secure_wipe_done),
.otp_key_cdc_done_i(otp_key_cdc_done),
.status_o (model_if.status),
.insn_cnt_o(model_insn_cnt),
.done_rr_o(),
.err_o(model_if.err),
.keymgr_key_i(model_if.keymgr_key_i)
);
// Pull the final PC and the OtbnModel handle out of the SV model wrapper.
assign model_if.stop_pc = u_model.stop_pc_q;
// The always_ff is because the spec doesn't allow continuous assignments for chandles. The value
// is populated in an init block and we'll only read this when the start signal is asserted, which
// will be much more than 1 cycle later, so we shouldn't need to worry about a stale value.
always_ff @(posedge model_if.clk_i) begin
model_if.handle <= u_model.model_handle;
end
otbn_insn_cnt_if insn_cnt_if (
.clk_i (clk),
.rst_ni (rst_n),
.insn_cnt_i (dut.insn_cnt),
.insn_executing_i (dut.u_otbn_core.u_otbn_controller.insn_executing),
.stall_i (dut.u_otbn_core.u_otbn_controller.stall),
.model_insn_cnt_i (model_insn_cnt)
);
//////////////////////////////////////////////////////////////////////////////
// Model/RTL consistency checks
//
// These are the sort of checks that are easier to state at the design level than in terms of UVM
// transactions.
// Check that the status output from the model exactly matches the register from the dut. In
// theory, we could see mismatches by probing the STATUS register over the TL bus, but we have to
// be lucky with exactly when the reads happen if we want to see off-by-one cycle errors. This
// assertion gives a continuous check.
`ASSERT(MatchingStatus_A, dut.u_reg.u_status.q == model_if.status, clk, !rst_n)
// Check that if the modelled EDN requests are matching with the requests from DUT
`ASSERT(MatchingReqRND_A, dut.u_otbn_core.edn_rnd_req_o == edn_rnd_req_model, clk, !rst_n)
// Disable checking URND in the case of Locked status since it's modelling is not exactly accurate
// for that state.
// TODO (#15710): Fix modelling of URND in the locked state.
`ASSERT(MatchingReqURND_A, dut.u_otbn_core.edn_urnd_req_o == edn_urnd_req_model,
clk, !rst_n || model_if.status == otbn_pkg::StatusLocked)
initial begin
mem_bkdr_util imem_util, dmem_util;
// drive clk and rst_n from clk_if
clk_rst_if.set_active();
otp_clk_rst_if.set_active(.drive_rst_n_val(1'b0));
uvm_config_db#(virtual clk_rst_if)::set(null, "*.env", "otp_clk_rst_vif", otp_clk_rst_if);
uvm_config_db#(virtual clk_rst_if)::set(null, "*.env", "clk_rst_vif", clk_rst_if);
uvm_config_db#(virtual tl_if)::set(null, "*.env.m_tl_agent*", "vif", tl_if);
uvm_config_db#(escalate_vif)::set(null, "*.env", "escalate_vif", escalate_if);
uvm_config_db#(intr_vif)::set(null, "*.env", "intr_vif", intr_if);
uvm_config_db#(virtual otbn_model_if)::set(null, "*.env.model_agent", "vif", model_if);
uvm_config_db#(virtual key_sideload_if#(keymgr_pkg::otbn_key_req_t))::set(
null, "*.env.keymgr_sideload_agent", "vif", keymgr_if);
uvm_config_db#(otp_key_vif)::set(
null, "*.env.key_agent*", "vif", otp_key_if);
uvm_config_db#(virtual otbn_trace_if)::set(null, "*.env", "trace_vif",
dut.u_otbn_core.i_otbn_trace_if);
uvm_config_db#(virtual otbn_loop_if)::set(
null, "*.env", "loop_vif",
dut.u_otbn_core.u_otbn_controller.u_otbn_loop_controller.i_otbn_loop_if);
uvm_config_db#(virtual otbn_alu_bignum_if)::set(
null, "*.env", "alu_bignum_vif",
dut.u_otbn_core.u_otbn_alu_bignum.i_otbn_alu_bignum_if);
uvm_config_db#(virtual otbn_controller_if)::set(
null, "*.env", "controller_vif",
dut.u_otbn_core.u_otbn_controller.i_otbn_controller_if);
uvm_config_db#(virtual otbn_mac_bignum_if)::set(
null, "*.env", "mac_bignum_vif",
dut.u_otbn_core.u_otbn_mac_bignum.i_otbn_mac_bignum_if);
uvm_config_db#(virtual otbn_rf_base_if)::set(
null, "*.env", "rf_base_vif",
dut.u_otbn_core.u_otbn_rf_base.i_otbn_rf_base_if);
uvm_config_db#(virtual otbn_rnd_if)::set(
null, "*.env", "rnd_vif",
dut.u_otbn_core.u_otbn_rnd.i_otbn_rnd_if);
// Instantiate mem_bkdr_util objects to allow access to IMEM and DMEM
//
// Note that n_bits is the number of bits in the memory, including ECC check bits.
imem_util = new(.name ("imem_util"),
.path ({"tb.dut.u_imem.u_prim_ram_1p_adv.",
"u_mem.gen_generic.u_impl_generic.mem"}),
.depth (ImemSizeByte / 4),
.n_bits (ImemSizeByte / 4 * 39),
.err_detection_scheme (mem_bkdr_util_pkg::EccInv_39_32));
// DMEM is twice as big as the bus-accessible part
dmem_util = new(.name ("dmem_util"),
.path ({"tb.dut.u_dmem.u_prim_ram_1p_adv.",
"u_mem.gen_generic.u_impl_generic.mem"}),
.depth (DmemSizeByte / 32),
.n_bits (DmemSizeByte / 32 * 312),
.err_detection_scheme (mem_bkdr_util_pkg::EccInv_39_32));
uvm_config_db#(mem_bkdr_util)::set(null, "*.env", imem_util.get_name(), imem_util);
uvm_config_db#(mem_bkdr_util)::set(null, "*.env", dmem_util.get_name(), dmem_util);
$timeformat(-12, 0, " ps", 12);
run_test();
end
// Assertion controls for turning them on/off easily
// A hook to allow sequences to enable or disable the MatchingStatus_A assertion below. This is
// needed for sequences that trigger alerts (locking OTBN) without telling the model.
`DV_ASSERT_CTRL("otbn_status_assert_en", tb.MatchingStatus_A)
// We need to turn off DMEM related assertions in the tests where we force internals of DMEM to generate errors
`DV_ASSERT_CTRL("DMemAsserts", tb.dut.u_otbn_core.u_otbn_lsu.DMemRValidAfterReq)
`DV_ASSERT_CTRL("DMemAsserts", tb.dut.u_otbn_core.OnlyWriteLoadDataBaseWhenDMemValid_A)
`DV_ASSERT_CTRL("DMemAsserts", tb.dut.u_otbn_core.OnlyWriteLoadDataBignumWhenDMemValid_A)
endmodule