blob: 8fcd506077cf2614f8c1ad199f683f07e2f1d938 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class i2c_driver extends dv_base_driver #(i2c_item, i2c_agent_cfg);
`uvm_component_utils(i2c_driver)
`uvm_component_new
rand bit [7:0] rd_data[256]; // max length of read transaction
byte wr_data;
int scl_spinwait_timeout_ns = 1_000_000; // 1ms
bit scl_pause = 0;
// get an array with unique read data
constraint rd_data_c { unique { rd_data }; }
virtual task reset_signals();
forever begin
@(negedge cfg.vif.rst_ni);
`uvm_info(`gfn, "\ndriver in reset progress", UVM_DEBUG)
release_bus();
@(posedge cfg.vif.rst_ni);
`uvm_info(`gfn, "\ndriver out of reset", UVM_DEBUG)
end
endtask : reset_signals
virtual task run_phase(uvm_phase phase);
fork
reset_signals();
get_and_drive();
begin
if (cfg.if_mode == Host) drive_scl();
end
begin
if (cfg.if_mode == Host) host_scl_pause_ctrl();
end
proc_hot_glitch();
join_none
endtask
virtual task get_and_drive();
i2c_item req;
@(posedge cfg.vif.rst_ni);
forever begin
if (cfg.if_mode == Device) release_bus();
// driver drives bus per mode
seq_item_port.get_next_item(req);
fork
begin: iso_fork
fork
begin
if (cfg.if_mode == Device) drive_device_item(req);
else drive_host_item(req);
end
// handle on-the-fly reset
begin
process_reset();
req.clear_all();
end
begin
// Agent hot reset. It only resets I2C agent.
// The DUT funtions normally without reset.
// This event only happens in directed test case so cannot set the timeout.
// It will be killed by disable fork when 'drive_*_item' is finished.
wait(cfg.agent_rst);
`uvm_info(`gfn, "drvdbg agent reset", UVM_MEDIUM)
req.clear_all();
end
join_any
disable fork;
end: iso_fork
join
seq_item_port.item_done();
// When agent reset happens, flush all sequence items from sequencer request queue,
// before it starts a new sequence.
if (cfg.agent_rst) begin
i2c_item dummy;
do begin
seq_item_port.try_next_item(dummy);
if (dummy != null) seq_item_port.item_done();
end while (dummy != null);
repeat(2) @(cfg.vif.cb);
cfg.agent_rst = 0;
end
end
endtask : get_and_drive
virtual task drive_host_item(i2c_item req);
// During pause period, let drive_scl control scl
`DV_WAIT(scl_pause == 1'b0,, scl_spinwait_timeout_ns, "drive_host_item")
`uvm_info(`gfn, $sformatf("drv: %s", req.drv_type.name), UVM_MEDIUM)
if (cfg.allow_bad_addr & !cfg.valid_addr) begin
if (req.drv_type inside {HostAck, HostNAck} & cfg.is_read) return;
end
case (req.drv_type)
HostStart: begin
cfg.vif.drv_phase = DrvAddr;
cfg.vif.host_start(cfg.timing_cfg);
cfg.host_scl_start = 1;
end
HostRStart: begin
cfg.vif.host_rstart(cfg.timing_cfg);
end
HostData: begin
`uvm_info(`gfn, $sformatf("Driving host item 0x%x", req.wdata), UVM_MEDIUM)
for (int i = $bits(req.wdata) -1; i >= 0; i--) begin
cfg.vif.host_data(cfg.timing_cfg, req.wdata[i]);
end
// Wait one more cycle for ack
cfg.vif.wait_scl(.iter(1), .tc(cfg.timing_cfg));
end
HostAck: begin
// Wait for read data and send ack
cfg.vif.wait_scl(.iter(8), .tc(cfg.timing_cfg));
cfg.vif.host_data(cfg.timing_cfg, 0);
end
HostNAck: begin
// Wait for read data and send nack
cfg.vif.wait_scl(.iter(8), .tc(cfg.timing_cfg));
cfg.vif.host_data(cfg.timing_cfg, 1);
end
HostStop: begin
cfg.vif.drv_phase = DrvStop;
cfg.host_scl_stop = 1;
cfg.vif.host_stop(cfg.timing_cfg);
if (cfg.allow_bad_addr & !cfg.valid_addr)cfg.got_stop = 1;
end
default: begin
`uvm_fatal(`gfn, $sformatf("\n host_driver, received invalid request"))
end
endcase
endtask : drive_host_item
virtual task drive_device_item(i2c_item req);
bit [7:0] rd_data_cnt = 8'd0;
bit [7:0] rdata;
case (req.drv_type)
DevAck: begin
cfg.timing_cfg.tStretchHostClock = gen_num_stretch_host_clks(cfg.timing_cfg);
`uvm_info(`gfn, $sformatf("sending an ack"), UVM_MEDIUM)
fork
// host clock stretching allows a high-speed host to communicate
// with a low-speed device by setting TIMEOUT_CTRL.EN bit
// the device asks host stretching its scl_i by pulling down scl_o
// the host clock pulse is extended until device scl_o is pulled up
// once scl_o is pulled down longer than TIMEOUT_CTRL.VAL field,
// intr_stretch_timeout_o is asserted (ref. https://www.i2c-bus.org/clock-stretching)
cfg.vif.device_stretch_host_clk(cfg.timing_cfg);
cfg.vif.device_send_ack(cfg.timing_cfg);
join
end
RdData: begin
`uvm_info(`gfn, $sformatf("Send readback data %0x", req.rdata), UVM_MEDIUM)
for (int i = 7; i >= 0; i--) begin
cfg.vif.device_send_bit(cfg.timing_cfg, req.rdata[i]);
end
`uvm_info(`gfn, $sformatf("\n device_driver, trans %0d, byte %0d %0x",
req.tran_id, req.num_data+1, rd_data[rd_data_cnt]), UVM_DEBUG)
// rd_data_cnt is rollled back (no overflow) after reading 256 bytes
rd_data_cnt++;
end
WrData: begin
// nothing to do
end
default: begin
`uvm_fatal(`gfn, $sformatf("\n device_driver, received invalid request"))
end
endcase
endtask : drive_device_item
function int gen_num_stretch_host_clks(ref timing_cfg_t tc);
// By randomly pulling down scl_o "offset" within [0:2*tc.tTimeOut],
// intr_stretch_timeout_o interrupt would be generated uniformly
// To test this feature more regressive, there might need a dedicated vseq (V2)
// in which TIMEOUT_CTRL.EN is always set.
// If Stretch value is greater than 2*tTimeOut, it will create 2 interrupt events.
// Which can cause faluse error in 'host_stretch_testmode'.
// So, this value should be associated with tTimeout in host stretch testmode
if (cfg.host_stretch_test_mode) return (tc.tTimeOut + 1);
else return $urandom_range(tc.tClockPulse, tc.tClockPulse + 2*tc.tTimeOut);
endfunction : gen_num_stretch_host_clks
virtual task process_reset();
@(negedge cfg.vif.rst_ni);
release_bus();
`uvm_info(`gfn, "\n driver is reset", UVM_DEBUG)
endtask : process_reset
virtual task release_bus();
`uvm_info(`gfn, "Driver released the bus", UVM_HIGH)
cfg.vif.scl_o = 1'b1;
cfg.vif.sda_o = 1'b1;
endtask : release_bus
task drive_scl();
// This timeout is extremely long since read trasnactions will stretch
// whenever there are unhanded write commands or format bytes.
int scl_spinwait_timeout_ns = 100_000_000; // 100ms
forever begin
@(cfg.vif.cb);
wait(cfg.host_scl_start);
fork begin
fork
// Original scl driver thread
while(!cfg.host_scl_stop) begin
cfg.vif.scl_o <= 1'b0;
cfg.vif.wait_for_dly(cfg.timing_cfg.tClockLow);
cfg.vif.wait_for_dly(cfg.timing_cfg.tSetupBit);
cfg.vif.scl_o <= 1'b1;
`DV_WAIT(cfg.vif.scl_i === 1'b1,, scl_spinwait_timeout_ns, "i2c_drv_scl")
cfg.vif.wait_for_dly(cfg.timing_cfg.tClockPulse);
// There is a corner case s.t.
// pause req -> drv got stop back to back.
// if that happens, skip to the next txn cycle to pause
// to avoid unsuccessful host timeout
if (cfg.host_scl_pause_ack & !cfg.host_scl_stop) begin
scl_pause = 1;
cfg.vif.wait_for_dly(cfg.host_scl_pause_cyc);
scl_pause = 0;
cfg.host_scl_pause_ack = 0;
end
if (!cfg.host_scl_stop) cfg.vif.scl_o = 1'b0;
cfg.vif.wait_for_dly(cfg.timing_cfg.tHoldBit);
end
// Force quit thread
begin
wait(cfg.host_scl_force_high | cfg.host_scl_force_low);
cfg.host_scl_stop = 1;
if (cfg.host_scl_force_high) begin
cfg.vif.scl_o <= 1'b1;
cfg.vif.sda_o <= 1'b1;
end else begin
cfg.vif.scl_o <= 1'b0;
cfg.vif.sda_o <= 1'b0;
end
end
join_any
disable fork;
end join
cfg.host_scl_start = 0;
cfg.host_scl_stop = 0;
end
endtask
task host_scl_pause_ctrl();
forever begin
@(cfg.vif.cb);
if (cfg.host_scl_pause_req & cfg.host_scl_start & !cfg.host_scl_stop) begin
cfg.host_scl_pause_ack = 1;
`DV_WAIT(cfg.host_scl_pause_ack == 0,,
scl_spinwait_timeout_ns, "host_scl_pause_ctrl")
cfg.host_scl_pause_req = 0;
end
end
endtask
// When 'cfg.hot_glitch' is triggered, it wait for data read state
// then add 'start' or 'stop' during data read state.
// Agent reset (without dut reset) is asserted after this event to
// clear driver and monitor state.
task proc_hot_glitch();
forever begin
@(cfg.vif.cb);
if (cfg.hot_glitch) begin
wait_for_read_data_state();
randcase
1: add_start();
1: add_stop();
endcase
cfg.agent_rst = 1;
cfg.hot_glitch = 0;
cfg.host_scl_force_high = 0;
cfg.host_scl_force_low = 0;
wait(!cfg.agent_rst);
end
end
endtask
// Task looking for data read state.
task wait_for_read_data_state();
int wait_timeout_ns = 500_000_000; // 500 ms
`DV_WAIT(cfg.vif.drv_phase == DrvRd,, wait_timeout_ns, "wait_for_read_data_state");
repeat(2) @(posedge cfg.vif.scl_i);
endtask
// Force quit scl stuck at high.
// 'start' will be added by the next sequence.
task add_start();
`uvm_info(`gfn, "proc_hot_glitch: add start", UVM_MEDIUM)
cfg.host_scl_force_high = 1;
cfg.vif.wait_for_dly(cfg.timing_cfg.tSetupStart);
endtask // add_start
// Force quit scl stuck at low.
// 'stop' is manually added here.
task add_stop();
`uvm_info(`gfn, "proc_hot_glitch: add stop", UVM_MEDIUM)
cfg.host_scl_force_low = 1;
cfg.host_scl_stop = 1;
cfg.vif.host_stop(cfg.timing_cfg);
endtask
endclass : i2c_driver