Skip to content

Commit

Permalink
[i2c,dv] Extend monitor / reference model to pass in-progress transfe…
Browse files Browse the repository at this point in the history
…r items

Signed-off-by: Harry Callahan <[email protected]>
  • Loading branch information
hcallahan-lowrisc committed Jul 5, 2024
1 parent 71b8a05 commit 07b23ab
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 13 deletions.
14 changes: 14 additions & 0 deletions hw/dv/sv/i2c_agent/i2c_agent_pkg.sv
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@ package i2c_agent_pkg;
HostWait
} drv_type_e;

// This enum is used to model the current state of an in-progress i2c transfer
typedef enum int {
StNone,
StStarted,
StAddrByte,
StAddrByteRcvd,
StAddrByteAckRcvd,
StDataByte,
StDataByteRcvd,
StDataByteAckRcvd,
StStopped,
StAborted
} transfer_state_e;

// register values
typedef struct {
// derived parameters
Expand Down
5 changes: 5 additions & 0 deletions hw/dv/sv/i2c_agent/i2c_item.sv
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ class i2c_item extends uvm_sequence_item;
// Transfer ID
int tran_id;

// Model the state of an in-progress i2c transfer. This can be used as a cheap proxy for
// breaking up the transfer into smaller sequence items (bytes, bits, etc.) when we need to
// update our predictions mid-transfer.
transfer_state_e state;

// Address / Direction
bit [9:0] addr; // enough to support both 7 & 10-bit target address
rw_e dir; // Transfer direction bit
Expand Down
56 changes: 43 additions & 13 deletions hw/dv/sv/i2c_agent/i2c_monitor.sv
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,17 @@ class i2c_monitor extends dv_base_monitor #(
// CLASS VARIABLES //
/////////////////////

// Full-transaction ports
uvm_analysis_port #(i2c_item) controller_mode_wr_item_port;
uvm_analysis_port #(i2c_item) controller_mode_rd_item_port;
uvm_analysis_port #(i2c_item) target_mode_wr_item_port;
uvm_analysis_port #(i2c_item) target_mode_rd_item_port;

// In-progress transaction ports
uvm_analysis_port #(i2c_item) controller_mode_in_progress_port;
uvm_analysis_port #(i2c_item) target_mode_in_progress_port;


// This item is used to capture and accumulate ongoing transaction control and data symbols.
// It is the main piece of state used by a monitor instance to keep track of the ongoing I2C
// transaction, across all subroutines. Once a transaction has completed, this item is cloned and
Expand All @@ -70,6 +76,8 @@ class i2c_monitor extends dv_base_monitor #(
controller_mode_rd_item_port = new("controller_mode_rd_item_port", this);
target_mode_wr_item_port = new("target_mode_wr_item_port", this);
target_mode_rd_item_port = new("target_mode_rd_item_port", this);
controller_mode_in_progress_port = new("controller_mode_in_progress_port", this);
target_mode_in_progress_port = new("target_mode_in_progress_port", this);
endfunction : build_phase

virtual task wait_for_reset_and_drop_item();
Expand Down Expand Up @@ -155,6 +163,12 @@ class i2c_monitor extends dv_base_monitor #(

`uvm_create_obj(i2c_item, mon_dut_item);

// Publish a handle of the in-progress transfer via the in_progress port.
// This can be used to make fine-grained predictions based on the ongoing state of the bus,
// such as by a predictor/reference_model.
// Note. that any consumers of this item should not modify it.
target_mode_in_progress_port.write(mon_dut_item);

// Decode the start of the next transfer. As we are calling this task in a loop, this may be
// the start of a new transaction, or the start of new transfer following a repeated-start
// condition.
Expand Down Expand Up @@ -182,6 +196,7 @@ class i2c_monitor extends dv_base_monitor #(
`uvm_fatal(`gfn, "State at start of target_collect_thread() is unexpected!")
end

mon_dut_item.state = i2c_agent_pkg::StStarted;
mon_dut_item.tran_id = num_dut_tran++;

// Capture the transfer
Expand All @@ -193,6 +208,9 @@ class i2c_monitor extends dv_base_monitor #(
default: `uvm_fatal(`gfn, "Should never get here!")
endcase
end

mon_dut_item.state = i2c_agent_pkg::StStopped;

// If the previous transfer ended via STOP (as opposed to RSTART), signal this to the driver.
if (mon_dut_item.stop) cfg.got_stop = 1;

Expand All @@ -210,11 +228,6 @@ class i2c_monitor extends dv_base_monitor #(
prev_item = full_item;
end

// Clear any data leftover in the temporary item (but don't clear the flags)
mon_dut_item.clear_data();
// Clear flags (start/stop/rstart/read/rcont/nakok)
mon_dut_item.clear_flags();

endtask: target_collect_thread


Expand All @@ -236,6 +249,7 @@ class i2c_monitor extends dv_base_monitor #(

cfg.stop_perf_monitor.trigger();

mon_dut_item.state = i2c_agent_pkg::StAddrByteRcvd; // Signal the addr+dir is captured
mon_dut_item.bus_op = (rw_req) ? BusOpRead : BusOpWrite;

`uvm_info(`gfn, $sformatf("target_address_thread(): req_analysis_port.write()"), UVM_DEBUG)
Expand All @@ -246,12 +260,14 @@ class i2c_monitor extends dv_base_monitor #(
// get ack after transmitting address
cfg.vif.wait_for_device_ack_or_nack(cfg.timing_cfg, mon_dut_item.addr_ack);
if (mon_dut_item.addr_ack == i2c_pkg::NACK) cfg.got_nack.trigger();
`uvm_info(`gfn,
$sformatf("target_address_thread(): %0s", mon_dut_item.addr_ack.name()),
mon_dut_item.state = i2c_agent_pkg::StAddrByteAckRcvd;

`uvm_info(`gfn, $sformatf("target_address_thread(): saw %0s", mon_dut_item.addr_ack.name()),
UVM_DEBUG)
`uvm_info(`gfn, "target_address_thread() detected ACK", UVM_FULL)

endtask : target_address_thread


virtual protected task target_read_thread();
i2c_item clone_item;
bit [7:0] mon_data;
Expand Down Expand Up @@ -281,6 +297,7 @@ class i2c_monitor extends dv_base_monitor #(

mon_dut_item.data_q.push_back(mon_data);
mon_dut_item.num_data++;
mon_dut_item.state = i2c_agent_pkg::StDataByteRcvd;
`uvm_info(`gfn, $sformatf("target_read_thread() trans %0d, byte %0d 0x%0x",
mon_dut_item.tran_id, mon_dut_item.num_data, mon_data), UVM_FULL)

Expand All @@ -292,6 +309,7 @@ class i2c_monitor extends dv_base_monitor #(
if (acknack_bit == i2c_pkg::NACK) cfg.got_nack.trigger();
mon_dut_item.data_ack_q.push_back(acknack_bit);
`uvm_info(`gfn, $sformatf("target_read_thread() saw %0s", acknack_bit.name()), UVM_HIGH)
mon_dut_item.state = i2c_agent_pkg::StDataByteAckRcvd;
end

// If we saw NACK, it must be followed by a STOP or RSTART condition.
Expand Down Expand Up @@ -334,6 +352,7 @@ class i2c_monitor extends dv_base_monitor #(
mon_dut_item.data_q.push_back(mon_data);
`uvm_info(`gfn, $sformatf("target_write_thread() data %2x num_data:%0d",
mon_data, mon_dut_item.num_data), UVM_FULL)
mon_dut_item.state = i2c_agent_pkg::StDataByteRcvd;

// Send data component of this write to the 'req_analysis_port', for consumption by
// reactive-agent sequences.
Expand All @@ -352,6 +371,7 @@ class i2c_monitor extends dv_base_monitor #(
if (acknack_bit == i2c_pkg::NACK) cfg.got_nack.trigger();
`uvm_info(`gfn, $sformatf("target_write_thread() saw %0s",
acknack_bit.name()), UVM_HIGH)
mon_dut_item.state = i2c_agent_pkg::StDataByteAckRcvd;
end
end
begin : end_of_transfer_thread
Expand Down Expand Up @@ -434,6 +454,12 @@ class i2c_monitor extends dv_base_monitor #(

`uvm_create_obj(i2c_item, mon_dut_item);

// Publish a handle of the in-progress transfer via the in_progress port.
// This can be used to make fine-grained predictions based on the ongoing state of the bus,
// such as by a predictor/reference_model.
// Note. that any consumers of this item should not modify it.
controller_mode_in_progress_port.write(mon_dut_item);

// Decode the start of the next transfer. As we are calling this task in a loop, this may be
// the start of a new transaction, or the start of new transfer following a repeated-start
// condition.
Expand Down Expand Up @@ -461,6 +487,7 @@ class i2c_monitor extends dv_base_monitor #(
`uvm_fatal(`gfn, "State at start of controller_collect_thread() is unexpected!")
end

mon_dut_item.state = i2c_agent_pkg::StStarted;
mon_dut_item.tran_id = num_dut_tran++;

// Capture the transfer
Expand All @@ -476,6 +503,8 @@ class i2c_monitor extends dv_base_monitor #(
endcase
end

mon_dut_item.state = i2c_agent_pkg::StStopped;

// If the previous transfer ended via STOP (as opposed to RSTART), signal this to the driver.
if (mon_dut_item.stop) cfg.got_stop = 1;

Expand All @@ -493,11 +522,6 @@ class i2c_monitor extends dv_base_monitor #(
prev_item = full_item;
end

// Clear any data leftover in the temporary item (but don't clear the flags)
mon_dut_item.clear_data();
// Clear flags (start/stop/rstart/read/rcont/nakok)
mon_dut_item.clear_flags();

endtask: controller_collect_thread


Expand Down Expand Up @@ -531,6 +555,7 @@ class i2c_monitor extends dv_base_monitor #(
mon_dut_item.bus_op = (mon_dut_item.dir == i2c_pkg::READ) ? BusOpRead : BusOpWrite;
end

mon_dut_item.state = i2c_agent_pkg::StAddrByteRcvd; // Signal the addr+dir is captured
`uvm_info(`gfn, $sformatf(
"controller_address_thread() got byte 0x%2x (address=2'h%2x (%0s), rw=1'b%b (%0s))",
{mon_dut_item.addr, mon_dut_item.dir},
Expand All @@ -551,6 +576,7 @@ class i2c_monitor extends dv_base_monitor #(
`uvm_info(`gfn, $sformatf("controller_address_thread() saw %0s",
mon_dut_item.addr_ack.name()), UVM_HIGH)
if (mon_dut_item.addr_ack == i2c_pkg::NACK) cfg.got_nack.trigger();
mon_dut_item.state = i2c_agent_pkg::StAddrByteAckRcvd; // Signal the addr_ack is captured

end : address_capture_thread
begin
Expand Down Expand Up @@ -583,6 +609,7 @@ class i2c_monitor extends dv_base_monitor #(
// Push read data into the item
mon_dut_item.data_q.push_back(data);
mon_dut_item.num_data++;
mon_dut_item.state = i2c_agent_pkg::StDataByteRcvd;
end

// Collect the ACK/NACK bit...
Expand All @@ -593,6 +620,7 @@ class i2c_monitor extends dv_base_monitor #(
if (acknack_bit == i2c_pkg::NACK) cfg.got_nack.trigger();
`uvm_info(`gfn, $sformatf("controller_read_thread(), detected %0s",
acknack_bit.name()), UVM_HIGH)
mon_dut_item.state = i2c_agent_pkg::StDataByteAckRcvd;
end

cfg.rcvd_rd_byte++;
Expand Down Expand Up @@ -650,6 +678,7 @@ class i2c_monitor extends dv_base_monitor #(
// Push write data into the item
mon_dut_item.data_q.push_back(data);
mon_dut_item.num_data++;
mon_dut_item.state = i2c_agent_pkg::StDataByteRcvd;
end

// Collect the ACK/NACK bit...
Expand All @@ -660,6 +689,7 @@ class i2c_monitor extends dv_base_monitor #(
if (acknack_bit == i2c_pkg::NACK) cfg.got_nack.trigger();
`uvm_info(`gfn, $sformatf("controller_write_thread(), detected %0s",
acknack_bit.name()), UVM_HIGH)
mon_dut_item.state = i2c_agent_pkg::StDataByteAckRcvd;
end

`uvm_info(`gfn, $sformatf("controller_write_thread(), trans %0d, byte_num %0d, 8'h%2x",
Expand Down
5 changes: 5 additions & 0 deletions hw/ip/i2c/dv/env/i2c_env.sv
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ class i2c_env extends cip_base_env #(
m_i2c_agent.monitor.controller_mode_rd_item_port.connect(
model.target_mode_rd_obs_fifo.analysis_export);

m_i2c_agent.monitor.controller_mode_in_progress_port.connect(
model.target_mode_in_progress_fifo.analysis_export);
m_i2c_agent.monitor.target_mode_in_progress_port.connect(
model.controller_mode_in_progress_fifo.analysis_export);

// (EXP) MODEL -> SCOREBOARD

model.controller_mode_wr_port.connect(scoreboard.controller_mode_wr_exp_fifo.analysis_export);
Expand Down
12 changes: 12 additions & 0 deletions hw/ip/i2c/dv/env/i2c_reference_model.sv
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ class i2c_reference_model extends uvm_component;
uvm_tlm_analysis_fifo #(i2c_item) target_mode_wr_obs_fifo; // Input -> Monitor 'analysis_port'
uvm_tlm_analysis_fifo #(i2c_item) target_mode_rd_obs_fifo; // Input -> Monitor 'analysis_port'

uvm_tlm_analysis_fifo #(i2c_item) controller_mode_in_progress_fifo;
uvm_tlm_analysis_fifo #(i2c_item) target_mode_in_progress_fifo;

// Outputs

uvm_analysis_port #(i2c_item) controller_mode_wr_port;
Expand All @@ -88,6 +91,13 @@ class i2c_reference_model extends uvm_component;
// Modelling Variables
//

// The following items are captured from the i2c_monitor, which we use to inform us about
// inputs to the DUT on the bus that may occur mid-transfer (e.g. NACKs and clock stretching)
// Note. In the future, it would be preferable to further break down i2c-transfers into smaller
// and smaller components, so we can make more granular updates to the DUT's predicted state.
local i2c_item in_progress_wr_transfer;
local i2c_item in_progress_rd_transfer;

// DUT-Controller
//
// The following local seq_items are used to construct larger transactions.
Expand Down Expand Up @@ -142,6 +152,8 @@ class i2c_reference_model extends uvm_component;
// Input fifos
target_mode_wr_obs_fifo = new("target_mode_wr_obs_fifo", this);
target_mode_rd_obs_fifo = new("target_mode_rd_obs_fifo", this);
controller_mode_in_progress_fifo = new("controller_mode_in_progress_fifo", this);
target_mode_in_progress_fifo = new("target_mode_in_progress_fifo", this);
// Output ports
controller_mode_wr_port = new("controller_mode_wr_port", this);
controller_mode_rd_port = new("controller_mode_rd_port", this);
Expand Down

0 comments on commit 07b23ab

Please sign in to comment.