blob: c3f158136fc58858628a785e81b87af1e875b893 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// ---------------------------------------------
// TileLink interface monitor
// ---------------------------------------------
class tl_monitor extends dv_base_monitor#(
.ITEM_T (tl_seq_item),
.CFG_T (tl_agent_cfg),
.COV_T (tl_agent_cov)
);
tl_seq_item pending_a_req[bit [SourceWidth - 1 : 0]];
string agent_name;
uvm_phase run_phase_h;
// Sequence items for transactions on the A and D channels. Sampled on the positive clock edge.
//
// If cfg.synchronise_ports (disabled by default), use these by waiting on channel_dir_port. This
// gets updated just after we push to a_chan_port or d_chan_port, telling the reader which channel
// to read. That way, the reader can guarantee a sample order if an A and D transaction both
// appear at the same time.
//
// Otherwise, wait on a_chan_port and d_chan_port with two concurrent processes. A and D
// transactions that happen at the same time will be popped from the fifos in an indeterminate
// order.
uvm_analysis_port #(tl_channels_e) channel_dir_port;
uvm_analysis_port #(tl_seq_item) a_chan_port;
uvm_analysis_port #(tl_seq_item) d_chan_port;
// Sequence items for transactions on the A channel, sampled shortly after the positive clock
// edge. This is only used if cfg.device_can_rsp_on_same_cycle, in which case a sequence item for
// a transaction will appear on this port when sampled and on a_chan_port on the following clock
// edge.
uvm_analysis_port #(tl_seq_item) a_chan_same_cycle_rsp_port;
`uvm_component_utils(tl_monitor)
`uvm_component_new
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (cfg.synchronise_ports) begin
channel_dir_port = new("channel_dir_port", this);
end
a_chan_port = new("a_chan_port", this);
d_chan_port = new("d_chan_port", this);
if (cfg.device_can_rsp_on_same_cycle) begin
a_chan_same_cycle_rsp_port = new("a_chan_same_cycle_rsp_port", this);
end
endfunction : build_phase
virtual task run_phase(uvm_phase phase);
run_phase_h = phase;
wait_for_reset_done();
fork
ad_channels_thread();
reset_thread();
join_none
endtask : run_phase
virtual task wait_for_reset_done();
@(posedge cfg.vif.rst_n);
endtask : wait_for_reset_done
// on reset flush pending request
virtual task reset_thread();
forever begin
@(negedge cfg.vif.rst_n);
cfg.reset_asserted = 1'b1;
// on reset asserted sample pending request is present or not
if (cfg.en_cov) cov.m_pending_req_on_rst_cg.sample(pending_a_req.size() != 0);
@(posedge cfg.vif.rst_n);
cfg.reset_asserted = 1'b0;
pending_a_req.delete();
end
endtask : reset_thread
// Check the A and D channels for transactions. When a transaction is seen, push it to a_chan_port
// or d_chan_port, respectively. If cfg.synchronise_ports, also push the channel to
// channel_dir_port. If transactions are seen for both channels on a clock edge, push D then A.
//
// If the device isn't expected to respond combinatorially (cfg.device_can_rsp_on_same_cycle = 0),
// then both channels are sampled on the posedge of the clock. If the device *can* respond
// combinatorially, we delay sampling on the A side by a short time. The idea is that the A
// transaction will be asserted just after a clock edge and then D will be asserted some time
// afterwards. Sampling A in the middle of the cycle and then D at the following clock ensures
// that we see A before D (the order in which the events happened).
task ad_channels_thread();
// The most recently seen request on the A channel. Set to something non-null by successful
// calls to check_a_channel. Cleared back to null when the item is written to a_chan_port.
tl_seq_item a_chan_req_item;
forever begin
@(cfg.vif.mon_cb);
// This is just after the clock edge, so time to sample D and A channels.
// Handle the D side.
check_d_channel();
// Handle the A side. If cfg.device_can_rsp_on_same_cycle then we've actually done the
// sampling already. Otherwise, we should check now.
if (!cfg.device_can_rsp_on_same_cycle) begin
a_chan_req_item = check_a_channel(.immediate(1'b0));
end
if (a_chan_req_item != null) begin
a_chan_port.write(a_chan_req_item);
if (cfg.synchronise_ports) channel_dir_port.write(AddrChannel);
a_chan_req_item = null;
end
// We're done with this clock edge. If cfg.device_can_rsp_on_same_cycle, we now wait a short
// time afterwards and sample a little after the clockedge.
if (cfg.device_can_rsp_on_same_cycle) begin
`DV_SPINWAIT_EXIT(
#(cfg.time_a_valid_avail_after_sample_edge);,
@(cfg.vif.mon_cb) `uvm_fatal(`gfn, $sformatf(
"time_a_valid_avail_after_sample_edge (%0t) is over one cycle",
cfg.time_a_valid_avail_after_sample_edge)))
// It's a short time afterwards. Do the sampling!
a_chan_req_item = check_a_channel(.immediate(1'b1));
if (a_chan_req_item != null) begin
a_chan_same_cycle_rsp_port.write(a_chan_req_item);
end
end
end
endtask
// Check the A channel for a transaction
//
// If immediate is true, this is running just after a posedge of the clock, to allow us to capture
// same-cycle responses. In this case, it samples the immediate values of the signals.
//
// Otherwise, this is running on the posedge. It samples signals through the mon_cb clocking
// block.
//
// In either case, if a transaction is found then it returns a sequence item. Otherwise, it
// returns null.
function tl_seq_item check_a_channel(bit immediate);
logic a_valid = immediate ? cfg.vif.h2d.a_valid : cfg.vif.mon_cb.h2d.a_valid;
logic a_ready = immediate ? cfg.vif.d2h.a_ready : cfg.vif.mon_cb.d2h.a_ready;
if (a_valid && a_ready) begin
tl_seq_item req = tl_seq_item::type_id::create("req");
tl_seq_item cloned_req;
tlul_pkg::tl_h2d_t h2d = immediate ? cfg.vif.h2d : cfg.vif.mon_cb.h2d;
// Create a sequence item. Note: this is a field in the class, which ad_channels_thread() uses
// to pass the transaction to the relevant analysis port/ports.
req.a_addr = h2d.a_address;
req.a_opcode = h2d.a_opcode;
req.a_size = h2d.a_size;
req.a_param = h2d.a_param;
req.a_data = h2d.a_data;
req.a_mask = h2d.a_mask;
req.a_source = h2d.a_source;
req.a_user = h2d.a_user;
`uvm_info("tl_logging",
$sformatf("[%0s][a_chan] : %0s", agent_name, req.convert2string()),
UVM_HIGH)
if (cfg.en_cov) sample_outstanding_cov(req);
`downcast(cloned_req, req.clone());
`DV_CHECK_EQ_FATAL(cloned_req.a_source >> cfg.valid_a_source_width, 0)
`DV_CHECK_EQ_FATAL(pending_a_req.exists(cloned_req.a_source), 0)
pending_a_req[cloned_req.a_source] = cloned_req;
if (cfg.max_outstanding_req > 0 && cfg.vif.rst_n === 1) begin
if (pending_a_req.size() > cfg.max_outstanding_req) begin
`uvm_error(`gfn,
$sformatf("Number of pending a_req exceeds limit %0d", pending_a_req.size()))
end
if (cfg.en_cov) cov.m_max_outstanding_cg.sample(pending_a_req.size());
end
return req;
end
return null;
endfunction
// Checks the D channel for a transaction
//
// If a transaction is found, writes it to d_chan_port and clears the corresponding pending
// request.
function void check_d_channel();
if (cfg.vif.mon_cb.d2h.d_valid && cfg.vif.mon_cb.h2d.d_ready) begin
tl_seq_item rsp;
// A matching request must exist
`DV_CHECK_FATAL(pending_a_req.exists(cfg.vif.mon_cb.d2h.d_source),
$sformatf("Cannot find request matching d_source 0x%0x",
cfg.vif.mon_cb.d2h.d_source))
rsp = pending_a_req[cfg.vif.mon_cb.d2h.d_source];
pending_a_req.delete(cfg.vif.mon_cb.d2h.d_source);
rsp.d_opcode = cfg.vif.mon_cb.d2h.d_opcode;
rsp.d_data = cfg.vif.mon_cb.d2h.d_data;
rsp.d_source = cfg.vif.mon_cb.d2h.d_source;
rsp.d_param = cfg.vif.mon_cb.d2h.d_param;
rsp.d_error = cfg.vif.mon_cb.d2h.d_error;
rsp.d_sink = cfg.vif.mon_cb.d2h.d_sink;
rsp.d_size = cfg.vif.mon_cb.d2h.d_size;
rsp.d_user = cfg.vif.mon_cb.d2h.d_user;
`uvm_info("tl_logging",
$sformatf("[%0s][d_chan] : %0s", agent_name, rsp.convert2string()), UVM_HIGH)
d_chan_port.write(rsp);
if (cfg.synchronise_ports) channel_dir_port.write(DataChannel);
if (cfg.en_cov) cov.sample(rsp);
end
endfunction
// update ok_to_end to prevent sim finish when there is any pending item
virtual task monitor_ready_to_end();
forever begin
ok_to_end = (pending_a_req.size() == 0);
if (ok_to_end) wait(pending_a_req.size() > 0);
else wait(pending_a_req.size() == 0);
end
endtask
virtual function void report_phase(uvm_phase phase);
if (pending_a_req.size() > 0) begin
`uvm_error(get_full_name(), $sformatf(
"%0d items left at the end of sim", pending_a_req.size()))
foreach (pending_a_req[i]) begin
`uvm_info(get_full_name(), $sformatf("pending_a_req[%0d] = %0s",
i, pending_a_req[i].convert2string()), UVM_LOW)
end
end
endfunction : report_phase
// sample outstanding related coverage
virtual function void sample_outstanding_cov(tl_seq_item item);
bit is_outstanding_item_w_same_addr;
// check if same address has been used in more than 1 outstanding_item
foreach (pending_a_req[i]) begin
if (pending_a_req[i].a_addr == item.a_addr) begin
is_outstanding_item_w_same_addr = 1;
break;
end
end
// sample a same address used in more than 1 outstanding items, if it's null, design doesn't
// support outstanding items use the same address
if (cov.m_outstanding_item_w_same_addr_cov_obj != null) begin
cov.m_outstanding_item_w_same_addr_cov_obj.sample(is_outstanding_item_w_same_addr);
end
endfunction : sample_outstanding_cov
endclass : tl_monitor