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
207 changes: 207 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,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

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.

Nit: most of your new methods are long enough to justify the fact to add the label, I feel it hard to read without it.


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;
Comment on lines +159 to +160

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 is dead code and can be dropped


// 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
89 changes: 70 additions & 19 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,26 +8,58 @@ 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="");

// 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);

// 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
Expand All @@ -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();

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?

// 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();
Expand Down
1 change: 1 addition & 0 deletions hw/top_chip/dv/env/seq_lib/top_chip_dv_vseq_list.sv
Original file line number Diff line number Diff line change
Expand Up @@ -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"
1 change: 1 addition & 0 deletions hw/top_chip/dv/env/top_chip_dv_env.core
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions hw/top_chip/dv/env/top_chip_dv_env.sv
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions hw/top_chip/dv/env/top_chip_dv_env_pkg.sv
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ 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;
import i2c_pkg::*;

// Macro includes
`include "uvm_macros.svh"
Expand Down
4 changes: 3 additions & 1 deletion hw/top_chip/dv/env/top_chip_dv_virtual_sequencer.sv
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
9 changes: 9 additions & 0 deletions hw/top_chip/dv/top_chip_sim_cfg.hjson
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down
Loading