Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions hw/top_chip/dv/env/seq_lib/top_chip_dv_i2c_device_tx_rx_vseq.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Copyright lowRISC contributors (COSMIC project).
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

class top_chip_dv_i2c_device_tx_rx_vseq extends top_chip_dv_i2c_tx_rx_vseq;
`uvm_object_utils(top_chip_dv_i2c_device_tx_rx_vseq)

// TODO: Remove once #600 is merged. This is maintained by SW to make this Vseq in sync

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR #600 has been merged now. Could you rebase and remove this workaround?

local bit [7:0] tx_fifo_wr_done[1];
local rand bit xfer_addr;

extern function new(string name="");

// Fill in the I2C transfer fields before driving it
extern local function void fill_i2c_xfer_flds(i2c_item item, rw_e dir, bus_op_e bus_op);

// Creates and starts the transfer
extern local task create_and_drive_i2c_xfer(i2c_item item, rw_e dir, bus_op_e bus_op);

// Convert the I2C transfer packet into the form which is understandable by the I2C driver
extern local function void conv_i2c_xfer_to_drv_type(ref i2c_item item_q[$], i2c_item xfer);

// Label the packet in the transfer with drv_type_e and assign wdata. i2c_driver takes an action
// based on the label.
extern local function i2c_item assign_drv_type_and_wdata(drv_type_e drv_type, bit [7:0] data);

// Manage the I2C transfer
extern local task i2c_xfer();
extern task body();
endclass : top_chip_dv_i2c_device_tx_rx_vseq

function top_chip_dv_i2c_device_tx_rx_vseq::new(string name = "");
super.new(name);
endfunction

function void top_chip_dv_i2c_device_tx_rx_vseq::fill_i2c_xfer_flds(i2c_item item,
rw_e dir,
bus_op_e bus_op);
item.addr = (xfer_addr) ? device_addr0[0] : device_addr1[0];
item.num_data = xfer_bytes[0];
item.addr_ack = ACK;
item.dir = dir;
item.bus_op = bus_op;
item.start = 1;
item.stop = 1;

// We don't need to fill data_ack_q for BusOpWrite, as N/Acking is the device's job.
if (bus_op == BusOpRead) begin
for (int unsigned i = 0; i < xfer_bytes[0]; i++) begin
// The host acks every byte except the last one to terminate the transfer
acknack_e ack_nack = (i == (xfer_bytes[0] - 1)) ? NACK : ACK;
item.data_ack_q.push_back(ack_nack);
end
end
endfunction

function i2c_item top_chip_dv_i2c_device_tx_rx_vseq::assign_drv_type_and_wdata(drv_type_e drv_type,
bit [7:0] data);
i2c_item item = i2c_item::type_id::create("item");
item.drv_type = drv_type;
item.wdata = data;
return item;
endfunction

function void top_chip_dv_i2c_device_tx_rx_vseq::conv_i2c_xfer_to_drv_type(ref i2c_item item_q[$],
i2c_item xfer);
// Each transfer starts with a start condition (ignoring repeated start for now)
if (xfer.start) item_q.push_back(assign_drv_type_and_wdata(HostStart, 'd0));

// Send Address + Direction information
item_q.push_back(assign_drv_type_and_wdata(HostData, (xfer.addr[6:0] << 1) | xfer.dir));

for(int unsigned i = 0; i < xfer_bytes[0]; i++) begin
case (xfer.bus_op)
BusOpRead: begin
drv_type_e ack_nack = (xfer.data_ack_q[i] == i2c_pkg::ACK) ? HostAck : HostNAck;
item_q.push_back(assign_drv_type_and_wdata(ack_nack, '0));
end
BusOpWrite:
// For write bytes, insert the data bytes in the data_q
item_q.push_back(assign_drv_type_and_wdata(HostData, xfer.data_q[i]));
default:;
endcase
end

// Assumes that each transfer ends with a stop. Ignoring repeated start for now
if (xfer.stop) item_q.push_back(assign_drv_type_and_wdata(HostStop, 'd0));
endfunction

task top_chip_dv_i2c_device_tx_rx_vseq::create_and_drive_i2c_xfer(i2c_item item,
rw_e dir,
bus_op_e bus_op);

i2c_target_base_seq host_seq = i2c_target_base_seq::type_id::create("host_seq");

fill_i2c_xfer_flds(item, dir, bus_op);
conv_i2c_xfer_to_drv_type(host_seq.req_q, item);

// The host_seq pops the items from the front inserted in host_seq.req_q through
// conv_i2c_xfer_to_drv_type() and sends those items to i2c_driver via the start_item() call.
host_seq.start(p_sequencer.i2c_sqr);
endtask

task top_chip_dv_i2c_device_tx_rx_vseq::i2c_xfer();
i2c_item xfer = i2c_item::type_id::create("xfer");

create_and_drive_i2c_xfer(xfer, READ, BusOpRead);

// Check if the agent received all the bytes
if (cfg.m_i2c_agent_cfg.rcvd_rd_byte != xfer_bytes[0])
`uvm_fatal(`gfn,
$sformatf("Agent received %0d bytes but expecting %0d",
cfg.m_i2c_agent_cfg.rcvd_rd_byte,
xfer_bytes[0]))

// The idea here is to write all the bytes previously read by the host.
//
// i2c_monitor has a port "controller_mode_rd_item_port" that contains the read transfer
// information. It is connected with the analysis FIFO "i2c_rd_xfer_fifo". Once, the transfer is
// finished, i2c_monitor writes that i2c_item to the controller_mode_rd_item_port. Check that this
// item exist in the analysis FIFO i2c_rd_xfer_fifo.
if (!p_sequencer.i2c_rd_xfer_fifo.used())
`uvm_fatal(`gfn, "Agent didn't push the last read transfer in the FIFO")

// Get the last transfer from the analysis FIFO
p_sequencer.i2c_rd_xfer_fifo.get(xfer);

// Now xfer contains the information involved in the last read transfer. The read bytes should be
// saved in data_q. Those are going to be the data bytes written to the target in the write
// transfer below.
create_and_drive_i2c_xfer(xfer, WRITE, BusOpWrite);
endtask

task top_chip_dv_i2c_device_tx_rx_vseq::body();
// Configure the agent to be the Host
cfg.m_i2c_agent_cfg.if_mode = Host;
super.body();
`DV_WAIT(cfg.sw_test_status_vif.sw_test_status == SwTestStatusInTest);

configure_agent_timing();
print_i2c_timing_cfg();

// Wait until SW is done writing to the TX FIFO
//
// TODO: Remove when #600 is merged

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here #600 is now merged

while (!tx_fifo_wr_done[0]) begin
cfg.sys_clk_vif.wait_n_clks(1);
sw_symbol_backdoor_read("tx_fifo_wr_done", tx_fifo_wr_done);
end

`uvm_info(`gfn, "Starting I2C Device TX-RX test", UVM_LOW)

i2c_xfer();
endtask : body
30 changes: 0 additions & 30 deletions hw/top_chip/dv/env/seq_lib/top_chip_dv_i2c_host_tx_rx_vseq.sv
Original file line number Diff line number Diff line change
Expand Up @@ -11,44 +11,14 @@
class top_chip_dv_i2c_host_tx_rx_vseq extends top_chip_dv_i2c_tx_rx_vseq;
`uvm_object_utils(top_chip_dv_i2c_host_tx_rx_vseq)

// Declare the device_addr as a byte size array because sw_symbol_backdoor_overwrite() expects the
// data that is going to overwrite the SW symbol to be an array
local rand bit [7:0] device_addr[1];

extern constraint device_addr_c;

extern function new(string name="");
extern virtual task dut_init(string reset_kind = "HARD");
extern task body();
endclass : top_chip_dv_i2c_host_tx_rx_vseq

constraint top_chip_dv_i2c_host_tx_rx_vseq::device_addr_c {
device_addr[0] inside {[8'h00 : 8'h7F]};
}

function top_chip_dv_i2c_host_tx_rx_vseq::new(string name = "");
super.new(name);
endfunction

task top_chip_dv_i2c_host_tx_rx_vseq::dut_init(string reset_kind = "HARD");
super.dut_init(reset_kind);

// Read the timing parameters through SW backdoor load
sw_symbol_backdoor_read("sys_clk_period_ns", sw_sys_clk_period_ns);
sw_symbol_backdoor_read("scl_low_time_ns", sw_scl_low_time_ns);
sw_symbol_backdoor_read("hold_data_time_ns", sw_data_hold_time_ns);

// Overwrite the SW symbol with the randomized value
sw_symbol_backdoor_overwrite("byte_count", xfer_bytes);
sw_symbol_backdoor_overwrite("device_addr", device_addr);

scl_low_cycles = round_up_divide({sw_scl_low_time_ns[1], sw_scl_low_time_ns[0]},
sw_sys_clk_period_ns[0]);
sda_hold_cycles = round_up_divide({sw_data_hold_time_ns[1], sw_data_hold_time_ns[0]},
sw_sys_clk_period_ns[0]);

endtask

task top_chip_dv_i2c_host_tx_rx_vseq::body();
i2c_device_response_seq seq = i2c_device_response_seq::type_id::create("seq");

Expand Down
165 changes: 135 additions & 30 deletions hw/top_chip/dv/env/seq_lib/top_chip_dv_i2c_tx_rx_vseq.sv
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,76 @@ class top_chip_dv_i2c_tx_rx_vseq extends top_chip_dv_base_vseq;
// Below variables will get assigned through SW backdoor load. They are defined as byte size
// arrays because "sw_symbol_backdoor_read/overwrite" takes an array as an argument to write or
// read the SW symbol.
protected bit [7:0] sw_sys_clk_period_ns[1];
protected bit [7:0] sw_scl_low_time_ns[2];
protected bit [7:0] sw_data_hold_time_ns[2];
//
// Array of size 1 means that the SW symbol is byte size, size 2 means half word and so on.
local bit [7:0] sw_sys_clk_period_ns[1];
local bit [7:0] sw_scl_low_time_ns[2];
local bit [7:0] sw_scl_high_time_ns[2];
local bit [7:0] sw_data_setup_time_ns[2];
local bit [7:0] sw_data_hold_time_ns[2];
local bit [7:0] sw_setup_start_time_ns[2];
local bit [7:0] sw_hold_start_time_ns[2];
local bit [7:0] sw_setup_stop_time_ns[2];
local bit [7:0] sw_bus_free_time_ns[2];
local bit [7:0] sw_rise_time_ns[2];
local bit [7:0] sw_fall_time_ns[2];

// The timing parameters in cycles used by the agent to add relevant delays before driving the
// responses.
protected bit [15:0] scl_low_cycles;
protected bit [15:0] sda_hold_cycles;

// Number of bytes to read / write in a transfer. This is going to overwrite a SW symbol so that
// the SW will read / write bytes based on the xfer_bytes count
// SCL and SDA.
local bit [15:0] scl_low_cycles;
local bit [15:0] scl_high_cycles;
local bit [15:0] sda_hold_cycles;
local bit [15:0] sda_setup_cycles;
local bit [15:0] start_setup_cycles;
local bit [15:0] start_hold_cycles;
local bit [15:0] stop_setup_cycles;
local bit [15:0] bus_free_cycles;
local bit [15:0] rise_cycles;
local bit [15:0] fall_cycles;

// Number of bytes to read / write in a transfer. This will overwrite a SW symbol so that
// the SW will read / write bytes based on xfer_bytes count
protected rand bit [7:0] xfer_bytes[1];

// Declare the device_addr as a byte size array because sw_symbol_backdoor_overwrite() expects the
// data that is going to overwrite the SW symbol to be an array
protected rand bit [7:0] device_addr0[1];
protected rand bit [7:0] device_addr1[1];

extern constraint xfer_bytes_c;
extern constraint device_addr_c;

extern function new(string name="");

// Returns the ceiling of (a / b), converting a timing parameter "a" in nanoseconds to an integer
// number of cycles by rounding up.
extern protected function int unsigned round_up_divide(int unsigned a, int unsigned b);
extern local function int unsigned round_up_divide(int unsigned a, int unsigned b);

// Calculate timing parameters in the number of cycles using clk_i as the reference clock
extern local function void calc_timing_params_in_cycles();

// Compute timing parameters utilized by the agent to add delays to the responses
// Assign timing parameters with the values defined in i2c_test.h along with assigning random
// byte_count and device_addr values to the SW symbols in i2c_test.h
extern virtual task dut_init(string reset_kind = "HARD");

// Compute timing parameters utilized by the agent to add delays before driving SCL and SDA. The
// calculations are taken from i2c_base_vseq.sv
extern protected function void configure_agent_timing();
extern protected function void print_i2c_timing_cfg();
endclass : top_chip_dv_i2c_tx_rx_vseq

// SW will perform a comparison on each byte that was read and written. To do this accurately,
// the number of bytes should be within the depth of TX / RX FIFO of target and host respectively.
// SW will perform a comparison check on each byte that was written and read. To do this accurately,
// the number of bytes should not exceed the depth of the TX / RX FIFO of the target and host,
// respectively.
constraint top_chip_dv_i2c_tx_rx_vseq::xfer_bytes_c {
xfer_bytes[0] inside {[1 : FifoDepth]};
}

constraint top_chip_dv_i2c_tx_rx_vseq::device_addr_c {
device_addr0[0] inside {[8'h00 : 8'h7F]};
device_addr1[0] inside {[8'h00 : 8'h7F]};
}

function top_chip_dv_i2c_tx_rx_vseq::new(string name = "");
super.new(name);
endfunction
Expand All @@ -48,24 +86,91 @@ function int unsigned top_chip_dv_i2c_tx_rx_vseq::round_up_divide(int unsigned a
return (((a - 1) / b) + 1);
endfunction

function void top_chip_dv_i2c_tx_rx_vseq::calc_timing_params_in_cycles();
scl_low_cycles = round_up_divide({sw_scl_low_time_ns[1], sw_scl_low_time_ns[0]},
sw_sys_clk_period_ns[0]);
scl_high_cycles = round_up_divide({sw_scl_high_time_ns[1], sw_scl_high_time_ns[0]},
sw_sys_clk_period_ns[0]);
sda_setup_cycles = round_up_divide({sw_data_setup_time_ns[1], sw_data_setup_time_ns[0]},
sw_sys_clk_period_ns[0]);
sda_hold_cycles = round_up_divide({sw_data_hold_time_ns[1], sw_data_hold_time_ns[0]},
sw_sys_clk_period_ns[0]);
start_setup_cycles = round_up_divide({sw_setup_start_time_ns[1], sw_setup_start_time_ns[0]},
sw_sys_clk_period_ns[0]);
start_hold_cycles = round_up_divide({sw_hold_start_time_ns[1], sw_hold_start_time_ns[0]},
sw_sys_clk_period_ns[0]);
stop_setup_cycles = round_up_divide({sw_setup_stop_time_ns[1], sw_setup_stop_time_ns[0]},
sw_sys_clk_period_ns[0]);
bus_free_cycles = round_up_divide({sw_bus_free_time_ns[1], sw_bus_free_time_ns[0]},
sw_sys_clk_period_ns[0]);
rise_cycles = round_up_divide({sw_rise_time_ns[1], sw_rise_time_ns[0]},
sw_sys_clk_period_ns[0]);
fall_cycles = round_up_divide({sw_fall_time_ns[1], sw_fall_time_ns[0]},
sw_sys_clk_period_ns[0]);
endfunction

task top_chip_dv_i2c_tx_rx_vseq::dut_init(string reset_kind = "HARD");
super.dut_init(reset_kind);

// Overwrite the SW symbol with the randomized value
sw_symbol_backdoor_overwrite("byte_count", xfer_bytes);
sw_symbol_backdoor_overwrite("device_addr0", device_addr0);
sw_symbol_backdoor_overwrite("device_addr1", device_addr1);

// Read the timing parameters through SW backdoor load
sw_symbol_backdoor_read("sys_clk_period_ns", sw_sys_clk_period_ns);
sw_symbol_backdoor_read("scl_low_time_ns", sw_scl_low_time_ns);
sw_symbol_backdoor_read("scl_high_time_ns", sw_scl_high_time_ns);
sw_symbol_backdoor_read("setup_data_time_ns", sw_data_setup_time_ns);
sw_symbol_backdoor_read("hold_data_time_ns", sw_data_hold_time_ns);
sw_symbol_backdoor_read("setup_start_time_ns", sw_setup_start_time_ns);
sw_symbol_backdoor_read("hold_start_time_ns", sw_hold_start_time_ns);
sw_symbol_backdoor_read("setup_stop_time_ns", sw_setup_stop_time_ns);
sw_symbol_backdoor_read("bus_free_time_ns", sw_bus_free_time_ns);
sw_symbol_backdoor_read("rise_time_ns", sw_rise_time_ns);
sw_symbol_backdoor_read("fall_time_ns", sw_fall_time_ns);

// Calculate all the timing parameters in cycles
calc_timing_params_in_cycles();
endtask

function void top_chip_dv_i2c_tx_rx_vseq::configure_agent_timing();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these changes also have an impact on top_chip_dv_i2c_host_tx_rx_vseq. Isn't it an issue?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that host_tx_rx_test gets affected. I've took these calculations from i2c_base_vseq function get_timing_values(). They look sensible to reason.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, sorry. I think I didn't answer this question well. These parameters are used to add delays which should depend on the speed of the I2C bus and not on host / device part. So, whatever the speed is just calculate the timing parameter the same way

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I was unclear. The issue I see is that you've created the dut_init task for top_chip_dv_i2c_device_tx_rx_vseq to read some SW symbols and then use them in this configure_agent_timing function, all fine here. But in your implementation of top_chip_dv_i2c_host_tx_rx_vseq::dut_init() should also backdoor read these missing SW symbols.

This also needs to be added in host_tx_rx_test.c as done in device_tx_rx_test.c or in a common shared file?

Something like:

const uint16_t scl_high_time_ns = 4000;
const uint16_t setup_data_time_ns = 250;
const uint16_t setup_start_time_ns = 4700;
const uint16_t hold_start_time_ns = 4000;
const uint16_t setup_stop_time_ns = 4000;
const uint16_t bus_free_time_ns = 4700;
const uint16_t rise_time_ns = I2C_RISE_NS;
const uint16_t fall_time_ns = I2C_FALL_NS;

@KinzaQamar KinzaQamar Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, they are not useful for the host test. For host test, the TB is the device responsible for sending responses (rdata, ack, and nack). This is the function i2c_driver.sv#L50 called by the driver where only device_send_bit(_) is used deep down in i2c_if that majorly uses tClockPulse, tClockLow, and tHoldBit. other parameters like tSdaUnstable and tTimeOut are not neccessary for smoke test

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Traced through calc_timing_params_in_cycles() for the host path and I don't think "the remaining parameters default to 0" holds up. round_up_divide(a, b) is ((a - 1) / b) + 1, with a/b as int unsigned. For every parameter host_tx_rx doesn't backdoor-read (scl_high, setup_data, setup_start, hold_start, setup_stop, bus_free, rise, fall), the backing sw_*_time_ns array is never written, so a = 0 when round_up_divide runs. 0 - 1 in unsigned 32-bit arithmetic wraps to 4294967295, not -1, so the result isn't 0 — it's floor(4294967295 / clk_period) + 1 truncated into a 16-bit field. For a 5ns clock period that's 858993460, which truncates to 13108 cycles, not 0. So 8 of the 10 *_cycles fields end up with a clock-period-dependent garbage value, and nearly every field configure_agent_timing() computes (tSetupBit, tClockPulse, tClockLow, tSetupStop, tHoldStop) mixes in at least one of them.

I'd rather we set these explicitly for the host test the same way device_tx_rx_test.c does, rather than leaning on round_up_divide to return 0 for an input it was never written to handle — that keeps both vseqs symmetric and doesn't depend on an accident of integer wraparound working out. Could you add the same 8 consts to host_tx_rx_test.c and the corresponding backdoor reads to top_chip_dv_i2c_host_tx_rx_vseq::dut_init(), same as the device vseq?

@KinzaQamar KinzaQamar Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes, thanks for figuring this out. My intention is just to add stuff that is required for the vseq / test. I can add all the timing parameters in host_tx_rx_test.x but it is not going to be consumed by the i2c_driver when it is driving the device interface. I can go ahead and revert it back to manually calculating scl_low_cycles and scl_hold_cycles. WDYT?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverting fixes the underflow, but tClockPulse/tSetupBit still end up 0 either way, and tClockPulse is one of the three you called essential. Rather than reason about it further, could you run i2c_host_tx_rx and paste the print_i2c_timing_cfg() output here? If it passes cleanly with tClockPulse/tSetupBit at 0, I'll drop this. If not, let's just add the explicit consts to host_tx_rx_test.c instead.

@KinzaQamar KinzaQamar Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but tClockPulse/tSetupBit still end up 0 either way, and tClockPulse is one of the three you called essential.

Oh yes, you are right. Sorry, I am a bit slow today. I'll re-think about it again. Thanks for your careful analysis

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a shared header file i2c_test.h and put the computations of timing parameters in top_chip_dv_tx_rx_vseq

// tSetupBit are the clk_i cycles before SCL goes high to drive SDA. Agent should drive SDA at
// least two cycles before SCL goes high.
int unsigned tSetupBit = 2;
cfg.m_i2c_agent_cfg.timing_cfg.tSetupBit = tSetupBit;

// tHoldBit are the clk_i cycles to hold SDA after SCL goes low.
cfg.m_i2c_agent_cfg.timing_cfg.tHoldBit = sda_hold_cycles;

// Used by i2c_if to stretch SCL by the amount of tClockpulse clk_i cycles before driving SDA. If
// tClockPulse is greater than the clk_i cycles taken by an SCL pulse, then i2c_monitor
// acknowledges the "ack" late and then drives Rdata on SDA when SCL is high.
cfg.m_i2c_agent_cfg.timing_cfg.tClockPulse = scl_low_cycles;

// tClockLow are the clk_i cycles that the i2c_driver use before driving SDA after stretching SCL
// by tClockPulse clk_i cycles. Drive SDA at least tSetupBit cycles earlier to avoid the chances
// of SDA interference.
cfg.m_i2c_agent_cfg.timing_cfg.tClockLow = scl_low_cycles - tSetupBit;
// tHoldStart are the clk_i cycles to hold SDA low when SCL is high. Once SDA is low, the wait of
// fall time and hold start time is required before pulling SCL low.
cfg.m_i2c_agent_cfg.timing_cfg.tHoldStart = fall_cycles + start_hold_cycles;

// Once i2c_if is done holding SDA low after the start condition, it pulls SCL low and waits for
// tClockStart clk_i cycles before preparing to drive data on SDA.
cfg.m_i2c_agent_cfg.timing_cfg.tClockStart = sda_hold_cycles;

// tClockLow are the clk_i cycles delay before driving SDA. The SCL low period includes setup and
// hold SDA times. The reason we subtract rise_cycles is that the later timing parameter
// tSetupBit use it in order to hold SDA once it is driven.
//
// The explanation of +1 is given in i2c_base_vseq under get_timing_values().
cfg.m_i2c_agent_cfg.timing_cfg.tClockLow = scl_low_cycles - (rise_cycles + sda_setup_cycles +
sda_hold_cycles + 1);

// tSetupBit are the clk_i cycles to hold the driven SDA during SCL low period.
cfg.m_i2c_agent_cfg.timing_cfg.tSetupBit = rise_cycles + sda_setup_cycles;

// For host, tClockPulse are the clk_i cycles to hold SCL high pulse. For Device, this is used as
// a clock stretching delay.
cfg.m_i2c_agent_cfg.timing_cfg.tClockPulse = rise_cycles + scl_high_cycles;

// tHoldBit are the clk_i cycles to hold SDA once SCL goes low.
cfg.m_i2c_agent_cfg.timing_cfg.tHoldBit = fall_cycles + sda_hold_cycles;

// tClockStop are the clk_i cycles delay before driving the SCL pulse for stop condition. We don't
// need to subtract any other parameter delay except sda_hold_cycles that was holding the N/Ack.
cfg.m_i2c_agent_cfg.timing_cfg.tClockStop = (fall_cycles + scl_low_cycles) - sda_hold_cycles;

// tSetupStop are the clk_i cycles delay before driving SDA high for stop condition.
cfg.m_i2c_agent_cfg.timing_cfg.tSetupStop = rise_cycles + stop_setup_cycles;

// tHoldStop are the clk_i cycles to hold the stop condition. Subtracts setup_start_cycles to initiate
// the next start / restart timely.
cfg.m_i2c_agent_cfg.timing_cfg.tHoldStop = (rise_cycles + bus_free_cycles) - start_setup_cycles;
endfunction

function void top_chip_dv_i2c_tx_rx_vseq::print_i2c_timing_cfg();
Expand Down
Loading
Loading