blob: 46000a5b8ef624485da01c693296495b44c3d2ce [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// ---------------------------------------------
// Alert sender receiver interface monitor
// ---------------------------------------------
class alert_monitor extends alert_esc_base_monitor;
`uvm_component_utils(alert_monitor)
`uvm_component_new
virtual task run_phase(uvm_phase phase);
super.run_phase(phase);
fork
alert_thread();
ping_thread();
reset_thread();
int_fail_thread();
alert_init_thread();
wait_ping_thread();
join_none
endtask : run_phase
virtual task reset_thread();
under_reset = 1;
forever begin
@(negedge cfg.vif.rst_n);
under_reset = 1;
cfg.alert_init_done = 0;
@(posedge cfg.vif.rst_n);
// Reset signals at posedge rst_n to avoid race condition at negedge rst_n
reset_signals();
end
endtask : reset_thread
virtual task alert_init_thread();
wait_alert_init_done();
forever @(posedge cfg.vif.rst_n) begin
wait_alert_init_done();
end
endtask : alert_init_thread
// This task called inside forever loop in the `reset_thread` task, intended to be a nonblocking
// process. However, it can still block alert handshake via the `cfg.alert_init_done` flag.
// To handle the scenario where reset is issued during alert init, we use a fork join_any thread.
virtual task wait_alert_init_done();
fork begin
fork
begin
wait (cfg.vif.monitor_cb.alert_tx_final.alert_p ==
cfg.vif.monitor_cb.alert_tx_final.alert_n);
wait (cfg.vif.monitor_cb.alert_tx_final.alert_p !=
cfg.vif.monitor_cb.alert_tx_final.alert_n);
`uvm_info("alert_monitor", "Alert init done!", UVM_HIGH)
cfg.alert_init_done = 1;
under_reset = 0;
end
begin
@(negedge cfg.vif.rst_n);
end
// Clear `under_reset` and `alert_init_done` when en_alert_lpg is on, because alert_sender
// can still send alerts, and alert_handler should ignore the alert_tx request.
begin
wait (cfg.en_alert_lpg == 1);
cfg.alert_init_done = 1;
under_reset = 0;
end
join_any
disable fork;
end
join
endtask
virtual task ping_thread();
alert_esc_seq_item req;
bit ping_p, alert_p;
forever @(cfg.vif.monitor_cb) begin
if (ping_p != cfg.vif.monitor_cb.alert_rx_final.ping_p) begin
if (!cfg.en_alert_lpg) begin
cfg.under_ping_handshake = 1;
req = alert_esc_seq_item::type_id::create("req");
req.alert_esc_type = AlertEscPingTrans;
fork
begin : isolation_fork
fork
begin : wait_ping_timeout
repeat (cfg.ping_timeout_cycle - 1) @(cfg.vif.monitor_cb);
req.ping_timeout = 1'b1;
end
begin : wait_ping_handshake
// In case there is an alert happened before ping.
if (alert_p != 0) wait_alert_complete();
// TODO: could use "wait_alert()" but right now scb needs to be cycle accurate to
// predict esc_cnt.
while (cfg.vif.alert_tx_final.alert_p !== 1'b1) @(cfg.vif.monitor_cb);
req.alert_handshake_sta = AlertReceived;
wait_ack();
req.alert_handshake_sta = AlertAckReceived;
cfg.under_ping_handshake = 0;
end
begin
wait (under_reset || cfg.en_alert_lpg);
end
join_any
// Wait 1ps in case 'wait_ping_handshake' and 'wait_ping_timeout' thread finish at
// the same clock cycle, and give 1ps to make sure both threads are able to update
// info.
if (!under_reset) #1ps;
disable fork;
end : isolation_fork
join
`uvm_info("alert_monitor", $sformatf("[%s]: handshake status is %s",
req.alert_esc_type.name(), req.alert_handshake_sta.name()), UVM_HIGH)
if (!under_reset && !cfg.en_alert_lpg) begin
alert_esc_port.write(req);
if (cfg.en_cov && cfg.en_ping_cov) cov.m_alert_trans_cg.sample(req.alert_esc_type);
// Spurious alert error, can only happen one clock after timeout. Detail please see
// discussion on Issue #2321.
if (req.ping_timeout && req.alert_handshake_sta == AlertReceived) begin
@(cfg.vif.monitor_cb);
if (cfg.vif.alert_rx_final.ack_p == 1'b1) alert_esc_port.write(req);
end
end
cfg.under_ping_handshake = 0;
end
end
ping_p = cfg.vif.monitor_cb.alert_rx_final.ping_p;
alert_p = cfg.vif.monitor_cb.alert_tx_final.alert_p;
end
endtask : ping_thread
virtual task alert_thread();
alert_esc_seq_item req;
bit alert_p, ping_p;
forever @(cfg.vif.monitor_cb) begin
// If ping and alert are triggered at the same clock cycle, the alert is considered a ping
// response
if (!alert_p && is_valid_alert() && !cfg.under_ping_handshake &&
ping_p == cfg.vif.monitor_cb.alert_rx_final.ping_p) begin
if (cfg.en_lpg_cov && cfg.en_cov) begin
cov.m_alert_lpg_cg.sample(cfg.en_alert_lpg);
end
if (!cfg.en_alert_lpg && !cfg.under_reset) begin
req = alert_esc_seq_item::type_id::create("req");
req.alert_esc_type = AlertEscSigTrans;
req.alert_handshake_sta = AlertReceived;
// Write alert packet to scb when receiving alert signal
alert_esc_port.write(req);
// Write alert packet to sequence for auto alert responses.
req_analysis_port.write(req);
// Duplicate req for writing alert packet at the end of alert handshake
`downcast(req, req.clone())
fork
begin : isolation_fork
fork
begin : alert_timeout
repeat (cfg.handshake_timeout_cycle) @(cfg.vif.monitor_cb);
req.ping_timeout = 1'b1;
end
begin : wait_alert_handshake
wait_ack();
req.alert_handshake_sta = AlertAckReceived;
wait_alert_complete();
req.alert_handshake_sta = AlertComplete;
wait_ack_complete();
req.alert_handshake_sta = AlertAckComplete;
end
begin
wait (under_reset || cfg.en_alert_lpg);
end
join_any
disable fork;
end : isolation_fork
join
`uvm_info("alert_monitor", $sformatf("[%s]: handshake status is %s",
req.alert_esc_type.name(), req.alert_handshake_sta.name()), UVM_HIGH)
if (!under_reset && !cfg.en_alert_lpg) alert_esc_port.write(req);
if (cfg.en_cov) begin
cov.m_alert_handshake_complete_cg.sample(req.alert_esc_type, req.alert_handshake_sta);
if (cfg.en_ping_cov) cov.m_alert_trans_cg.sample(req.alert_esc_type);
end
end
end
ping_p = cfg.vif.monitor_cb.alert_rx_final.ping_p;
alert_p = cfg.vif.monitor_cb.alert_tx_final.alert_p;
end
endtask : alert_thread
virtual task int_fail_thread();
alert_esc_seq_item req;
bit prev_err;
forever @(cfg.vif.monitor_cb) begin
// use prev_err to exclude the async clk skew
if (!under_reset && !cfg.en_alert_lpg && is_sig_int_err() &&
(!cfg.is_async || prev_err != 0)) begin
fork
begin
req = alert_esc_seq_item::type_id::create("req");
req.alert_esc_type = AlertEscIntFail;
alert_esc_port.write(req);
end
join_none;
end
prev_err = is_sig_int_err();
end
endtask : int_fail_thread
virtual task wait_ping_thread();
forever begin
alert_esc_seq_item req = alert_esc_seq_item::type_id::create("req");
logic ping_p_value;
req.alert_esc_type = AlertEscPingTrans;
wait (!under_reset && !cfg.en_alert_lpg);
`DV_SPINWAIT_EXIT(
ping_p_value = cfg.vif.monitor_cb.alert_rx_final.ping_p;
while (cfg.vif.monitor_cb.alert_rx_final.ping_p === ping_p_value) begin
ping_p_value = cfg.vif.monitor_cb.alert_rx_final.ping_p;
@(cfg.vif.monitor_cb);
end
req_analysis_port.write(req);,
wait (under_reset || cfg.en_alert_lpg);)
@(cfg.vif.monitor_cb);
end
endtask
virtual task wait_alert();
while (cfg.vif.monitor_cb.alert_tx_final.alert_p !== 1'b1) @(cfg.vif.monitor_cb);
endtask : wait_alert
virtual task wait_alert_complete();
while (cfg.vif.monitor_cb.alert_tx_final.alert_p !== 1'b0) @(cfg.vif.monitor_cb);
endtask : wait_alert_complete
virtual task wait_ack();
while (cfg.vif.monitor_cb.alert_rx_final.ack_p !== 1'b1) @(cfg.vif.monitor_cb);
endtask : wait_ack
virtual task wait_ack_complete();
while (cfg.vif.monitor_cb.alert_rx_final.ack_p !== 1'b0) @(cfg.vif.monitor_cb);
endtask : wait_ack_complete
virtual function bit is_sig_int_err();
return cfg.vif.monitor_cb.alert_tx_final.alert_p === cfg.vif.monitor_cb.alert_tx_final.alert_n;
endfunction : is_sig_int_err
virtual function bit is_valid_alert();
return cfg.vif.monitor_cb.alert_tx_final.alert_p && !cfg.vif.monitor_cb.alert_tx_final.alert_n;
endfunction : is_valid_alert
// end phase when no alert is triggered
virtual task monitor_ready_to_end();
if (!cfg.bypass_alert_ready_to_end_check) begin
forever begin
@(cfg.vif.monitor_cb.alert_tx_final.alert_p);
ok_to_end = !cfg.vif.monitor_cb.alert_tx_final.alert_p &&
cfg.vif.monitor_cb.alert_tx_final.alert_n;
end
end
endtask
endclass : alert_monitor