blob: c36bdaa4db57e309a137013b47699ec30e8f766b [file]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class otbn_scoreboard extends cip_base_scoreboard #(
.CFG_T(otbn_env_cfg),
.RAL_T(otbn_reg_block),
.COV_T(otbn_env_cov)
);
`uvm_component_utils(otbn_scoreboard)
uvm_tlm_analysis_fifo #(otbn_model_item) model_fifo;
uvm_tlm_analysis_fifo #(otbn_trace_item) trace_fifo;
// Queues of trace items copied from the model_fifo and trace_fifo, respectively. These get paired
// up in pop_trace_queues(). process_model_fifo and process_trace_fifo copy items from the
// (blocking) FIFOs to the queues and then call pop_trace_queues(), which avoids having to poll
// the (non-blocking) queues directly.
otbn_model_item iss_trace_queue[$];
otbn_trace_item rtl_trace_queue[$];
// Each time we see a read on the A side, we set an entry in the exp_read_values associative
// array. There are three situations that we need to deal with:
//
// (1) A read from a register that can only be written by SW, so the RAL's mirrored value should
// already be right.
//
// (2) A read from a register that can be updated by HW, but where we know what the value should
// be.
//
// (3) A read from a register that can be updated by HW and where we have no idea what the value
// should be.
//
// To handle these three possibilities, we store a 34-bit otbn_exp_read_data_t value with fields
// upd, chk and val. Here upd and chk are 1 bit each and val is 32 bits.
//
// If upd is false, we ignore the other fields and do not update the RAL's mirrored value.
// Otherwise we update RAL's mirrored value to match the data from the D channel. If both upd and
// chk are True, we raise an error if the data from the D channel doesn't match val.
//
// To connect up the A-side request with a subsequent D-side response, we use an associative
// array, mapping the transaction source ID (a_source in the TL transaction) to an expected value.
otbn_exp_read_data_t exp_read_values [tl_source_t];
// A flag that tracks the fact that we've seen a TL write to the CMD register that we expect to
// start OTBN. We track this because we derive the "start" signal in the model from an internal
// DUT signal, so need to make sure it stays in sync with the TL side.
//
// If false, there are no transactions pending. This gets set in process_tl_addr when we see a
// write to CMD. This runs on a posedge of the clock. We expect to see the model start on the
// following negedge. When we process a change to model status that shows things starting (in
// process_model_fifo), we check that the flag is set and then clear it. When setting the flag, we
// queue a check to run on the following posedge of the clock to make sure the flag isn't still
// set.
bit pending_start_tl_trans = 1'b0;
// The mirrored STATUS register from the ISS.
bit [7:0] model_status;
// The "locked" field is used to track whether OTBN is "locked". For most operational state
// tracking, we go through the ISS, but OTBN can become locked without actually starting an
// operation (for example, there might be a malformed TL transaction). We spot that sort of thing
// here, updating the "locked" flag if either the ISS says to do so OR if we see something 'out of
// band'.
bit locked = 1'b0;
// A counter for the number of fatal alerts that we've seen. This gets incremented by on_alert().
int unsigned fatal_alert_count = 0;
// A flag showing that we've seen a recoverable alert
bit recov_alert_seen = 1'b0;
// Flags saying that we expect alerts. These might be set (slightly) after an alert comes in and
// that's ok. We'll marry up the flags and the alerts themselves in the check phase. The
// *_alert_expected bits get cleared when we see the expected alert. The fatal_alert_allowed bit
// gets set at the same time as fatal_alert_expected but is not cleared: this represents the fact
// that fatal alerts are sent continuously.
bit fatal_alert_expected = 1'b0;
bit fatal_alert_allowed = 1'b0;
bit recov_alert_expected = 1'b0;
// A counter giving how many "alert wait counters" are currently running. The scoreboard should
// object to ending the run phase if this is nonzero.
int unsigned num_alert_wait_counters = 0;
`uvm_component_new
function void build_phase(uvm_phase phase);
super.build_phase(phase);
model_fifo = new("model_fifo", this);
trace_fifo = new("trace_fifo", this);
// Disable alert checking in cip_base_scoreboard (we've got a different system set up which
// handles the fact that we might not know that an alert should happen until after the fact).
do_alert_check = 1'b0;
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
endfunction
task run_phase(uvm_phase phase);
super.run_phase(phase);
fork
process_model_fifo();
process_trace_fifo();
join_none
endtask
virtual function void reset(string kind = "HARD");
super.reset(kind);
// Delete all the entries in exp_read_values: if there are D transactions pending after an A
// transaction, the reset will have caused them to be forgotten.
exp_read_values.delete();
// Clear the locked bit (this is modelling RTL state that should be cleared on reset)
locked = 1'b0;
// Clear all alert counters and flags
fatal_alert_count = 0;
fatal_alert_expected = 1'b0;
fatal_alert_allowed = 1'b0;
recov_alert_expected = 1'b0;
// Reset any state tracking in the coverage collector
if (cfg.en_cov) cov.on_reset();
endfunction
task process_tl_access(tl_seq_item item, tl_channels_e channel, string ral_name);
case (channel)
AddrChannel: process_tl_addr(item);
DataChannel: process_tl_data(item);
default: `uvm_fatal(`gfn, $sformatf("Invalid channel: %0h", channel))
endcase
endtask
task process_tl_addr(tl_seq_item item);
uvm_reg csr;
uvm_reg_addr_t masked_addr, aligned_addr;
operational_state_e state;
otbn_exp_read_data_t exp_read_data = '{upd: 1'b0, chk: 'x, val: 'x};
state = get_operational_state(status_e'(model_status));
aligned_addr = ral.get_word_aligned_addr(item.a_addr);
masked_addr = aligned_addr & ral.get_addr_mask();
// If coverage is enabled, track the write.
if (cfg.en_cov) cov.on_tl_write(masked_addr, item.a_data, state);
// Is this a write to memory (either DMEM or IMEM)?
if (item.is_write()) begin
uvm_mem mem = ral.default_map.get_mem_by_offset(aligned_addr);
if (mem != null) begin
uvm_reg_addr_t base = mem.get_offset(0, ral.default_map);
`DV_CHECK_FATAL(base <= masked_addr)
process_tl_mem_write(mem, masked_addr - base, item);
end
end
csr = ral.default_map.get_reg_by_offset(aligned_addr);
// csr might be null and that's ok (it just means an access to an unmapped register, which will
// have no effect)
if (csr == null)
return;
if (item.is_write()) begin
// Track coverage for write accesses to external CSRs over TL-UL.
if (cfg.en_cov) begin
cov.on_ext_csr_access(csr, otbn_env_pkg::AccessSoftwareWrite, item.a_data, state);
end
// If this is a write, update the RAL model
void'(csr.predict(.value(item.a_data), .kind(UVM_PREDICT_WRITE), .be(item.a_mask)));
case (csr.get_name())
// Spot writes to the "cmd" register that tell us to start
"cmd": begin
// We start any operation when we see a write of the related command and we are currently
// in the IDLE operational state. See the comment above pending_start_tl_trans to see how
// this tracking works.
bit cmd_operation = item.a_data[7:0] inside {otbn_pkg::CmdSecWipeImem,
otbn_pkg::CmdSecWipeDmem,
otbn_pkg::CmdExecute};
if (cmd_operation && (model_status == otbn_pkg::StatusIdle)) begin
// Set a flag: we're expecting the model to start on the next posedge. Also, spawn off a
// checking thread that will make sure the flag has been cleared again by the following
// posedge (or the one after that in the case of memory secure wipe operations).
// Note that the reset() method is only called in the DV base class on the
// following posedge of rst_n, so we have to check whether we're still in reset here.
pending_start_tl_trans = 1'b1;
fork begin
repeat (2) @(cfg.clk_rst_vif.cb);
`DV_CHECK_FATAL(!cfg.clk_rst_vif.rst_n || !pending_start_tl_trans,
"Model ignored a write to the CMD register.")
end
join_none;
end
end
"alert_test": begin
if (item.is_write && (item.a_data[0])) begin
expect_alert("fatal");
end
if (item.is_write && (item.a_data[1])) begin
expect_alert("recov");
end
end
"ctrl": begin
// Let model know that CTRL register has changed. This is not ideal since scoreboard
// ideally supposed be a passive component. Although we are still not affecting DUT in
// any way while doing this and an alternative way would include catching this register
// write in testbench level and sending a signal to otbn_core_model, which is not ideal
// as well.
if (item.is_write) begin
cfg.model_agent_cfg.vif.set_software_errs_fatal(item.a_data[0]);
end
end
default: begin
// No other special behaviour for writes
end
endcase
return;
end
// Otherwise, this is a read transaction. Fill in an otbn_exp_read_data_t struct appropriately.
case (csr.get_name())
"intr_state": begin
// Interrupt state register.
//
// TODO: Track this more precisely. We know that it should latch !status.busy if intr_enable
// is set.
exp_read_data = '{upd: 1'b1, chk: 1'b0, val: 'x};
end
"status": begin
// Status register. If we're locked, this should read 0xff. Otherwise, fall back to the
// expected operational state (IDLE = 0; BUSY_EXECUTE = 1; BUSY_SEC_WIPE_DMEM = 2;
// BUSY_SEC_WIPE_IMEM = 3).
//
// TODO: Track states other than IDLE and BUSY_EXECUTE.
exp_read_data = '{upd: 1'b1, chk: 1'b1, val: locked ? 32'hff : model_status};
end
"err_bits": begin
// Error bitfield
//
// TODO: Maybe this could be tracked more precisely. It should only update when an operation
// finishes.
exp_read_data = '{upd: 1'b1, chk: 1'b0, val: 'x};
end
"fatal_alert_cause": begin
// Bitfield for the cause of a fatal alert
//
// TODO: Maybe this could be tracked more precisely. It should only update when a fatal
// alert is signalled.
exp_read_data = '{upd: 1'b1, chk: 1'b0, val: 'x};
end
"insn_cnt": begin
// Instruction count
//
// TODO: Track this properly. We've got the magic number on the insn_cnt_if interface.
exp_read_data = '{upd: 1'b1, chk: 1'b0, val: 'x};
end
default: begin
// Other registers cannot be updated by the hardware, so don't need any special handling
// here. The registers that aren't write-only are: intr_enable and cmd.
end
endcase
// There shouldn't be an existing entry in exp_read_values for a_source: if there is, then the
// host side must have sent two messages for the same source without waiting for a response,
// violating the TL protocol.
`DV_CHECK_FATAL(!exp_read_values.exists(item.a_source))
exp_read_values[item.a_source] = exp_read_data;
endtask
task process_tl_data(tl_seq_item item);
uvm_reg csr;
uvm_reg_addr_t csr_addr;
otbn_exp_read_data_t exp_read_data;
// The data-channel response to a write is just an ack, which isn't particularly interesting.
// Check for integrity errors (which should lock the block), but otherwise there's nothing to
// do.
if (item.is_write()) begin
if (item.d_error) locked = 1'b1;
return;
end
// We're also only interested in registers; the scoreboard doesn't explicitly model memories in
// the RAL. Look to see if this is a valid register address. If not, it was to a memory and we
// can ignore it.
csr_addr = ral.get_word_aligned_addr(item.a_addr);
csr = ral.default_map.get_reg_by_offset(csr_addr);
if (csr == null)
return;
// Track coverage for read accesses through the bus to external CSRs.
if (cfg.en_cov) begin
cov.on_ext_csr_access(csr, otbn_env_pkg::AccessSoftwareRead, item.d_data,
get_operational_state(status_e'(model_status)));
end
// Look up the expected read data for item and then clear it (to get a quick error if something
// has come unstuck and we see two replies for a single addr + source combo)
`DV_CHECK_FATAL(exp_read_values.exists(item.a_source))
exp_read_data = exp_read_values[item.a_source];
exp_read_values.delete(item.a_source);
if (exp_read_data.upd) begin
if (exp_read_data.chk) begin
// This is a value that can be written by HW, but we think we know what the value should be
`DV_CHECK_EQ(item.d_data, exp_read_data.val,
$sformatf("value for register %0s", csr.get_full_name()))
end
// Update the RAL model to match the value we've just read from HW
void'(csr.predict(.value(item.d_data), .kind(UVM_PREDICT_READ)));
end else begin
// We don't predict any sort of hardware backdoor updates to this register so the mirrored
// value in the RAL should be correct. Is it?
`DV_CHECK_EQ(item.d_data, csr.get_mirrored_value(),
$sformatf("value for auto-predicted register %0s", csr.get_full_name()))
end
endtask
// Called on each write to memory (on the A side). This is responsible for updating the model of
// the CRC register.
function void process_tl_mem_write(uvm_mem mem, bit [31:0] offset, tl_seq_item item);
bit is_imem;
logic [14:0] mem_idx;
logic [47:0] crc_item;
uvm_reg_data_t old_crc;
bit [31:0] new_crc;
// Ignore any partial or misaligned writes: these don't update the CRC.
if ((item.a_addr & 3) || (item.a_size != 2))
return;
// Build the 48-bit value that's supposed to be added to the CRC. This is built as the triple
// {imem, idx, wdata}, where imem is a 1-bit value showing whether this is IMEM, idx is the
// index of the 32-bit word in memory, zero-extended to 15 bits, and wdata is the 32-bit word
// that was written.
is_imem = mem.get_name() == "imem";
mem_idx = offset >> 2;
crc_item = {is_imem, mem_idx, item.a_data};
`uvm_info(`gfn,
$sformatf("Updating CRC with memory write: {%0d, 0x%0h, 0x%0h} = 0x%012h",
is_imem, mem_idx, item.a_data, crc_item),
UVM_HIGH);
`DV_CHECK_FATAL(!$isunknown(crc_item))
// Grab the old modelled CRC value (which we store in the RAL as the predicted value for
// LOAD_CHECKSUM). This should be a 32-bit number and shouldn't have any unknown bits.
old_crc = ral.load_checksum.checksum.get_mirrored_value();
`DV_CHECK_FATAL(old_crc >> 32 == 0)
`DV_CHECK_FATAL(!$isunknown(old_crc))
new_crc = cfg.model_agent_cfg.vif.step_crc(crc_item, old_crc);
`uvm_info(`gfn,
$sformatf("CRC step: 0x%08h -> 0x%08h (or 0x%08h -> 0x%08h)",
old_crc, new_crc, old_crc ^ {32{1'b1}}, new_crc ^ {32{1'b1}}),
UVM_HIGH);
if (cfg.en_cov) begin
cov.on_mem_write(mem, offset, item.a_data, get_operational_state(status_e'(model_status)));
end
// Predict the resulting value of LOAD_CHECKSUM
`DV_CHECK_FATAL(ral.load_checksum.checksum.predict(.value(new_crc), .kind(UVM_PREDICT_READ)))
endfunction
task process_model_fifo();
otbn_model_item item;
forever begin
model_fifo.get(item);
`uvm_info(`gfn, $sformatf("received model transaction:\n%0s", item.sprint()), UVM_HIGH)
case (item.item_type)
OtbnModelStatus: begin
bit was_executing = model_status inside {otbn_pkg::StatusBusyExecute,
otbn_pkg::StatusBusySecWipeInt};
bit is_busy = otbn_pkg::is_busy_status(status_e'(item.status));
// Did the status change happen due to a reset? If so, reset the model status as well.
if (item.rst_n !== 1'b1) model_status = item.status;
// Has the status changed from idle to busy? If so, we should have seen a write to the
// command register on the previous posedge. See comment above pending_start_tl_trans for
// the details.
if (model_status == otbn_pkg::StatusIdle && is_busy) begin
`DV_CHECK_FATAL(pending_start_tl_trans,
"Saw start transaction without corresponding write to CMD")
pending_start_tl_trans = 1'b0;
end
// Has the status changed to locked? This should be accompanied by a fatal alert
if (item.status == otbn_pkg::StatusLocked) begin
expect_alert("fatal");
end
// Has the status changed from executing to idle with a nonzero err_bits?
// If so, we should see a recoverable alert. Note that we are not expecting to catch
// recoverable alert when we do SecWipe of any kind.
if (was_executing && item.status == otbn_pkg::StatusIdle && item.err_bits != 0) begin
expect_alert("recov");
end
model_status = item.status;
if (cfg.en_cov) begin
cov.on_state_change(get_operational_state(status_e'(model_status)));
end
end
OtbnModelInsn: begin
// The model agent's monitor should be configured to only emit OtbnModelInsn items if
// coverage is enabled.
`DV_CHECK_FATAL(cfg.en_cov)
// We don't expect any instructions unless we're currently running something.
`DV_CHECK_EQ_FATAL(model_status, 1 /* BUSY_EXECUTE */,
"Saw instruction when not in BUSY_EXECUTE operational state.")
iss_trace_queue.push_back(item);
pop_trace_queues();
end
default: `uvm_fatal(`gfn, $sformatf("Bad item type %0d", item.item_type))
endcase
end
endtask
task process_trace_fifo();
otbn_trace_item item;
forever begin
trace_fifo.get(item);
// The trace monitor should be configured to only emit items if coverage is enabled. Here, we
// wait on trace_fifo either way, to avoid a massive memory leak if something comes unstuck.
// However, we check that we were actually expecting things if anything comes through.
`DV_CHECK_FATAL(cfg.en_cov)
rtl_trace_queue.push_back(item);
pop_trace_queues();
end
endtask
// Pop from iss_trace_queue and rtl_trace_queue while they both contain an entry
function void pop_trace_queues();
while ((iss_trace_queue.size() > 0) && (rtl_trace_queue.size() > 0)) begin
otbn_model_item iss_item = iss_trace_queue.pop_front();
otbn_trace_item rtl_item = rtl_trace_queue.pop_front();
cov.on_insn(iss_item, rtl_item);
end
endfunction
// Wait up to max_wait cycles for us to decide that we should be expecting the named alert. While
// this is running, num_alert_wait_counters will be positive which should mean that we object to
// end of the run phase.
protected task wait_for_expected_alert(string alert_name, int unsigned max_wait);
bit expected = 1'b0;
num_alert_wait_counters++;
for (int unsigned i = 0; i < max_wait; i++) begin
// Note that if we're waiting for a status change that implies a recoverable alert, we'll also
// accept one that implies a fatal alert. The reason is that you can trigger a recoverable
// alert and then, on the next cycle, a fatal alert. You'll only see one status change (from
// busy to locked), so the scoreboard has no way of knowing whether the recoverable alert was
// supposed to have happened. In practice, I suspect we don't care: if a fatal alert was
// raised, a recoverable alert doesn't really matter.
expected = ((alert_name == "recov") && recov_alert_expected) || fatal_alert_allowed;
if (expected || cfg.under_reset) begin
break;
end
@(cfg.clk_rst_vif.cb);
end
num_alert_wait_counters--;
if (cfg.under_reset) begin
// If we're in reset, exit immediately. No need to check anything or update any state, except
// that we have not seen a recoverable alert since the last reset.
recov_alert_seen = 1'b0;
return;
end
if (!expected) begin
`uvm_fatal(`gfn,
$sformatf({"A %0s alert arrived %0d cycles ago and ",
"we still don't think it should have done."},
alert_name, max_wait))
end
if (alert_name == "fatal") begin
// If this is a fatal alert, check the counter is positive (otherwise something has gone really
// wrong), but leave it unchanged.
`DV_CHECK_FATAL((fatal_alert_count > 0) && fatal_alert_allowed)
end else begin
// Otherwise this was a recoverable alert. Check that the seen flag is set (this should have
// happened just before we were originally called) and then wait a cycle of the other clock
// before clearing it. This extra cycle is to allow the wait_for_alert() task which should be
// running at the same time to see the flag set.
`DV_CHECK_FATAL(recov_alert_seen)
@(cfg.m_alert_agent_cfgs[alert_name].vif.receiver_cb);
recov_alert_seen = 1'b0;
end
endtask
// Wait up to max_wait cycles on the alert interface for the named alert. While this is running,
// num_alert_wait_counters will be positive which should mean that we object to end of the run
// phase.
protected task wait_for_alert(string alert_name, int unsigned max_wait);
bit seen = 1'b0;
num_alert_wait_counters++;
for (int unsigned i = 0; i < max_wait; i++) begin
seen = (alert_name == "recov") ? recov_alert_seen : (fatal_alert_count > 0);
if (seen || cfg.under_reset) begin
break;
end
@(cfg.m_alert_agent_cfgs[alert_name].vif.receiver_cb);
end
num_alert_wait_counters--;
if (cfg.under_reset) begin
// If we're in reset, exit immediately. No need to check anything or update any state
return;
end
if (!seen) begin
`uvm_fatal(`gfn,
$sformatf({"We saw a STATUS change %0d cycles ago that implied we'd ",
"get a %0s alert but it still hasn't arrived."},
max_wait, alert_name))
end
if (alert_name == "fatal") begin
// If this was a fatal alert then check the counter is positive and that the expected flag is
// set. Clear the "expected" flag, but not "allowed" (so that we won't see a problem when the
// fatal alert is re-triggered).
`DV_CHECK_FATAL((fatal_alert_count > 0) && fatal_alert_expected)
fatal_alert_expected = 1'b0;
end else begin
// Otherwise this was a recoverable alert. Check that the expected flag is set and then wait a
// cycle of the other clock before clearing it (to allow the wait_for_expected_alert() task
// that should be running at the same time to see it set).
`DV_CHECK_FATAL(recov_alert_expected)
@(cfg.clk_rst_vif.cb);
recov_alert_expected = 1'b0;
end
endtask
// Overridden from cip_base_scoreboard. Called when an alert happens.
function void on_alert(string alert_name, alert_esc_agent_pkg::alert_esc_seq_item item);
`uvm_info(`gfn, $sformatf("on_alert(%0s)", alert_name), UVM_HIGH)
// An alert has just come in. Increment counter / set flag showing that it has been seen.
if (alert_name == "fatal") begin
fatal_alert_count += 1;
end else if (alert_name == "recov") begin
// We're not supposed to see a second recoverable alert while we're still "waiting to expect"
// the previous one. If that happens then recov_alert_seen will be set.
`DV_CHECK_FATAL(!recov_alert_seen, "Double recoverable alert seen")
recov_alert_seen = 1'b1;
end
else `uvm_fatal(`gfn, $sformatf("Bad alert name: %0s", alert_name));
// Wait up to 400 cycles for the prediction to come through (giving up on reset). This is a
// long time, but that's needed because an error that comes in when we're running will cause an
// immediate alert but the status change will only appear after secure wipe is done. A secure
// wipe includes a reseed of the URND, and the time for that depends on the EDN. Note that
// the alert might be here already, in which case `wait_for_expected_alert` will take zero time.
fork
wait_for_expected_alert(alert_name, 400);
join_none
endfunction
// Called when we see something that makes us think an alert should happen
protected function void expect_alert(string alert_name);
int unsigned max_delay;
`uvm_info(`gfn, $sformatf("expect_alert(%0s)", alert_name), UVM_HIGH)
if (alert_name == "fatal") begin
fatal_alert_expected = 1'b1;
fatal_alert_allowed = 1'b1;
end else if (alert_name == "recov") begin
// We're not supposed to see an event that expects a second recoverable alert while we're
// still waiting for the previous one to arrive. If that happens then recov_alert_expected
// will be set.
`DV_CHECK_FATAL(!recov_alert_expected, "Double recoverable alert expect with no alert")
recov_alert_expected = 1'b1;
end
else `uvm_fatal(`gfn, $sformatf("Bad alert name: %0s", alert_name));
// Otherwise, we haven't seen the corresponding alert yet. Wait for a bit on the (slower) alert
// interface clock for the alert to come through, giving up on reset. The wait is calculated as
//
// ack_delay_max +
// ack_stable_max +
// arbitrary delay for alert_p to go down +
// 2 cycles of main clock
//
// We model the 3rd and 4th term as 10 slow clock cycles in total, giving:
max_delay = (cfg.m_alert_agent_cfgs[alert_name].ack_delay_max +
cfg.m_alert_agent_cfgs[alert_name].ack_stable_max +
10);
fork
wait_for_alert(alert_name, max_delay);
join_none
endfunction
virtual function void phase_ready_to_end(uvm_phase phase);
if (phase.get_name() != "run") return;
// We cannot end while num_alert_wait_counters is positive (which means that wait_for_alert
// and/or wait_for_expected_alert are running). Wait until they are finished to make sure that
// we don't fail to notice a missing (or unjustified) alert.
if (num_alert_wait_counters != 0) begin
phase.raise_objection(this, "num_alert_wait_counters != 0");
fork
begin
wait (num_alert_wait_counters == 0);
phase.drop_objection(this);
end
join_none
end
endfunction
virtual function void mem_compare(string ral_name, uvm_reg_addr_t addr, tl_seq_item item);
// We can only compare the contents inside memories when the OTBN is not busy executing
// or wiping the memories
if (model_status inside {otbn_pkg::StatusIdle, otbn_pkg::StatusBusySecWipeInt}) begin
super.mem_compare(ral_name, addr, item);
// Otherwise the contents will read out as zeros so compare expected memory with zero.
end else begin
`DV_CHECK_EQ(item.d_data, '0, "Memory read out nonzero value while OTBN is not IDLE")
end
endfunction
virtual task process_mem_read(tl_seq_item item, string ral_name);
super.process_mem_read(item, ral_name);
if (model_status == 'b1 && item.d_data != 0) begin
`uvm_error(`gfn, "read data is non zero when memory is accessed while otbn is busy")
end
endtask
endclass