-
Notifications
You must be signed in to change notification settings - Fork 19
[top-i2c,dv] Added chip level I2C Device TX_RX test #633
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
KinzaQamar
wants to merge
3
commits into
lowRISC:main
Choose a base branch
from
KinzaQamar:i2c_device_smoke
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
207 changes: 207 additions & 0 deletions
207
hw/top_chip/dv/env/seq_lib/top_chip_dv_i2c_device_tx_rx_vseq.sv
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
|
|
||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
|
@@ -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(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think these changes also have an impact on |
||
| // 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(); | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.