From 718246b199d83e1fcbc225d4b47cf069f69b84b5 Mon Sep 17 00:00:00 2001 From: Kinza Qamar Date: Mon, 22 Jun 2026 13:00:54 +0100 Subject: [PATCH 1/3] [i2c, dv] Modified the vseq to support TX_RX tests for both host / device Signed-off-by: Kinza Qamar --- .../env/seq_lib/top_chip_dv_i2c_tx_rx_vseq.sv | 89 +++++++++++++++---- hw/top_chip/dv/env/top_chip_dv_env_pkg.sv | 1 + 2 files changed, 71 insertions(+), 19 deletions(-) diff --git a/hw/top_chip/dv/env/seq_lib/top_chip_dv_i2c_tx_rx_vseq.sv b/hw/top_chip/dv/env/seq_lib/top_chip_dv_i2c_tx_rx_vseq.sv index 666e17633..b0f2c96f1 100644 --- a/hw/top_chip/dv/env/seq_lib/top_chip_dv_i2c_tx_rx_vseq.sv +++ b/hw/top_chip/dv/env/seq_lib/top_chip_dv_i2c_tx_rx_vseq.sv @@ -8,14 +8,38 @@ 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. + // + // Array of size 1 means that the SW symbol is byte size, size 2 means half word and so on. 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_scl_high_time_ns[2]; + protected bit [7:0] sw_data_setup_time_ns[2]; protected bit [7:0] sw_data_hold_time_ns[2]; + protected bit [7:0] sw_setup_start_time_ns[2]; + protected bit [7:0] sw_hold_start_time_ns[2]; + protected bit [7:0] sw_setup_stop_time_ns[2]; + protected bit [7:0] sw_hold_stop_time_ns[2]; + protected bit [7:0] sw_rise_time_ns[2]; + protected 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. + // SCL and SDA. protected bit [15:0] scl_low_cycles; + protected bit [15:0] scl_high_cycles; protected bit [15:0] sda_hold_cycles; + protected bit [15:0] sda_setup_cycles; + protected bit [15:0] start_setup_cycles; + protected bit [15:0] start_hold_cycles; + protected bit [15:0] stop_setup_cycles; + protected bit [15:0] stop_hold_cycles; + protected bit [15:0] rise_cycles; + protected 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]; + + extern constraint xfer_bytes_c; extern function new(string name=""); @@ -23,11 +47,19 @@ class top_chip_dv_i2c_tx_rx_vseq extends top_chip_dv_base_vseq; // number of cycles by rounding up. extern protected function int unsigned round_up_divide(int unsigned a, int unsigned b); - // Compute timing parameters utilized by the agent to add delays to the responses + // 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 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]}; +} + function top_chip_dv_i2c_tx_rx_vseq::new(string name = ""); super.new(name); endfunction @@ -37,23 +69,42 @@ function int unsigned top_chip_dv_i2c_tx_rx_vseq::round_up_divide(int unsigned a endfunction function void top_chip_dv_i2c_tx_rx_vseq::configure_agent_timing(); - // 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 / 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 + stop_hold_cycles) - start_setup_cycles; endfunction function void top_chip_dv_i2c_tx_rx_vseq::print_i2c_timing_cfg(); diff --git a/hw/top_chip/dv/env/top_chip_dv_env_pkg.sv b/hw/top_chip/dv/env/top_chip_dv_env_pkg.sv index e65aaab3d..ad8a50d44 100644 --- a/hw/top_chip/dv/env/top_chip_dv_env_pkg.sv +++ b/hw/top_chip/dv/env/top_chip_dv_env_pkg.sv @@ -10,6 +10,7 @@ package top_chip_dv_env_pkg; import sw_test_status_pkg::*; import uart_agent_pkg::*; import gpio_env_pkg::NUM_GPIOS; + import i2c_reg_pkg::FifoDepth; // Macro includes `include "uvm_macros.svh" From f6968565ebcb2f36b09c567bf88e221754a2ba0c Mon Sep 17 00:00:00 2001 From: Kinza Qamar Date: Mon, 22 Jun 2026 16:37:25 +0100 Subject: [PATCH 2/3] [i2c, sw] Added I2C device TX_RX test Signed-off-by: Kinza Qamar --- sw/device/lib/hal/i2c.c | 65 ++++++++++ sw/device/lib/hal/i2c.h | 35 +++++- sw/device/tests/CMakeLists.txt | 1 + sw/device/tests/i2c/device_tx_rx_test.c | 155 ++++++++++++++++++++++++ 4 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 sw/device/tests/i2c/device_tx_rx_test.c diff --git a/sw/device/lib/hal/i2c.c b/sw/device/lib/hal/i2c.c index c854da576..fefb543e7 100644 --- a/sw/device/lib/hal/i2c.c +++ b/sw/device/lib/hal/i2c.c @@ -217,3 +217,68 @@ uint8_t i2c_rdata_byte(i2c_t i2c) i2c_rdata rdata_reg = VOLATILE_READ(i2c->rdata); return rdata_reg.rdata; } + +void i2c_enable_target_mode(i2c_t i2c) +{ + VOLATILE_WRITE(i2c->ctrl, i2c_ctrl_enabletarget); +} + +i2c_acqdata i2c_read_acqdata(i2c_t i2c) +{ + return VOLATILE_READ(i2c->acqdata); +} + +void i2c_set_target_id(i2c_t i2c, uint8_t addr0, uint8_t mask0, uint8_t addr1, uint8_t mask1) +{ + i2c_target_id target_id = { + .address0 = addr0, .mask0 = mask0, .address1 = addr1, .mask1 = mask1 + }; + VOLATILE_WRITE(i2c->target_id, target_id); +} + +void i2c_write_tx_data(i2c_t i2c, uint8_t data) +{ + i2c_txdata txdata = { .txdata = data }; + VOLATILE_WRITE(i2c->txdata, txdata); +} + +void i2c_clear_cmd_complete(i2c_t i2c) +{ + VOLATILE_WRITE(i2c->intr_state, i2c_intr_cmd_complete); +} + +i2c_intr i2c_read_intr_state(i2c_t i2c) +{ + return VOLATILE_READ(i2c->intr_state); +} + +i2c_status i2c_read_status(i2c_t i2c) +{ + return VOLATILE_READ(i2c->status); +} + +bool i2c_target_drain_start_sig_acq_data(i2c_t i2c) +{ + // ACQ FIFO fills the byte and the signal information from the start till the end of + // the transfer. + i2c_acqdata acq_data = i2c_read_acqdata(i2c); + + if (!(acq_data.signal == start)) { + return false; + } + + return true; +} + +bool i2c_target_drain_end_sig_acq_data(i2c_t i2c) +{ + // ACQ FIFO fills the byte and the signal information from the start till the end of + // the transfer. + i2c_acqdata acq_data = i2c_read_acqdata(i2c); + + if (!(acq_data.signal == stop)) { + return false; + } + + return true; +} diff --git a/sw/device/lib/hal/i2c.h b/sw/device/lib/hal/i2c.h index ac7190c44..018460889 100644 --- a/sw/device/lib/hal/i2c.h +++ b/sw/device/lib/hal/i2c.h @@ -110,6 +110,12 @@ typedef struct { uint16_t bus_free_time_cycles; } i2c_timing_params_t; +enum : uint8_t { + none = 0x0, + start = 0x1, + stop = 0x2, +}; + void i2c_init(i2c_t i2c, i2c_speed_mode_t speed_mode); // Transmits multiple bytes to the target @@ -125,5 +131,32 @@ bool i2c_wait_transfer_finish(i2c_t i2c); // Enable I2C in controller mode void i2c_enable_controller_mode(i2c_t i2c); -// Return the data in the target's tx fifo +// Enable I2C block in target mode +void i2c_enable_target_mode(i2c_t i2c); + +// Return the data in the target's TX FIFO uint8_t i2c_rdata_byte(i2c_t i2c); + +// Return the acquire data in the ACQ FIFO +i2c_acqdata i2c_read_acqdata(i2c_t i2c); + +// Write data in TX FIFO +void i2c_write_tx_data(i2c_t i2c, uint8_t data); + +// Set I2C target's address and mask pairs +void i2c_set_target_id(i2c_t i2c, uint8_t addr0, uint8_t mask0, uint8_t addr1, uint8_t mask1); + +// Clear cmd_complete interrupt state +void i2c_clear_cmd_complete(i2c_t i2c); + +// Read the intr_state register to check the interrupt states generated byt the I2C transfer +i2c_intr i2c_read_intr_state(i2c_t i2c); + +// Read status register to check the status of FIFOs during or at the end of the I2C transfer +i2c_status i2c_read_status(i2c_t i2c); + +// Drains the entry that contains the start signal from the ACQ FIFO +bool i2c_target_drain_start_sig_acq_data(i2c_t i2c); + +// Drains the entry that contains the end signal from the ACQ FIFO +bool i2c_target_drain_end_sig_acq_data(i2c_t i2c); diff --git a/sw/device/tests/CMakeLists.txt b/sw/device/tests/CMakeLists.txt index 1613ff128..1d8c35ae5 100644 --- a/sw/device/tests/CMakeLists.txt +++ b/sw/device/tests/CMakeLists.txt @@ -18,6 +18,7 @@ mocha_add_test(NAME gpio_reg_access_test SOURCES gpio/reg_access_test.c LIBRARIE mocha_add_test(NAME gpio_smoketest SOURCES gpio/smoketest.c LIBRARIES ${LIBS} SKIP_VERILATOR SKIP_FPGA) mocha_add_test(NAME i2c_temperature_sensor_test SOURCES i2c/temperature_sensor_test.c LIBRARIES ${LIBS}) mocha_add_test(NAME i2c_host_tx_rx_test SOURCES i2c/host_tx_rx_test.c LIBRARIES ${LIBS} SKIP_VERILATOR SKIP_FPGA) +mocha_add_test(NAME i2c_device_tx_rx_test SOURCES i2c/device_tx_rx_test.c LIBRARIES ${LIBS} SKIP_VERILATOR SKIP_FPGA) mocha_add_test(NAME mailbox_smoketest SOURCES mailbox/smoketest.c LIBRARIES ${LIBS}) mocha_add_test(NAME plic_smoketest SOURCES plic/smoketest.c LIBRARIES ${LIBS}) mocha_add_test(NAME pwrmgr_smoketest SOURCES pwrmgr/smoketest.c LIBRARIES ${LIBS}) diff --git a/sw/device/tests/i2c/device_tx_rx_test.c b/sw/device/tests/i2c/device_tx_rx_test.c new file mode 100644 index 000000000..81957e5c0 --- /dev/null +++ b/sw/device/tests/i2c/device_tx_rx_test.c @@ -0,0 +1,155 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "hal/i2c.h" +#include "hal/mmio.h" +#include "hal/mocha.h" +#include +#include + +#define TX_FIFO_DEPTH (64) + +// The const variables below are treated as symbols read by top_chip_dv_i2c_host_tx_rx_vseq in order +// to calculate agent timing parameters. +const uint8_t sys_clk_period_ns = SYSCLK_NS; + +// The constants assigned below are the spec minimums for standard mode speed except +// hold_data_time_ns which should be at least one according to OpenTitan's programming guide. +const uint16_t scl_low_time_ns = 4700; +const uint16_t scl_high_time_ns = 4000; +const uint16_t hold_data_time_ns = 1; +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 hold_stop_time_ns = 4700; +const uint16_t rise_time_ns = I2C_RISE_NS; +const uint16_t fall_time_ns = I2C_FALL_NS; +const uint8_t device_mask0 = 0x7F; +const uint8_t device_mask1 = 0x7F; + +// The symbols below are going to be overwritten through sw_symbol_backdoor_overwrite() in +// top_chip_dv_i2c_device_tx_rx_vseq.sv +volatile const uint8_t device_addr0 = 0x0; +volatile const uint8_t device_addr1 = 0x0; +volatile const uint8_t byte_count = TX_FIFO_DEPTH; +volatile uint8_t tx_fifo_wr_done = 0x0; + +static bool check_num_bytes() +{ + return ((byte_count > 0) && (byte_count <= TX_FIFO_DEPTH)); +} + +// Wait for the read transfer to complete by checking interrupt state and status register fields. +static bool wait_read_finish(i2c_t i2c) +{ + while (true) { + if (i2c_read_intr_state(i2c) & i2c_intr_cmd_complete) { + // If the SW is here, then transfer is terminated with an `stop`. Check if every byte in + // TX FIFO was sent to the host. + if (!(i2c_read_status(i2c) & i2c_status_txempty)) { + return false; + } + return true; + } + } +} + +static bool drain_acq_fifo(i2c_t i2c) +{ + return (i2c_target_drain_start_sig_acq_data(i2c) && i2c_target_drain_end_sig_acq_data(i2c)); +} + +static bool read_transfer_done(i2c_t i2c) +{ + // Wait until host done reading all the bytes in TX FIFO + if (!wait_read_finish(i2c)) { + return false; + } + + // Drain the ACQ FIFO so that the comparison between read / write bytes can perform accurately + if (!drain_acq_fifo(i2c)) { + return false; + } + + return true; +} + +static bool write_transfer_done(i2c_t i2c) +{ + // Wait for the write transfer to complete by checking interrupt state. + while (true) { + if (i2c_read_intr_state(i2c) & i2c_intr_cmd_complete) { + // Drop the address, direction and start signal entry + if (!i2c_target_drain_start_sig_acq_data(i2c)) { + return false; + } + return true; + } + } +} + +static bool compare_read_write_bytes(i2c_t i2c, uint8_t num_bytes, const uint8_t *data) +{ + for (uint8_t i = 0; i < num_bytes; i++) { + i2c_acqdata acq_data = i2c_read_acqdata(i2c); + + // The data byte contained in the ACQ FIFO should have the signal stored as "none" + if ((data[i] != acq_data.abyte) || (acq_data.signal != none)) { + return false; + } + } + + return true; +} + +static bool device_tx_rx_test(i2c_t i2c) +{ + uint8_t data_bytes[byte_count]; + + if (!check_num_bytes()) { + return false; + } + + // Set the target addresses. Both address masks are set as 0x7F to match every bit of + // device_addr0 and device_addr1. Now, if the host sends the address that matches with either of + // the two addresses then the I2C target will respond to that byte. + i2c_set_target_id(i2c, device_addr0, device_mask0, device_addr1, device_mask1); + + // Write walking 1's pattern + for (uint8_t i = 0; i < byte_count; i++) { + data_bytes[i] = 1u << (i % 8); + i2c_write_tx_data(i2c, data_bytes[i]); + } + + // top_chip_i2c_device_tx_rx_vseq will wait before driving the transfer for this SW symbol. + // Otherwise, SW and Vseq will be out of sync i.e. Vseq drives a start condition when SW is in + // the middle of configuring the I2C target registers. + tx_fifo_wr_done = 0x1u; + + if (!read_transfer_done(i2c)) { + return false; + } + + // Clear the cmd_complete interrupt state + i2c_clear_cmd_complete(i2c); + + if (!write_transfer_done(i2c)) { + return false; + } + + if (!compare_read_write_bytes(i2c, byte_count, data_bytes)) { + return false; + } + + return true; +} + +bool test_main() +{ + i2c_t i2c = mocha_system_i2c(); + i2c_init(i2c, i2c_speed_mode_standard); + i2c_enable_target_mode(i2c); + return device_tx_rx_test(i2c); +} From eb7d381197db4ea42463ccd2d948a95c8f2cbb0c Mon Sep 17 00:00:00 2001 From: Kinza Qamar Date: Mon, 22 Jun 2026 16:53:24 +0100 Subject: [PATCH 3/3] [i2c, dv] Added I2C device TX_RX Vseq Signed-off-by: Kinza Qamar --- .../top_chip_dv_i2c_device_tx_rx_vseq.sv | 207 ++++++++++++++++++ .../dv/env/seq_lib/top_chip_dv_vseq_list.sv | 1 + hw/top_chip/dv/env/top_chip_dv_env.core | 1 + hw/top_chip/dv/env/top_chip_dv_env.sv | 3 + hw/top_chip/dv/env/top_chip_dv_env_pkg.sv | 1 + .../dv/env/top_chip_dv_virtual_sequencer.sv | 4 +- hw/top_chip/dv/top_chip_sim_cfg.hjson | 9 + 7 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 hw/top_chip/dv/env/seq_lib/top_chip_dv_i2c_device_tx_rx_vseq.sv diff --git a/hw/top_chip/dv/env/seq_lib/top_chip_dv_i2c_device_tx_rx_vseq.sv b/hw/top_chip/dv/env/seq_lib/top_chip_dv_i2c_device_tx_rx_vseq.sv new file mode 100644 index 000000000..7e41ce008 --- /dev/null +++ b/hw/top_chip/dv/env/seq_lib/top_chip_dv_i2c_device_tx_rx_vseq.sv @@ -0,0 +1,207 @@ +// 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 + bit [7:0] tx_fifo_wr_done[1]; + + local rand bit [7:0] device_addr0[1]; + local rand bit [7:0] device_addr1[1]; + local rand bit xfer_addr; + + extern function new(string name=""); + extern virtual task dut_init(string reset_kind = "HARD"); + + // 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, + i2c_target_base_seq host_seq); + + + + // 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 + +task top_chip_dv_i2c_device_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("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("hold_stop_time_ns", sw_hold_stop_time_ns); + sw_symbol_backdoor_read("rise_time_ns", sw_rise_time_ns); + sw_symbol_backdoor_read("fall_time_ns", sw_fall_time_ns); + + // 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); + + 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]); + stop_hold_cycles = round_up_divide({sw_hold_stop_time_ns[1], sw_hold_stop_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]); +endtask + +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); + 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"); + i2c_target_base_seq host_seq = i2c_target_base_seq::type_id::create("host_seq"); + + create_and_drive_i2c_xfer(xfer, READ, BusOpRead, host_seq); + + // De-allocate the xfer item in order to send a new transfer + xfer = null; + + // 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, host_seq); +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 + 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 diff --git a/hw/top_chip/dv/env/seq_lib/top_chip_dv_vseq_list.sv b/hw/top_chip/dv/env/seq_lib/top_chip_dv_vseq_list.sv index 14d38ea17..e569b18c0 100644 --- a/hw/top_chip/dv/env/seq_lib/top_chip_dv_vseq_list.sv +++ b/hw/top_chip/dv/env/seq_lib/top_chip_dv_vseq_list.sv @@ -7,3 +7,4 @@ `include "top_chip_dv_gpio_smoke_vseq.sv" `include "top_chip_dv_i2c_tx_rx_vseq.sv" `include "top_chip_dv_i2c_host_tx_rx_vseq.sv" +`include "top_chip_dv_i2c_device_tx_rx_vseq.sv" diff --git a/hw/top_chip/dv/env/top_chip_dv_env.core b/hw/top_chip/dv/env/top_chip_dv_env.core index 2ed5b43ac..65ef43b5e 100644 --- a/hw/top_chip/dv/env/top_chip_dv_env.core +++ b/hw/top_chip/dv/env/top_chip_dv_env.core @@ -27,6 +27,7 @@ filesets: - seq_lib/top_chip_dv_gpio_smoke_vseq.sv: {is_include_file: true} - seq_lib/top_chip_dv_i2c_tx_rx_vseq.sv: {is_include_file: true} - seq_lib/top_chip_dv_i2c_host_tx_rx_vseq.sv: {is_include_file: true} + - seq_lib/top_chip_dv_i2c_device_tx_rx_vseq.sv: {is_include_file: true} file_type: systemVerilogSource targets: diff --git a/hw/top_chip/dv/env/top_chip_dv_env.sv b/hw/top_chip/dv/env/top_chip_dv_env.sv index 8f9c3e950..4a02a527c 100644 --- a/hw/top_chip/dv/env/top_chip_dv_env.sv +++ b/hw/top_chip/dv/env/top_chip_dv_env.sv @@ -93,6 +93,9 @@ function void top_chip_dv_env::connect_phase(uvm_phase phase); // Connect monitor output to matching FIFO in the virtual sequencer. // Allows virtual sequences to check TX items. m_uart_agent.monitor.tx_analysis_port.connect(top_vsqr.uart_tx_fifo.analysis_export); + + // Grab the read transfer item + m_i2c_agent.monitor.controller_mode_rd_item_port.connect(top_vsqr.i2c_rd_xfer_fifo.analysis_export); endfunction : connect_phase task top_chip_dv_env::load_memories(); diff --git a/hw/top_chip/dv/env/top_chip_dv_env_pkg.sv b/hw/top_chip/dv/env/top_chip_dv_env_pkg.sv index ad8a50d44..d254cea30 100644 --- a/hw/top_chip/dv/env/top_chip_dv_env_pkg.sv +++ b/hw/top_chip/dv/env/top_chip_dv_env_pkg.sv @@ -11,6 +11,7 @@ package top_chip_dv_env_pkg; import uart_agent_pkg::*; import gpio_env_pkg::NUM_GPIOS; import i2c_reg_pkg::FifoDepth; + import i2c_pkg::*; // Macro includes `include "uvm_macros.svh" diff --git a/hw/top_chip/dv/env/top_chip_dv_virtual_sequencer.sv b/hw/top_chip/dv/env/top_chip_dv_virtual_sequencer.sv index d6c9a12dc..9bdb29ee5 100644 --- a/hw/top_chip/dv/env/top_chip_dv_virtual_sequencer.sv +++ b/hw/top_chip/dv/env/top_chip_dv_virtual_sequencer.sv @@ -19,6 +19,7 @@ class top_chip_dv_virtual_sequencer extends uvm_sequencer; // FIFOs for monitor output. Used by some virtual sequences to check // TX (from-chip) items. uvm_tlm_analysis_fifo #(uart_item) uart_tx_fifo; + uvm_tlm_analysis_fifo #(i2c_item) i2c_rd_xfer_fifo; // Standard SV/UVM methods extern function new(string name = "", uvm_component parent = null); @@ -33,5 +34,6 @@ endfunction : new function void top_chip_dv_virtual_sequencer::build_phase(uvm_phase phase); super.build_phase(phase); // Construct monitor output FIFOs - uart_tx_fifo = new("uart_tx_fifo", this); + uart_tx_fifo = new("uart_tx_fifo", this); + i2c_rd_xfer_fifo = new("i2c_rd_xfer_fifo", this); endfunction : build_phase diff --git a/hw/top_chip/dv/top_chip_sim_cfg.hjson b/hw/top_chip/dv/top_chip_sim_cfg.hjson index 32a639fa2..a77861bb6 100644 --- a/hw/top_chip/dv/top_chip_sim_cfg.hjson +++ b/hw/top_chip/dv/top_chip_sim_cfg.hjson @@ -166,6 +166,13 @@ run_opts: ["+ChipMemSRAM_image_file={run_dir}/i2c_device_tx_rx_test_vanilla_sram.vmem", "+ChipMemROM_image_file={run_dir}/bootrom_scrambled.vmem"] } + { + name: i2c_device_tx_rx_cheri + uvm_test_seq: top_chip_dv_i2c_device_tx_rx_vseq + sw_images: ["i2c_device_tx_rx_test_vanilla_sram:5" "bootrom:5"] + run_opts: ["+ChipMemSRAM_image_file={run_dir}/i2c_device_tx_rx_test_vanilla_sram.vmem", + "+ChipMemROM_image_file={run_dir}/bootrom_scrambled.vmem"] + } { name: gpio_smoke uvm_test_seq: top_chip_dv_gpio_smoke_vseq @@ -354,6 +361,8 @@ "spi_host_smoke_cheri", "i2c_host_tx_rx", "i2c_host_tx_rx_cheri", + "i2c_device_tx_rx", + "i2c_device_tx_rx_cheri", "gpio_smoke", "gpio_smoke_cheri", "mailbox_smoke",