blob: 84bf7d7e754dcce78d934a942decae70467223dd [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// Captures reads and writes to DMI SBA registers to infer transactions on the SBA interface.
//
// Unlike the name suggests, this monitor does not actually monitor the SBA interface
// directly, but indirectly by snooping the reads and writes to the SBA registers in the JTAG DMI
// register space. These are defined in the RISCV debug spec 0.13.2.
//
// From the series of reads and writes to the SBA registers in the JTAG DMI address space, this
// monitor infers accesses over the SBA and writes such predicted accesses to the analysis ports for
// higher level testbench components to process.
//
// Reads and writes made to non SBA registers are passed on through non_sba_jtag_dmi_analysis_port.
class sba_access_monitor #(type ITEM_T = sba_access_item) extends dv_base_monitor#(
.ITEM_T (ITEM_T),
.CFG_T (jtag_agent_cfg));
`uvm_component_param_utils(sba_access_monitor #(ITEM_T))
// A handle to the JTAG DMI RAL model. Please set this handle as soon as the instance is created.
jtag_dmi_reg_block jtag_dmi_ral;
uvm_reg_addr_t sba_addrs[$];
// Enables the monitor.
bit enable;
// A queue holding pending, unserviced SBA request.
ITEM_T sba_req_q[$];
// Incoming (inferred) JTAG DMI transactions.
uvm_tlm_analysis_fifo #(jtag_dmi_item) jtag_dmi_fifo;
// Outgoing filtered JTAG DMI transactions that do not touch the SBA registers.
uvm_analysis_port #(jtag_dmi_item) non_sba_jtag_dmi_analysis_port;
`uvm_component_new
function void build_phase(uvm_phase phase);
super.build_phase(phase);
jtag_dmi_fifo = new("jtag_dmi_fifo", this);
non_sba_jtag_dmi_analysis_port = new("non_sba_jtag_dmi_analysis_port", this);
sba_addrs.push_back(jtag_dmi_ral.sbcs.get_address());
sba_addrs.push_back(jtag_dmi_ral.sbaddress0.get_address());
if (jtag_dmi_ral.sbcs.sbasize.get_reset() > 32) begin
sba_addrs.push_back(jtag_dmi_ral.sbaddress1.get_address());
end
if (jtag_dmi_ral.sbcs.sbasize.get_reset() > 64) begin
sba_addrs.push_back(jtag_dmi_ral.sbaddress2.get_address());
end
if (jtag_dmi_ral.sbcs.sbasize.get_reset() > 96) begin
sba_addrs.push_back(jtag_dmi_ral.sbaddress3.get_address());
end
sba_addrs.push_back(jtag_dmi_ral.sbdata0.get_address());
if (jtag_dmi_ral.sbcs.sbaccess64.get_reset()) begin
sba_addrs.push_back(jtag_dmi_ral.sbdata1.get_address());
end
if (jtag_dmi_ral.sbcs.sbaccess128.get_reset()) begin
sba_addrs.push_back(jtag_dmi_ral.sbdata2.get_address());
sba_addrs.push_back(jtag_dmi_ral.sbdata3.get_address());
end
`uvm_info(`gfn, $sformatf("sba_addrs: %0p", sba_addrs), UVM_LOW)
endfunction
task run_phase(uvm_phase phase);
fork
super.run_phase(phase);
monitor_reset();
join
endtask
virtual function void report_phase(uvm_phase phase);
`DV_EOT_PRINT_Q_CONTENTS(sba_access_item, sba_req_q)
`DV_EOT_PRINT_TLM_FIFO_CONTENTS(jtag_dmi_item, jtag_dmi_fifo)
endfunction
virtual protected task collect_trans(uvm_phase phase);
jtag_dmi_item dmi_item;
forever begin
bit busy;
uvm_reg csr;
jtag_dmi_fifo.get(dmi_item);
`uvm_info(`gfn, $sformatf("Received jtag DMI item:\n%0s",
dmi_item.sprint(uvm_default_line_printer)), UVM_HIGH)
// Pass through DMI accesses that do not touch the SBA registers.
if (!(dmi_item.addr inside {sba_addrs})) begin
non_sba_jtag_dmi_analysis_port.write(dmi_item);
continue;
end
// Pass through unsuccessful accesses.
if (dmi_item.rsp_op != DmiOpOk) begin
non_sba_jtag_dmi_analysis_port.write(dmi_item);
continue;
end
csr = jtag_dmi_ral.default_map.get_reg_by_offset(dmi_item.addr);
if (dmi_item.req_op == DmiOpRead) begin
if (process_sba_csr_read(csr, dmi_item)) begin
void'(csr.predict(.value(dmi_item.rdata), .kind(UVM_PREDICT_READ)));
end
end
else if (dmi_item.req_op == DmiOpWrite) begin
void'(csr.predict(.value(dmi_item.wdata), .kind(UVM_PREDICT_WRITE)));
process_sba_csr_write(csr);
end
end
endtask
// Predict what is expected to happen if one of the SBA registers is read.
//
// If the sbcs register is read, and sbbusy bit drops on a pending write, we consider the
// transaction to have completed - we write the predicted SBA transaction to the analysis port.
//
// Returns a bit to the caller indicating whether to predict the CSR read or not.
virtual protected function bit process_sba_csr_read(uvm_reg csr, jtag_dmi_item dmi_item);
uvm_reg_data_t readondata = `gmv(jtag_dmi_ral.sbcs.sbreadondata);
uvm_reg_data_t readonaddr = `gmv(jtag_dmi_ral.sbcs.sbreadonaddr);
uvm_reg_data_t sbbusy = `gmv(jtag_dmi_ral.sbcs.sbbusy);
uvm_reg_data_t sbbusyerror = `gmv(jtag_dmi_ral.sbcs.sbbusyerror);
uvm_reg_data_t sberror = `gmv(jtag_dmi_ral.sbcs.sberror);
bit do_predict = 1;
case (csr.get_name())
"sbcs": begin
// Update the status bits from transaction item.
sbbusy = get_field_val(jtag_dmi_ral.sbcs.sbbusy, dmi_item.rdata);
sbbusyerror = get_field_val(jtag_dmi_ral.sbcs.sbbusyerror, dmi_item.rdata);
sberror = get_field_val(jtag_dmi_ral.sbcs.sberror, dmi_item.rdata);
// We should have predicted an SBA access if any of the status bits got set.
if (sbbusy || sbbusyerror) begin
`DV_CHECK(sba_req_q.size(),
$sformatf({"One of these bits is set, but no SBA access was predicted: ",
"sbbusy=%0b, sbbusyerror=%0b"}, sbbusy, sbbusyerror))
end
// Check if we correctly predicted busy error.
`DV_CHECK_EQ(sbbusyerror, `gmv(jtag_dmi_ral.sbcs.sbbusyerror))
if (sbbusyerror) sba_req_q[0].is_busy_err = 1'b1;
// Check if we correctly predicted the malformed SBA access request errors.
//
// We can only predict SbaErrBadAlignment and SbaErrBadSize errors. For the externally
// indicated errors SbaErrTimeout, SbaErrBadAddr and SbaErrOther, we pass the actually seen
// sberror to the sba_access_item that is written to the analysis port. The external entity
// reading from this port is expected to verify the correctness of these errors.
if (sberror inside {SbaErrNone, SbaErrBadAlignment, SbaErrBadSize}) begin
`DV_CHECK_EQ(sberror, `gmv(jtag_dmi_ral.sbcs.sberror))
end
if (sba_req_q.size()) begin
if (sberror) sba_req_q[0].is_err = sba_access_err_e'(sberror);
if (!sbbusy) begin
// Write the predicted SBA write transaction to the analysis port.
if (sba_req_q[0].bus_op == BusOpWrite) begin
analysis_port.write(sba_req_q.pop_front());
predict_autoincr_sba_addr();
end
end
end
end
"sbaddress0": begin
uvm_reg_data_t exp_addr = `gmv(jtag_dmi_ral.sbaddress0);
uvm_reg_data_t autoincrement = `gmv(jtag_dmi_ral.sbcs.sbautoincrement);
if (autoincrement) begin
sba_access_size_e size = sba_access_size_e'(`gmv(jtag_dmi_ral.sbcs.sbaccess));
// Depending on when the sbaddress0 is read, the predicted sbaddress0 value could be off
// (less than) the observed by at most 1 increment value.
`DV_CHECK(dmi_item.rdata inside {exp_addr, exp_addr + (1 << size)})
// Skip updating the mirrored value (at the call site) since we predict the addr
// separately.
do_predict = 0;
end else begin
`DV_CHECK_EQ(dmi_item.rdata, exp_addr)
end
end
"sbdata0": begin
// `DV_CHECK_EQ(dmi_item.rdata, jtag_dmi_ral.sbdata0.get_mirrored_value())
// If SBA read access completed, then return the data read from this register. We count
// on stimulus to have read the sbcs register before to ensure the access actually
// completed. The external scoreboard is expected to verify the correctness of externally
// indicated errors SbaErrTimeout, SbaErrBadAddr and SbaErrOther, when the stimulus reads
// the sbcs register during a pending SBA read transaction.
//
// The stimulus (in jtag_rv_debugger:sba_access()) terminates the SBA access after
// reading sbdata0 on read transactions.
if (sba_req_q.size()) begin
if (sba_req_q[0].bus_op == BusOpRead && !sbbusy) begin
sba_req_q[0].rdata[0] = dmi_item.rdata;
analysis_port.write(sba_req_q.pop_front());
predict_autoincr_sba_addr();
end
end
// If readondata is set, then a read to this register will trigger a new SBA read.
if (readondata) begin
void'(predict_sba_req(BusOpRead));
end
end
default: begin
`uvm_fatal(`gfn, $sformatf("Read to SBA CSR %0s is unsupported", csr.`gfn))
end
endcase
return do_predict;
endfunction
// Predict what is expected to happen if one of the SBA registers is written.
virtual protected function void process_sba_csr_write(uvm_reg csr);
case (csr.get_name())
"sbcs": begin
// Nothing to do.
end
"sbaddress0": begin
// If readonaddr is set, then a write to this register will trigger an SBA read.
if (jtag_dmi_ral.sbcs.sbreadonaddr.get_mirrored_value()) begin
void'(predict_sba_req(BusOpRead));
end
end
"sbdata0": begin
// A write to this register will trigger an SBA write.
void'(predict_sba_req(BusOpWrite));
end
default: begin
`uvm_fatal(`gfn, $sformatf("Write to SBA CSR %0s is unsupported", csr.`gfn))
end
endcase
endfunction
virtual task monitor_ready_to_end();
forever begin
if (!sba_req_q.size()) begin
ok_to_end = 1'b1;
wait (sba_req_q.size());
end else begin
string msg;
ok_to_end = 1'b0;
foreach (sba_req_q[i]) begin
msg = {msg, "\n", $sformatf(" sba_req_q[%0d]: %0s",
i, sba_req_q[i].sprint(uvm_default_line_printer))};
end
`uvm_info(`gfn, $sformatf("The following transactions are pending:\n%0s", msg), UVM_LOW)
wait (sba_req_q.size() == 0);
end
end
endtask
virtual protected task monitor_reset();
forever @cfg.in_reset begin
if (cfg.in_reset) sba_req_q.delete();
end
endtask
// Predicts if an SBA request will be made.
//
// It uses the values predicted from writes to the SBA registers that occurred before, to check
// validity and create a predicted SBA request item. It returns 1 if the SBA access is predicted
// to be made, else 0. Before returning 1, it records the predicted SBA request.
//
// bus_op: the predicted read or write operation.
// item: The returned expected request predicted to be sent.
// returns 1 if a new SBA request is expected to be sent, else 0.
virtual protected function bit predict_sba_req(input bus_op_e bus_op);
uvm_reg_addr_t addr = `gmv(jtag_dmi_ral.sbaddress0);
uvm_reg_data_t data = `gmv(jtag_dmi_ral.sbdata0);
sba_access_size_e size = sba_access_size_e'(`gmv(jtag_dmi_ral.sbcs.sbaccess));
sba_access_item item;
// Is the address aligned?
if (addr << ($bits(addr) - size)) begin
void'(jtag_dmi_ral.sbcs.sberror.predict(.value(SbaErrBadAlignment),
.kind(UVM_PREDICT_DIRECT)));
return 0;
end
// Is the transfer size supported?
if (size > $clog2(bus_params_pkg::BUS_DBW)) begin
void'(jtag_dmi_ral.sbcs.sberror.predict(.value(SbaErrBadSize), .kind(UVM_PREDICT_DIRECT)));
return 0;
end
// Is there already a pending transaction?
if (`gmv(jtag_dmi_ral.sbcs.sbbusy)) begin
void'(jtag_dmi_ral.sbcs.sbbusyerror.predict(.value(1), .kind(UVM_PREDICT_DIRECT)));
return 0;
end
item = ITEM_T::type_id::create("item");
item.bus_op = bus_op;
item.addr = addr;
item.size = size;
item.is_err = SbaErrNone;
item.is_busy_err = 0;
item.timed_out = 0;
if (bus_op == BusOpWrite) item.wdata[0] = data;
`uvm_info(`gfn, $sformatf("Predicted new SBA req: %0s",
item.sprint(uvm_default_line_printer)), UVM_MEDIUM)
`DV_CHECK_EQ(sba_req_q.size(), 0,
$sformatf("Predicted new SBA req before previous req %0s was popped.",
sba_req_q[$].sprint(uvm_default_line_printer)))
sba_req_q.push_back(item);
req_analysis_port.write(item);
void'(jtag_dmi_ral.sbcs.sbbusy.predict(.value(1), .kind(UVM_PREDICT_DIRECT)));
return 1;
endfunction
// If autoincr is set then predict the new address. Invoked after the successful completion of
// previous transfer.
virtual function void predict_autoincr_sba_addr();
if (`gmv(jtag_dmi_ral.sbcs.sbautoincrement)) begin
sba_access_size_e size = sba_access_size_e'(`gmv(jtag_dmi_ral.sbcs.sbaccess));
uvm_reg_data_t addr = `gmv(jtag_dmi_ral.sbaddress0);
void'(jtag_dmi_ral.sbaddress0.predict(.value(addr + (1 << size)),
.kind(UVM_PREDICT_DIRECT)));
`uvm_info(`gfn, $sformatf("Predicted sbaddr after autoincr: 0x%0h -> 0x%0h",
addr, `gmv(jtag_dmi_ral.sbaddress0)), UVM_HIGH)
end
endfunction
endclass