blob: bd59b9c8ba7d4d1248f227a88deb4f9c183e2130 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class i2c_base_vseq extends cip_base_vseq #(
.CFG_T (i2c_env_cfg),
.RAL_T (i2c_reg_block),
.COV_T (i2c_env_cov),
.VIRTUAL_SEQUENCER_T (i2c_virtual_sequencer)
);
`uvm_object_utils(i2c_base_vseq)
// class property
bit program_incorrect_regs = 1'b0;
local timing_cfg_t timing_cfg;
bit [7:0] rd_data;
i2c_item fmt_item;
// random property
rand uint fmt_fifo_access_dly;
rand uint rx_fifo_access_dly;
rand uint clear_intr_dly;
rand uint num_runs;
rand uint num_wr_bytes;
rand uint num_rd_bytes;
rand uint num_data_ovf;
rand bit rw_bit; // 0 write, 1 read
rand bit [9:0] addr; // support both 7-bit and 10-bit target address
rand bit [6:0] target_addr0; // Target Address 0
rand bit [6:0] target_addr1; // Target Address 1
rand bit [6:0] illegal_addr; // Illegal target address
rand bit [7:0] txdata;
rand bit [2:0] rxilvl;
rand bit [1:0] fmtilvl;
// timing property
rand bit [15:0] thigh; // high period of the SCL in clock units
rand bit [15:0] tlow; // low period of the SCL in clock units
rand bit [15:0] t_r; // rise time of both SDA and SCL in clock units
rand bit [15:0] t_f; // fall time of both SDA and SCL in clock units
rand bit [15:0] thd_sta; // hold time for (repeated) START in clock units
rand bit [15:0] tsu_sta; // setup time for repeated START in clock units
rand bit [15:0] tsu_sto; // setup time for STOP in clock units
rand bit [15:0] tsu_dat; // data setup time in clock units
rand bit [15:0] thd_dat; // data hold time in clock units
rand bit [15:0] t_buf; // bus free time between STOP and START in clock units
rand bit [30:0] t_timeout; // max time target may stretch the clock
rand bit e_timeout; // max time target may stretch the clock
rand uint t_sda_unstable; // sda unstable time during the posedge_clock
rand uint t_sda_interference; // sda interference time during the posedge_clock
rand uint t_scl_interference; // scl interference time during the posedge_clock
// error intrs probability
rand uint prob_sda_unstable;
rand uint prob_sda_interference;
rand uint prob_scl_interference;
// host timeout ctrl value
bit [31:0] host_timeout_ctrl = 32'hffff;
// Start counter including restart. Starting from 1 to match rtl value
// and easy to trace.
int start_cnt = 1;
int read_rcvd[$];
int full_txn_num = 0;
int exp_rd_id = 0;
int exp_wr_id = 0;
// read_txn_q is used differently in drooling tx mode.
// Normal mode entry :
// a single byte of read data and only wdata is valid.
// Drooling tx mode entry :
// exp read txn all 'target_rd_comp' methods are valid.
i2c_item read_txn_q[$];
int tran_id = 0;
int sent_txn_cnt = 0;
i2c_intr_e intr_q[$];
bit expected_intr[i2c_intr_e];
// Used for drooling tx mode
bit read_on_going = 0;
int drooling_read_size = 0;
int drooling_rx_cnt = 0;
i2c_item drooling_exp_rd_item;
bit [7:0] spill_over_data_q[$];
bit read_cmd_q[$];
// Used for ack stop test
bit [7:0] pre_feed_rd_data_q[$];
int pre_feed_cnt = 0;
bit read_ack_nack_q[$];
bit adjust_exp_read_byte = 0;
// constraints
constraint target_addr_c {
solve target_addr0 before target_addr1;
solve target_addr1 before illegal_addr;
!(illegal_addr inside {target_addr0, target_addr1});
}
constraint addr_c {
addr inside {[cfg.seq_cfg.i2c_min_addr : cfg.seq_cfg.i2c_max_addr]};
}
constraint fmtilvl_c {
fmtilvl inside {[0 : cfg.seq_cfg.i2c_max_fmtilvl]};
}
constraint num_trans_c {
num_trans inside {[cfg.seq_cfg.i2c_min_num_trans : cfg.seq_cfg.i2c_max_num_trans]};
}
constraint num_runs_c {
num_runs inside {[cfg.seq_cfg.i2c_min_num_runs : cfg.seq_cfg.i2c_max_num_runs]};
}
// number of extra data write written to fmt to trigger interrupts
// i.e. overflow, threshold
constraint num_data_ovf_c {
num_data_ovf inside {[I2C_RX_FIFO_DEPTH/4 : I2C_RX_FIFO_DEPTH/2]};
}
// create uniform assertion distributions of rx_threshold interrupt
constraint rxilvl_c {
rxilvl dist {
[0:4] :/ 5,
[5:cfg.seq_cfg.i2c_max_rxilvl] :/ 1
};
}
constraint num_wr_bytes_c {
num_wr_bytes dist {
1 :/ 2,
[2:4] :/ 2,
[5:8] :/ 2,
[9:31] :/ 1,
32 :/ 1
};
}
constraint num_rd_bytes_c {
num_rd_bytes < 256;
num_rd_bytes dist {
1 :/ 2,
[2:4] :/ 2,
[5:8] :/ 2,
[9:16] :/ 1,
[17:31] :/ 1,
32 :/ 1
};
}
// use this prob_dist value to make interrupt assertion more discrepancy
constraint prob_error_intr_c {
prob_sda_unstable dist {0 :/ (100 - cfg.seq_cfg.i2c_prob_sda_unstable),
1 :/ cfg.seq_cfg.i2c_prob_sda_unstable};
prob_sda_interference dist {0 :/ (100 - cfg.seq_cfg.i2c_prob_sda_interference),
1 :/ cfg.seq_cfg.i2c_prob_sda_interference};
prob_scl_interference dist {0 :/ (100 - cfg.seq_cfg.i2c_prob_scl_interference),
1 :/ cfg.seq_cfg.i2c_prob_scl_interference};
}
// contraints for fifo access delay
constraint clear_intr_dly_c {
clear_intr_dly inside {[cfg.seq_cfg.i2c_min_dly : cfg.seq_cfg.i2c_max_dly]};
}
constraint fmt_fifo_access_dly_c {
fmt_fifo_access_dly inside {[cfg.seq_cfg.i2c_min_dly : cfg.seq_cfg.i2c_max_dly]};
}
constraint rx_fifo_access_dly_c {
rx_fifo_access_dly inside {[cfg.seq_cfg.i2c_min_dly : cfg.seq_cfg.i2c_max_dly]};
}
// constraints for i2c timing registers
constraint t_timeout_c {
t_timeout inside {[cfg.seq_cfg.i2c_min_timing : cfg.seq_cfg.i2c_max_timing]};
}
constraint timing_val_c {
thigh inside {[cfg.seq_cfg.i2c_min_timing : cfg.seq_cfg.i2c_max_timing]};
t_r inside {[cfg.seq_cfg.i2c_min_timing : cfg.seq_cfg.i2c_max_timing]};
t_f inside {[cfg.seq_cfg.i2c_min_timing : cfg.seq_cfg.i2c_max_timing]};
thd_sta inside {[cfg.seq_cfg.i2c_min_timing : cfg.seq_cfg.i2c_max_timing]};
tsu_sto inside {[cfg.seq_cfg.i2c_min_timing : cfg.seq_cfg.i2c_max_timing]};
tsu_dat inside {[cfg.seq_cfg.i2c_min_timing : cfg.seq_cfg.i2c_max_timing]};
thd_dat inside {[cfg.seq_cfg.i2c_min_timing : cfg.seq_cfg.i2c_max_timing]};
solve t_r, tsu_dat, thd_dat before tlow;
solve t_r before t_buf;
solve t_f, thigh before t_sda_unstable, t_sda_interference;
if (program_incorrect_regs) {
// force derived timing parameters to be negative (incorrect DUT config)
tsu_sta == t_r + t_buf + 1; // negative tHoldStop
tlow == 2; // negative tClockLow
t_buf == 2;
t_sda_unstable == 0;
t_sda_interference == 0;
t_scl_interference == 0;
} else {
tsu_sta inside {[cfg.seq_cfg.i2c_min_timing : cfg.seq_cfg.i2c_max_timing]};
// force derived timing parameters to be positive (correct DUT config)
// tlow must be at least 2 greater than the sum of t_r + tsu_dat + thd_dat
// because the flopped clock (see #15003 below) reduces tClockLow by 1.
thigh == (thd_sta + tsu_sta + t_r);
tlow inside {[(t_r + tsu_dat + thd_dat + 2) :
(t_r + tsu_dat + thd_dat + 2) + cfg.seq_cfg.i2c_time_range]};
t_buf inside {[(tsu_sta - t_r + 1) :
(tsu_sta - t_r + 1) + cfg.seq_cfg.i2c_time_range]};
t_sda_unstable inside {[0 : t_r + thigh + t_f - 1]};
t_sda_interference inside {[0 : t_r + thigh + t_f - 1]};
t_scl_interference inside {[0 : t_r + thigh + t_f - 1]};
}
}
`uvm_object_new
virtual task pre_start();
cfg.reset_seq_cfg();
// sync monitor and scoreboard setting
cfg.m_i2c_agent_cfg.en_monitor = cfg.en_scb;
`uvm_info(`gfn, $sformatf("\n %s monitor and scoreboard",
cfg.en_scb ? "enable" : "disable"), UVM_DEBUG)
num_runs.rand_mode(0);
num_trans_c.constraint_mode(0);
// Initialize counters for stop_target_interrupt for stress_all test
cfg.sent_acq_cnt = 0;
cfg.rcvd_acq_cnt = 0;
sent_txn_cnt = 0;
cfg.m_i2c_agent_cfg.sent_rd_byte = 0;
cfg.m_i2c_agent_cfg.rcvd_rd_byte = 0;
host_timeout_ctrl = 32'hffff;
cfg.m_i2c_agent_cfg.allow_ack_stop = 0;
expected_intr.delete();
super.pre_start();
endtask : pre_start
virtual task post_start();
// env_cfg must be reset after vseq completion
cfg.reset_seq_cfg();
super.post_start();
print_seq_cfg_vars("post-start");
endtask : post_start
// TODO remove input arguments
virtual task initialization(if_mode_e mode = Host);
wait(cfg.m_i2c_agent_cfg.vif.rst_ni);
if (mode == Host) begin
i2c_init(Host);
agent_init(Device);
end else begin
i2c_init(Device);
agent_init(Host);
end
`uvm_info(`gfn, $sformatf("\n initialization is done, DUT/AGENT = %s",
(mode == Host) ? "Host/Target" : "Target/Host"), UVM_LOW)
endtask : initialization
// 'cfg.m_i2c_agent_cfg.if_mode' is set by plusarg.
// default value of if_mode in block level is 'Device'
virtual task agent_init(if_mode_e mode = Device);
i2c_base_seq m_base_seq;
`uvm_info(`gfn, $sformatf("\n initialize agent in mode %s", mode.name()), UVM_DEBUG)
if (mode == Device) begin
m_base_seq = i2c_base_seq::type_id::create("m_base_seq");
`uvm_info(`gfn, $sformatf("\n start i2c_sequence %s",
cfg.m_i2c_agent_cfg.if_mode.name()), UVM_DEBUG)
fork
m_base_seq.start(p_sequencer.i2c_sequencer_h);
join_none
end
// TODO: initialization for the agent running in Host mode
endtask : agent_init
virtual task i2c_init(if_mode_e mode = Host);
bit [TL_DW-1:0] intr_state;
`uvm_info(`gfn, $sformatf("\n initialize host in mode %s", mode.name()), UVM_DEBUG)
if (mode == Host) begin
ral.ctrl.enablehost.set(1'b1);
ral.ctrl.enabletarget.set(1'b0);
ral.ctrl.llpbk.set(1'b0);
csr_update(ral.ctrl);
// diable override
ral.ovrd.txovrden.set(1'b0);
csr_update(ral.ovrd);
end else begin
ral.ctrl.enablehost.set(1'b0);
ral.ctrl.enabletarget.set(1'b1);
ral.ctrl.llpbk.set(1'b0);
csr_update(ral.ctrl);
// TODO: more initialization for the host running Target mode
ral.target_id.address0.set(target_addr0);
ral.target_id.mask0.set(7'h7f);
ral.target_id.address1.set(target_addr1);
ral.target_id.mask1.set(7'h7f);
csr_update(ral.target_id);
// Host timeout control
ral.host_timeout_ctrl.set(this.host_timeout_ctrl);
csr_update(ral.host_timeout_ctrl);
end
// clear fifos
ral.fifo_ctrl.rxrst.set(1'b1);
ral.fifo_ctrl.fmtrst.set(1'b1);
ral.fifo_ctrl.acqrst.set(1'b1);
ral.fifo_ctrl.txrst.set(1'b1);
csr_update(ral.fifo_ctrl);
//enable then clear interrupts
csr_wr(.ptr(ral.intr_enable), .value({TL_DW{1'b1}}));
process_interrupts();
endtask : i2c_init
virtual task wait_for_reprogram_registers();
bit fmtempty, hostidle;
bit [TL_DW-1:0] reg_val;
do begin
if (cfg.under_reset) break;
csr_rd(.ptr(ral.status), .value(reg_val));
fmtempty = bit'(get_field_val(ral.status.fmtempty, reg_val));
hostidle = bit'(get_field_val(ral.status.hostidle, reg_val));
end while (!fmtempty || !hostidle);
`uvm_info(`gfn, $sformatf("\n registers can be reprogrammed"), UVM_DEBUG);
endtask : wait_for_reprogram_registers
virtual task wait_host_for_idle();
bit fmtempty, hostidle, rxempty;
bit [TL_DW-1:0] reg_val;
do begin
if (cfg.under_reset) break;
csr_rd(.ptr(ral.status), .value(reg_val));
fmtempty = bit'(get_field_val(ral.status.fmtempty, reg_val));
rxempty = bit'(get_field_val(ral.status.rxempty, reg_val));
hostidle = bit'(get_field_val(ral.status.hostidle, reg_val));
end while (!fmtempty || !hostidle || !rxempty);
`uvm_info(`gfn, $sformatf("\n host is in idle state"), UVM_DEBUG);
endtask : wait_host_for_idle
virtual task wait_for_target_idle();
bit acqempty, targetidle;
bit [TL_DW-1:0] reg_val;
do begin
if (cfg.under_reset) break;
csr_rd(.ptr(ral.status), .value(reg_val));
acqempty = bit'(get_field_val(ral.status.acqempty, reg_val));
targetidle = bit'(get_field_val(ral.status.targetidle, reg_val));
end while (!acqempty || !targetidle);
`uvm_info(`gfn, $sformatf("\n target is in idle state"), UVM_DEBUG);
endtask : wait_for_target_idle
function automatic void get_timing_values();
// derived timing parameters
timing_cfg.enbTimeOut = e_timeout;
timing_cfg.tTimeOut = t_timeout;
timing_cfg.tSetupStart = t_r + tsu_sta;
timing_cfg.tHoldStart = t_f + thd_sta;
timing_cfg.tClockStart = thd_dat;
// An extra -1 is added to tClokLow because scl coming from the host side is now flopped.
// See #15003
// This means, relative to the expectation of the DUT, we have "1 less" cycle of clock
// time. Specifically, if the DUT drives the clock at cycle 1, the device does not see
// the clock until cycle 2. This means, the device expectation of how long "low" is
// now shrunk by 1, since the end point is still fixed.
timing_cfg.tClockLow = tlow - t_r - tsu_dat - thd_dat - 1;
timing_cfg.tSetupBit = t_r + tsu_dat;
timing_cfg.tClockPulse = t_r + thigh + t_f;
timing_cfg.tHoldBit = t_f + thd_dat;
timing_cfg.tClockStop = t_f + tlow - thd_dat;
timing_cfg.tSetupStop = t_r + tsu_sto;
timing_cfg.tHoldStop = t_r + t_buf - tsu_sta;
// control interference and unstable interrupts
timing_cfg.tSclInterference = (cfg.seq_cfg.en_scl_interference) ?
prob_scl_interference * t_scl_interference : 0;
timing_cfg.tSdaInterference = (cfg.seq_cfg.en_sda_interference) ?
prob_sda_interference * t_sda_interference : 0;
timing_cfg.tSdaUnstable = (cfg.seq_cfg.en_sda_unstable) ?
prob_sda_unstable * t_sda_unstable : 0;
`uvm_info(`gfn, $sformatf("\n tSclItf = %0d, tSdaItf = %0d, tSdaUnstable = %0d",
timing_cfg.tSclInterference,
timing_cfg.tSdaInterference,
timing_cfg.tSdaUnstable), UVM_DEBUG)
// ensure these parameter must be greater than zeros
if (!program_incorrect_regs) begin
`DV_CHECK_GT_FATAL(timing_cfg.tClockLow, 0)
`DV_CHECK_GT_FATAL(timing_cfg.tClockStop, 0)
`DV_CHECK_GT_FATAL(timing_cfg.tHoldStop, 0)
end
endfunction : get_timing_values
virtual task program_registers();
//*** program timing register
ral.timing0.tlow.set(tlow);
ral.timing0.thigh.set(thigh);
csr_update(.csr(ral.timing0));
ral.timing1.t_f.set(t_f);
ral.timing1.t_r.set(t_r);
csr_update(.csr(ral.timing1));
ral.timing2.thd_sta.set(thd_sta);
ral.timing2.tsu_sta.set(tsu_sta);
csr_update(.csr(ral.timing2));
ral.timing3.thd_dat.set(thd_dat);
ral.timing3.tsu_dat.set(tsu_dat);
csr_update(.csr(ral.timing3));
ral.timing4.tsu_sto.set(tsu_sto);
ral.timing4.t_buf.set(t_buf);
csr_update(.csr(ral.timing4));
ral.timeout_ctrl.en.set(e_timeout);
ral.timeout_ctrl.val.set(t_timeout);
csr_update(.csr(ral.timeout_ctrl));
// configure i2c_agent_cfg
cfg.m_i2c_agent_cfg.timing_cfg = timing_cfg;
`uvm_info(`gfn, $sformatf("\n cfg.m_i2c_agent_cfg.timing_cfg\n%p",
cfg.m_i2c_agent_cfg.timing_cfg), UVM_MEDIUM)
//*** program ilvl
`DV_CHECK_MEMBER_RANDOMIZE_FATAL(fmtilvl)
`DV_CHECK_MEMBER_RANDOMIZE_FATAL(rxilvl)
ral.fifo_ctrl.rxilvl.set(rxilvl);
ral.fifo_ctrl.fmtilvl.set(fmtilvl);
csr_update(ral.fifo_ctrl);
endtask : program_registers
virtual task program_format_flag(i2c_item item, string msg = "", bit do_print = 1'b0);
bit fmtfull;
ral.fdata.nakok.set(item.nakok);
ral.fdata.rcont.set(item.rcont);
ral.fdata.read.set(item.read);
ral.fdata.stop.set(item.stop);
ral.fdata.start.set(item.start);
ral.fdata.fbyte.set(item.fbyte);
// en_fmt_underflow is set to ensure no write data overflow with fmt_fifo
// regardless en_fmt_underflow set/unset, the last data (consist of STOP bit) must be
// pushed into fmt_fifo to safely complete transaction
if (!cfg.seq_cfg.en_fmt_overflow || fmt_item.stop) begin
csr_spinwait(.ptr(ral.status.fmtfull), .exp_data(1'b0));
end
// if fmt_overflow irq is triggered it must be cleared before new fmt data is programmed
// otherwise, scoreboard can drop this data while fmt_fifo is not full
wait(!cfg.intr_vif.pins[FmtOverflow]);
// program fmt_fifo
csr_update(.csr(ral.fdata));
`DV_CHECK_MEMBER_RANDOMIZE_FATAL(fmt_fifo_access_dly)
cfg.clk_rst_vif.wait_clks(fmt_fifo_access_dly);
print_format_flag(item, msg, do_print);
endtask : program_format_flag
// read interrupts and randomly clear interrupts if set
virtual task process_interrupts();
bit [TL_DW-1:0] intr_state, intr_clear;
// read interrupt
csr_rd(.ptr(ral.intr_state), .value(intr_state));
// clear interrupt if it is set
`DV_CHECK_STD_RANDOMIZE_WITH_FATAL(intr_clear,
foreach (intr_clear[i]) {
intr_state[i] -> intr_clear[i] == 1;
})
if (bit'(get_field_val(ral.intr_state.fmt_threshold, intr_clear))) begin
`uvm_info(`gfn, "\n clearing fmt_threshold", UVM_DEBUG)
end
if (bit'(get_field_val(ral.intr_state.rx_threshold, intr_clear))) begin
`uvm_info(`gfn, "\n clearing rx_threshold", UVM_DEBUG)
end
if (bit'(get_field_val(ral.intr_state.stretch_timeout, intr_clear))) begin
`uvm_info(`gfn, "\n clearing stretch_timeout", UVM_DEBUG)
end
if (bit'(get_field_val(ral.intr_state.tx_stretch, intr_clear))) begin
`uvm_info(`gfn, "\n clearing tx_stretch", UVM_DEBUG)
end
if (bit'(get_field_val(ral.intr_state.tx_overflow, intr_clear))) begin
`uvm_info(`gfn, "\n clearing tx_overflow", UVM_DEBUG)
end
`DV_CHECK_MEMBER_RANDOMIZE_FATAL(clear_intr_dly)
cfg.clk_rst_vif.wait_clks(clear_intr_dly);
csr_wr(.ptr(ral.intr_state), .value(intr_clear));
endtask : process_interrupts
virtual task clear_interrupt(i2c_intr_e intr, bit verify_clear = 1'b1);
csr_wr(.ptr(ral.intr_state), .value(1 << intr));
if (verify_clear) wait(!cfg.intr_vif.pins[intr]);
endtask : clear_interrupt
virtual function void print_seq_cfg_vars(string msg = "", bit do_print = 1'b0);
if (do_print) begin
string str;
str = {str, $sformatf("\n %s, %s, i2c_seq_cfg", msg, get_name())};
str = {str, $sformatf("\n en_scb %b", cfg.en_scb)};
str = {str, $sformatf("\n en_monitor %b", cfg.m_i2c_agent_cfg.en_monitor)};
str = {str, $sformatf("\n do_apply_reset %b", do_apply_reset)};
str = {str, $sformatf("\n en_fmt_overflow %b", cfg.seq_cfg.en_fmt_overflow)};
str = {str, $sformatf("\n en_rx_overflow %b", cfg.seq_cfg.en_rx_overflow)};
str = {str, $sformatf("\n en_rx_threshold %b", cfg.seq_cfg.en_rx_threshold)};
str = {str, $sformatf("\n en_sda_unstable %b", cfg.seq_cfg.en_sda_unstable)};
str = {str, $sformatf("\n en_scl_interference %b", cfg.seq_cfg.en_scl_interference)};
str = {str, $sformatf("\n en_sda_interference %b", cfg.seq_cfg.en_sda_interference)};
`uvm_info(`gfn, $sformatf("%s", str), UVM_LOW)
end
endfunction : print_seq_cfg_vars
virtual function void print_format_flag(i2c_item item, string msg = "", bit do_print = 1'b0);
if (do_print) begin
string str;
str = {str, $sformatf("\n%s, format flags 0x%h \n", msg,
{item.nakok, item.rcont, item.read, item.stop, item.start, item.fbyte})};
if (item.start) begin
str = {str, $sformatf(" | %5s | %5s | %5s | %5s | %5s | %8s | %3s |\n",
"nakok", "rcont", "read", "stop", "start", "addr", "r/w")};
str = {str, $sformatf(" | %5d | %5d | %5d | %5d | %5d | %8x | %3s |",
item.nakok, item.rcont, item.read, item.stop, item.start, item.fbyte[7:1],
(item.fbyte[0]) ? "R" : "W")};
end else begin
str = {str, $sformatf(" | %5s | %5s | %5s | %5s | %5s | %8s |\n",
"nakok", "rcont", "read", "stop", "start", "fbyte")};
str = {str, $sformatf(" | %5d | %5d | %5d | %5d | %5d | %8x |",
item.nakok, item.rcont, item.read, item.stop, item.start, item.fbyte)};
end
`uvm_info(`gfn, $sformatf("%s", str), UVM_LOW)
end
endfunction : print_format_flag
virtual function void bound_check(bit [TL_DW-1:0] x, uint low_bound, uint high_bound);
// check low_bound <= x <= high_bound
`DV_CHECK_GE(high_bound, low_bound);
`DV_CHECK_GE(x, low_bound);
`DV_CHECK_LE(x, high_bound);
endfunction : bound_check
task reset_rx_fifo();
csr_wr(.ptr(ral.fifo_ctrl.rxrst), .value(1'b1));
csr_wr(.ptr(ral.fifo_ctrl.rxrst), .value(1'b0));
endtask : reset_rx_fifo
task reset_fmt_fifo();
csr_wr(.ptr(ral.fifo_ctrl.fmtrst), .value(1'b1));
csr_wr(.ptr(ral.fifo_ctrl.fmtrst), .value(1'b0));
endtask : reset_fmt_fifo
task program_tx_fifo(int tx_bytes);
for (int i = 0; i < tx_bytes; i++) begin
`DV_CHECK_MEMBER_RANDOMIZE_FATAL(txdata)
csr_wr(.ptr(ral.txdata), .value(txdata));
end
endtask : program_tx_fifo
task read_acqdata (int num_bytes);
bit [6:0] acqlvl;
bit [7:0] abyte;
bit [1:0] signal;
csr_rd_check(.ptr(ral.status.acqempty), .compare_value(0));
csr_rd(.ptr(ral.fifo_status.acqlvl), .value(acqlvl));
`DV_CHECK_EQ(acqlvl, (num_bytes+2)) // addr byte + data bytes + junk byte
for (int i = 0; i < (num_bytes+2); i++) begin
csr_rd(.ptr(ral.acqdata), .value({signal,abyte}));
end
endtask : read_acqdata
// Use for debug only
function void print_time_property();
`uvm_info(`gfn, $sformatf("timing_prop"), UVM_MEDIUM)
// high period of the SCL in clock units
`uvm_info(`gfn, $sformatf("thigh:%0d", thigh), UVM_MEDIUM);
// low period of the SCL in clock units
`uvm_info(`gfn, $sformatf("tlow:%0d", tlow), UVM_MEDIUM);
// rise time of both SDA and SCL in clock units
`uvm_info(`gfn, $sformatf("t_r:%0d", t_r), UVM_MEDIUM);
// fall time of both SDA and SCL in clock units
`uvm_info(`gfn, $sformatf("t_f:%0d", t_f), UVM_MEDIUM);
// hold time for (repeated) START in clock units
`uvm_info(`gfn, $sformatf("thd_sta:%0d", thd_sta), UVM_MEDIUM);
// setup time for repeated START in clock units
`uvm_info(`gfn, $sformatf("tsu_sta:%0d", tsu_sta), UVM_MEDIUM);
// setup time for STOP in clock units
`uvm_info(`gfn, $sformatf("tsu_sto:%0d", tsu_sto), UVM_MEDIUM);
// data setup time in clock units
`uvm_info(`gfn, $sformatf("tsu_dat:%0d", tsu_dat), UVM_MEDIUM);
// data hold time in clock units
`uvm_info(`gfn, $sformatf("thd_dat:%0d", thd_dat), UVM_MEDIUM);
// bus free time between STOP and START in clock units
`uvm_info(`gfn, $sformatf("t_buf:%0d", t_buf), UVM_MEDIUM);
// max time target may stretch the clock
`uvm_info(`gfn, $sformatf("t_timeout:%0d", t_timeout), UVM_MEDIUM);
// max time target may stretch the clock
`uvm_info(`gfn, $sformatf("e_timeout:%0d", e_timeout), UVM_MEDIUM);
// sda unstable time during the posedge_clock
`uvm_info(`gfn, $sformatf("t_sda_unstable:%0d", t_sda_unstable), UVM_MEDIUM);
// sda interference time during the posedge_clock
`uvm_info(`gfn, $sformatf("t_sda_interference:%0d", t_sda_interference), UVM_MEDIUM);
// scl interference time during the posedge_clock
`uvm_info(`gfn, $sformatf("t_scl_interference:%0d", t_scl_interference), UVM_MEDIUM);
`uvm_info(`gfn, $sformatf("error intrs probability"), UVM_MEDIUM)
`uvm_info(`gfn, $sformatf("prob_sda_unstable:%0d ", prob_sda_unstable), UVM_MEDIUM);
`uvm_info(`gfn, $sformatf("prob_sda_interference:%0d", prob_sda_interference), UVM_MEDIUM);
`uvm_info(`gfn, $sformatf("prob_scl_interference:%0d", prob_scl_interference), UVM_MEDIUM);
endfunction
// Print i2c_item.data_q with RS command notation
function void print_wr_data(bit is_read, i2c_item myq[$]);
int idx = 1;
int rd_idx = 0;
bit read = is_read;
foreach (myq[i]) begin
if (myq[i].rstart) begin
if (cfg.m_i2c_agent_cfg.allow_ack_stop & read) begin
`uvm_info("seq", $sformatf("eos: %s", (read_ack_nack_q[rd_idx++]) ?
"NACK" : "ACK"), UVM_MEDIUM)
end
read = myq[i].read;
`uvm_info("seq", $sformatf("idx %0d RS rw:%0d", start_cnt++, myq[i].read), UVM_MEDIUM)
idx = 1;
end else begin
`uvm_info("seq", $sformatf("%2d: 0x%2x", idx++, myq[i].wdata), UVM_MEDIUM)
end
end
if (cfg.m_i2c_agent_cfg.allow_ack_stop & read) begin
`uvm_info("seq", $sformatf("eos: %s", (read_ack_nack_q[rd_idx]) ?
"NACK" : "ACK"), UVM_MEDIUM)
end
endfunction
// Update read data to send based on
// pre written tx data and pre defined ack / nack status
// Used only for ack stop test
function void update_wr_data(bit is_read, ref i2c_item myq[$],
input bit force_ack);
bit read = is_read;
foreach (myq[i]) begin
if (myq[i].rstart) begin
// if previous segment is read, store ack/nack
if (read) read_ack_nack_q.push_back($urandom_range(0 ,1));
read = myq[i].read;
end else begin
if (read) begin
if (pre_feed_rd_data_q.size > 0) begin
myq[i].wdata = pre_feed_rd_data_q.pop_front();
end
end
end
end
if (read) begin
bit ack_nack = force_ack ? 0 : $urandom_range(0, 1);
read_ack_nack_q.push_back(ack_nack);
end
endfunction // update_wr_data
// Set rw bit based on cfg rd/wr pct
function bit get_read_write();
bit rw;
randcase
cfg.wr_pct: rw = 0;
cfg.rd_pct: rw = 1;
endcase
return rw;
endfunction // get_read_write
// Call read_acq_fifo until send and rcv counter become the same.
virtual task process_acq();
int delay;
bit acq_fifo_empty;
bit read_one = 0;
`DV_WAIT(cfg.sent_acq_cnt > 0,, cfg.spinwait_timeout_ns, "process_acq")
while (cfg.sent_acq_cnt != cfg.rcvd_acq_cnt) begin
if (cfg.slow_acq) begin
delay = $urandom_range(50, 100);
#(delay * 1us);
read_one = 1;
end
read_acq_fifo(read_one, acq_fifo_empty);
end
endtask
// Polling read_rcvd q and fetch read data to txdata fifo
virtual task process_txq();
`DV_WAIT(cfg.m_i2c_agent_cfg.sent_rd_byte > 0,, cfg.spinwait_timeout_ns, "process_txq")
while (cfg.m_i2c_agent_cfg.sent_rd_byte != cfg.m_i2c_agent_cfg.rcvd_rd_byte ||
sent_txn_cnt < num_trans) begin
cfg.clk_rst_vif.wait_clks(1);
write_tx_fifo(.add_delay(cfg.slow_txq));
end
endtask
// Create byte transaction (payload) to read or write.
// Restart can be stuffed in between bytes except first and the last bytes.
// While normal transaction type (read, write) is decided in 'fetch_txn',
// Restat transaction type is set here to make io trace easier.
virtual function void create_txn(ref i2c_item myq[$]);
bit [7:0] wdata_q[$];
i2c_item txn;
bit rs_avl;
`DV_CHECK_STD_RANDOMIZE_WITH_FATAL(wdata_q,
wdata_q.size() inside {
[cfg.min_data : cfg.max_data]};)
for (int i = 0; i < wdata_q.size(); i++) begin
if ($urandom_range(0,9) < cfg.rs_pct) rs_avl = 1;
else rs_avl = 0;
// restart entry
if (rs_avl == 1 && wdata_q.size() > 1 &&
i inside {[1:wdata_q.size() -1]}) begin
`uvm_info("seq", $sformatf("RS inserted before data %0d", i), UVM_HIGH)
`uvm_create_obj(i2c_item, txn)
txn.drv_type = HostRStart;
txn.rstart = 1;
txn.stop = 0;
txn.read = get_read_write();
myq.push_back(txn);
end
`uvm_create_obj(i2c_item, txn)
txn.drv_type = HostData;
txn.wdata = wdata_q[i];
myq.push_back(txn);
end
endfunction
// Receive byte stream from 'create_txn' and forward to driver's request q,
// with adding 'Start and Stop'.
// Expected transaction is created in the task.
// For write transaction and address transaction, expected transaction mimics acq read data.
// For read transaction, all read bytes for one transaction is accumulated to 'full_txn'
// and compared with received transaction at the scoreboard.
virtual function void fetch_txn(ref i2c_item src_q[$], i2c_item dst_q[$],
input bit force_ack = 0);
i2c_item txn;
i2c_item rs_txn;
i2c_item exp_txn;
i2c_item full_txn;
int read_size;
bit is_read = get_read_write();
bit [6:0] t_addr;
bit valid_addr;
bit got_valid;
`uvm_info("seq", $sformatf("idx %0d:is_read:%0b size:%0d fetch_txn:%0d", start_cnt++, is_read,
src_q.size(), full_txn_num++), UVM_MEDIUM)
// Update read data with pre filled one and ack/nack for ack stop test
if (cfg.m_i2c_agent_cfg.allow_ack_stop) begin
update_wr_data(is_read, src_q, force_ack);
end
print_wr_data(is_read, src_q);
`uvm_create_obj(i2c_item, full_txn)
// Add 'START' to the front
`uvm_create_obj(i2c_item, txn)
txn.drv_type = HostStart;
dst_q.push_back(txn);
full_txn.start = 1;
if (is_read) full_txn.tran_id = this.exp_rd_id;
// Address
`uvm_create_obj(i2c_item, txn)
`uvm_create_obj(i2c_item, exp_txn)
txn.drv_type = HostData;
txn.start = 1;
txn.wdata[7:1] = get_target_addr(); //target_addr0;
txn.wdata[0] = is_read;
valid_addr = is_target_addr(txn.wdata[7:1]);
txn.tran_id = this.tran_id;
`downcast(exp_txn, txn.clone());
dst_q.push_back(txn);
full_txn.addr = txn.wdata[7:1];
full_txn.read = is_read;
// Start command acq entry
if (valid_addr) begin
p_sequencer.target_mode_wr_exp_port.write(exp_txn);
cfg.sent_acq_cnt++;
this.tran_id++;
got_valid = 1;
if (is_read) this.exp_rd_id++;
end
read_size = get_read_data_size(src_q, is_read, read_rcvd);
cfg.m_i2c_agent_cfg.sent_rd_byte += read_size;
// Data
while (src_q.size() > 0) begin
`uvm_create_obj(i2c_item, txn)
txn = src_q.pop_front();
if (txn.drv_type != HostRStart) begin
// Restart only has empty data for address holder
full_txn.data_q.push_back(txn.wdata);
end
// RS creates 2 extra acq entry
// one for RS
// the other for a new start acq_entry with address
if (txn.drv_type == HostRStart) begin
bit prv_read = 0;
bit prv_valid = valid_addr;
t_addr = get_target_addr();
valid_addr = is_target_addr(t_addr);
`uvm_create_obj(i2c_item, exp_txn)
exp_txn.drv_type = HostRStart;
exp_txn.tran_id = this.tran_id;
// In 'txn' boundary, only if previous segment is valid,
// we create current RS header.
if (prv_valid) begin
exp_txn.rstart = 1;
this.tran_id++;
// RS head
p_sequencer.target_mode_wr_exp_port.write(exp_txn);
cfg.sent_acq_cnt++;
end
got_valid = valid_addr;
`uvm_create_obj(i2c_item, rs_txn)
`downcast(rs_txn, txn.clone())
dst_q.push_back(txn);
rs_txn.drv_type = HostData;
rs_txn.start = 1;
rs_txn.rstart = 0;
rs_txn.wdata[7:1] = t_addr;
prv_read = is_read;
is_read = rs_txn.read;
rs_txn.wdata[0] = is_read;
dst_q.push_back(rs_txn);
// create a separate stat/addr entry for exp
`uvm_create_obj(i2c_item, exp_txn)
`downcast(exp_txn, rs_txn.clone());
exp_txn.tran_id = this.tran_id;
// Restart command entry
if (valid_addr) begin
p_sequencer.target_mode_wr_exp_port.write(exp_txn);
cfg.sent_acq_cnt++;
this.tran_id++;
end
// fetch previous full_txn and creat a new one
if (prv_read) begin
full_txn.stop = 1;
// read data will be created on the fly if cfg.use_drooling tx is set
if (!cfg.use_drooling_tx) begin
if (prv_valid) p_sequencer.target_mode_rd_exp_port.write(full_txn);
end else read_txn_q.push_back(full_txn);
end
`uvm_create_obj(i2c_item, full_txn)
`downcast(full_txn, rs_txn);
if (is_read) begin
full_txn.tran_id = exp_rd_id;
if (valid_addr) exp_rd_id++;
end
end else begin
if (is_read) begin
i2c_item read_txn;
`uvm_create_obj(i2c_item, read_txn)
`downcast(read_txn, txn.clone())
full_txn.num_data++;
if (src_q.size() == 0) begin
txn.drv_type = get_eos(.is_stop(1));
end else begin
// if your next item is restart Do nack
if (src_q[0].drv_type == HostRStart) txn.drv_type = get_eos();
else txn.drv_type = HostAck;
end
dst_q.push_back(txn);
if (!cfg.use_drooling_tx) read_txn_q.push_back(read_txn);
end else begin
`downcast(exp_txn, txn.clone());
// Write payload
dst_q.push_back(txn);
if (valid_addr) begin
exp_txn.tran_id = this.tran_id++;
p_sequencer.target_mode_wr_exp_port.write(exp_txn);
cfg.sent_acq_cnt++;
end
end
end
end // while (src_q.size() > 0)
// Stop
`uvm_create_obj(i2c_item, txn)
`uvm_create_obj(i2c_item, exp_txn)
txn.tran_id = this.tran_id;
txn.stop = 1;
txn.drv_type = HostStop;
`downcast(exp_txn, txn.clone());
dst_q.push_back(txn);
full_txn.stop = 1;
if (valid_addr) begin
p_sequencer.target_mode_wr_exp_port.write(exp_txn);
cfg.sent_acq_cnt++;
this.tran_id++;
end
if (is_read) begin
// read data will be created on the fly if use_drooling_tx is set
if (!cfg.use_drooling_tx) begin
if (valid_addr) p_sequencer.target_mode_rd_exp_port.write(full_txn);
end else read_txn_q.push_back(full_txn);
end
endfunction
// Scan i2c_item q and count total byte of read data.
// Also store read data per commmand to rcvd q.
function int get_read_data_size(i2c_item myq[$], bit is_read, ref int rcvd[$]);
int cnt = 0;
int per_cmd_cnt = 0;
for (int i = 0; i < myq.size(); i++) begin
if (myq[i].rstart) is_read = myq[i].read;
if (is_read & !myq[i].rstart) begin
cnt++;
per_cmd_cnt++;
end else begin
if (per_cmd_cnt > 0) begin
rcvd.push_back(per_cmd_cnt);
per_cmd_cnt = 0;
end
end
end // for (int i = 0; i < myq.size(); i++)
if (per_cmd_cnt > 0) begin
rcvd.push_back(per_cmd_cnt);
per_cmd_cnt = 0;
end
return cnt;
endfunction
// This task needs to set do_clear_all_interrupts = 0
virtual task process_target_interrupts();
int delay;
bit acq_fifo_empty;
bit read_one = 0;
while (!cfg.stop_intr_handler) begin
@(posedge cfg.clk_rst_vif.clk);
if (cfg.intr_vif.pins[AcqFull]) begin
if (cfg.slow_acq) begin
acq_fifo_empty = 0;
while (!acq_fifo_empty) begin
delay = $urandom_range(50, 100);
#(delay * 1us);
read_acq_fifo(1, acq_fifo_empty);
end
end else begin
read_acq_fifo(0, acq_fifo_empty);
end
end else if (cfg.intr_vif.pins[TxStretch]) begin
proc_intr_txstretch();
end else if (cfg.intr_vif.pins[CmdComplete]) begin
proc_intr_cmdcomplete();
end else if (cfg.read_all_acq_entries) begin
read_acq_fifo(0, acq_fifo_empty);
end else begin
for (int i = 0; i < NumI2cIntr; i++) begin
i2c_intr_e my_intr = i2c_intr_e'(i);
if (!expected_intr.exists(my_intr)) begin
if (cfg.intr_vif.pins[i] !== 0) begin
`uvm_error("process_target_interrupts",
$sformatf("Unexpected unterrupt is set %s", my_intr.name))
end
end
end
end // else: !if(cfg.intr_vif.pins[CmdComplete])
if (cfg.use_drooling_tx & (read_cmd_q.size > 0 || read_on_going == 1)) begin
bit dum;
if (read_cmd_q.size > 0) dum = read_cmd_q.pop_front();
drooling_write_tx_fifo();
end
// When bad command is dropped, expected rd_byte is adjust in 'write_tx_fifo'.
// But for the last bad read command, we have to adjust expected rd_byte separately
// because we will not call 'write_tx_fifo'.
if (adjust_exp_read_byte == 1 &&
cfg.sent_acq_cnt == cfg.rcvd_acq_cnt) begin
int read_size;
while (cfg.m_i2c_agent_cfg.read_addr_q.size > 0) begin
read_size = read_rcvd.pop_front();
if (!cfg.m_i2c_agent_cfg.read_addr_q.pop_front) begin
repeat (read_size) void'(read_txn_q.pop_front());
cfg.m_i2c_agent_cfg.sent_rd_byte -= read_size;
end
end
end
end
endtask
// Fill tx fifo for a single read transaction.
task write_tx_fifo(bit add_delay = 0);
uvm_reg_data_t data;
int read_size;
int rd_txfifo_timeout_ns = 100_000;
// indefinite time
int tx_empty_timeout_ns = 500_000_000;
bit is_valid;
string id = "write_tx_fifo";
if (cfg.m_i2c_agent_cfg.allow_bad_addr) begin
is_valid = cfg.m_i2c_agent_cfg.read_addr_q.pop_front();
if (!is_valid) begin
read_size = read_rcvd.pop_front();
repeat (read_size) void'(read_txn_q.pop_front());
cfg.m_i2c_agent_cfg.sent_rd_byte -= read_size;
return;
end
end
if (add_delay) id = {id, "_delay"};
if (read_rcvd.size() > 0) begin
read_size = read_rcvd.pop_front();
`uvm_info(id, $sformatf("read_size :%0d", read_size), UVM_HIGH)
end else begin
// if ack stop test mode,
// test need to feed 1 extraa tx fifo data at the end
// to avoid dead lock
if (cfg.m_i2c_agent_cfg.allow_ack_stop) begin
`uvm_info(id, "feed extra ack/stop data", UVM_MEDIUM)
csr_wr(.ptr(ral.txdata), .value('hff));
pre_feed_rd_data_q.push_back(8'hff);
pre_feed_cnt++;
end
end
while (read_size > 0) begin
cfg.clk_rst_vif.wait_clks(1);
if (add_delay) begin
if ($urandom_range(0, 1)) begin
// Wait for txfifo to be empty before adding data,
// this creates more stretching conditions.
csr_spinwait(.ptr(ral.status.txempty), .exp_data(1'b1),
.timeout_ns(tx_empty_timeout_ns));
end
end
if (read_txn_q.size() > 0) begin
i2c_item item;
// check tx fifo is full
if (cfg.use_intr_handler) begin
data = 1;
// If tx_stretch interrupt is set,
// interrupt handler in tb routine is to fill up tx fifo and empty acq fifo.
// if tx fifo is alreay full, this routine can block 'read_acq_fifo'
// and causes deadlock by waiting txempty forever.
// So if interrupt handler is used, make each task not block the other
while (data) begin
bit is_empty;
csr_rd(.ptr(ral.status.txfull), .value(data));
// if tx fifo is full, real acq fifo to remove
// tx_stretch condition
if (data) begin
read_acq_fifo(0, is_empty);
end
end
end else begin
csr_spinwait(.ptr(ral.status.txfull), .exp_data(1'b0),
.timeout_ns(rd_txfifo_timeout_ns));
end
`uvm_create_obj(i2c_item, item)
item = read_txn_q.pop_front();
`uvm_info(id, $sformatf("send rdata:%x", item.wdata), UVM_MEDIUM)
// Send only there is no pre feed data
if (pre_feed_cnt == 0) csr_wr(.ptr(ral.txdata), .value(item.wdata));
else pre_feed_cnt--;
read_size--;
end
end
endtask
// Write tx fifo in drooling tx mode (cfg.use_drooling_tx = 1)
// When dut received read command, fill up tx fifo with random
// number of entries.
// This can create 'tx_fifo_full or tx_fifo_empty' depends on
// number of filled entries.
task drooling_write_tx_fifo();
string id = "drooling_write_tx_fifo";
bit [7:0] wdata;
int lvl;
uvm_reg_data_t data;
if (read_rcvd.size == 0 && read_on_going == 0) begin
`uvm_error(id, "read_rcvd size is zero and read_on_going is zero")
end
if (read_on_going == 0) begin
`uvm_create_obj(i2c_item, drooling_exp_rd_item)
drooling_read_size = read_rcvd.pop_front();
`uvm_info(id, $sformatf("read_size :%0d", drooling_read_size), UVM_MEDIUM)
`DV_WAIT(read_txn_q.size > 0,, cfg.spinwait_timeout_ns, id)
drooling_exp_rd_item = read_txn_q.pop_front();
drooling_exp_rd_item.tran_id = drooling_rx_cnt++;
drooling_exp_rd_item.data_q.delete();
read_on_going = 1;
end
// Previously, in tx overflow test, expected data can only be sent when
// tx fifo is not full, but since we hae spill_over_data_q which has
// 'past' expected data, this can be sent regardless of tx_fifo status.
if (read_on_going) begin
while (drooling_read_size > 0 && spill_over_data_q.size > 0) begin
drooling_exp_rd_item.data_q.push_back(spill_over_data_q.pop_front);
drooling_read_size--;
end
if (drooling_read_size == 0) begin
p_sequencer.target_mode_rd_exp_port.write(drooling_exp_rd_item);
read_on_going = 0;
end
end
lvl = $urandom_range(1, 200);
`uvm_info(id, $sformatf("spill %0d entries", lvl), UVM_MEDIUM)
// Fill up tx fifo with lvl entries.
repeat (lvl) begin
cfg.clk_rst_vif.wait_clks(1);
wdata = $urandom();
csr_wr(.ptr(ral.txdata), .value(wdata));
// check if tx_fifo is ovf.
csr_rd(.ptr(ral.intr_state.tx_overflow), .value(data));
if (data == 0) begin
spill_over_data_q.push_back(wdata);
if (drooling_read_size > 0) begin
drooling_exp_rd_item.data_q.push_back(spill_over_data_q.pop_front());
drooling_read_size--;
if (drooling_read_size == 0) begin
p_sequencer.target_mode_rd_exp_port.write(drooling_exp_rd_item);
read_on_going = 0;
end
end
end else begin // if (data == 0)
clear_interrupt(TxOverflow);
end // else: !if(data == 0)
end // repeat (lvl)
endtask
// when read_one = 1. check acqempty and read a single entry
// and return acq_fifo_empty.
// When read_one = 0, read acq fifo up to acqlvl and convert read data
// to i2c_item and send to wr_obs_port
task read_acq_fifo(bit read_one, ref bit acq_fifo_empty);
uvm_reg_data_t read_data;
i2c_item obs;
acq_fifo_empty = 0;
if (read_one) begin
// Polling if status.acqempty is zero and skip read fifo
// if fifo is empty.
csr_rd(.ptr(ral.status.acqempty), .value(acq_fifo_empty));
read_data = (acq_fifo_empty)? 0 : 1;
end else csr_rd(.ptr(ral.fifo_status.acqlvl), .value(read_data));
repeat(read_data) begin
// read one entry and compare
csr_rd(.ptr(ral.acqdata), .value(read_data));
`uvm_info("process_acq", $sformatf("acq data %x", read_data), UVM_MEDIUM)
// Capture the same read data from 'process_tl_access' sb
obs = acq2item(read_data);
// Push read command to read_cmd_q to trigger drooling_wr_fifo
if (obs.read & cfg.use_drooling_tx) begin
read_cmd_q.push_back(1);
end
end
if (read_data == 0) begin
cfg.clk_rst_vif.wait_clks(1);
`uvm_info("process_acq", $sformatf("acq_dbg: sent:%0d rcvd:%0d acq_is_empty",
cfg.sent_acq_cnt, cfg.rcvd_acq_cnt), UVM_HIGH)
end
endtask // read_acq_fifo
function drv_type_e get_eos(bit is_stop = 0);
drv_type_e rsp = HostNAck;
if (cfg.m_i2c_agent_cfg.allow_ack_stop) begin
if (read_ack_nack_q.pop_front()) begin
rsp = HostNAck;
end else begin
rsp = HostAck;
if (is_stop) begin
cfg.sent_ack_stop++;
end
end
end
return rsp;
endfunction // get_eos
// Calling this task will trigger unexp_stop interrupt.
task send_ack_stop();
i2c_target_base_seq m_i2c_host_seq;
i2c_item txn_q[$];
cfg.rs_pct = 0;
cfg.wr_pct = 0;
`uvm_create_obj(i2c_target_base_seq, m_i2c_host_seq)
create_txn(txn_q);
fetch_txn(txn_q, m_i2c_host_seq.req_q, 1);
m_i2c_host_seq.start(p_sequencer.i2c_sequencer_h);
sent_txn_cnt++;
endtask
virtual task stop_target_interrupt_handler();
string id = "stop_interrupt_handler";
int acq_rd_cyc;
acq_rd_cyc = 9 * (thigh + tlow);
`DV_WAIT(cfg.sent_acq_cnt > 0,, cfg.spinwait_timeout_ns, id)
`DV_WAIT(sent_txn_cnt == num_trans,, cfg.long_spinwait_timeout_ns, id)
cfg.read_all_acq_entries = 1;
if (cfg.rd_pct != 0) begin
`DV_WAIT(cfg.m_i2c_agent_cfg.sent_rd_byte > 0,, cfg.spinwait_timeout_ns, id)
if (cfg.m_i2c_agent_cfg.allow_bad_addr) adjust_exp_read_byte = 1;
`DV_WAIT(cfg.m_i2c_agent_cfg.sent_rd_byte == cfg.m_i2c_agent_cfg.rcvd_rd_byte,,
cfg.long_spinwait_timeout_ns, id)
end
`DV_WAIT(cfg.sent_acq_cnt == cfg.rcvd_acq_cnt,, cfg.spinwait_timeout_ns, id)
csr_spinwait(.ptr(ral.status.acqempty), .exp_data(1'b1));
if (cfg.m_i2c_agent_cfg.allow_ack_stop) send_ack_stop();
// add drain time before stop interrupt handler
cfg.clk_rst_vif.wait_clks(1000);
// Add extra draintime for tx overflow test
if (cfg.use_drooling_tx) begin
cfg.clk_rst_vif.wait_clks(256 * acq_rd_cyc);
end
cfg.stop_intr_handler = 1;
`uvm_info(id, "called stop_intr_handler", UVM_MEDIUM)
endtask // stop_target_interrupt_handler
// This task is called when tb interrupt handler receives
// cmdcomplete interrupt.
virtual task proc_intr_cmdcomplete();
bit acq_fifo_empty;
int delay;
if (cfg.slow_acq) begin
if($urandom()%2) begin
acq_fifo_empty = 0;
while (!acq_fifo_empty) begin
delay = $urandom_range(1, 50);
#(delay * 1us);
read_acq_fifo(1, acq_fifo_empty);
end
end
end else begin
// read one entry at a time to create acq fifo back pressure
read_acq_fifo(1, acq_fifo_empty);
end
clear_interrupt(CmdComplete, 0);
endtask // proc_intr_cmdcomplete
// This task is called when tb interrupt handler receives
// txstretch interrupt.
virtual task proc_intr_txstretch();
bit acq_fifo_empty;
if (!cfg.use_drooling_tx) begin
write_tx_fifo();
end
read_acq_fifo(0, acq_fifo_empty);
// interrupt can't be clear until
// txfifo get data or acq fifo get entry. So verify_clear can
// causes deadlock. Set verify_clear to 0 to avoid deadlock
clear_interrupt(TxStretch, 0);
endtask // proc_intr_txstretch
// return target address between, address0, address1 and illegal address
// illegal address can be return ony if cfg.bad_addr_pct > 0
// Make sure the last transaction should return good address to guarantee
// at least one good transaction
function bit [6:0] get_target_addr();
if (sent_txn_cnt == (num_trans - 1)) return target_addr0;
randcase
cfg.bad_addr_pct: begin
return illegal_addr;
end
(10 - cfg.bad_addr_pct): begin
randcase
1: get_target_addr = target_addr0;
1: get_target_addr = target_addr1;
endcase
end
endcase
endfunction
function bit is_target_addr(bit [6:0] addr);
return (addr == target_addr0 || addr == target_addr1);
endfunction
endclass : i2c_base_vseq