blob: bbba0683872894c9993dd9eb4ed1d78c8c736983 [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 spi_host_driver extends spi_driver;
`uvm_component_utils(spi_host_driver)
`uvm_component_new
uint sck_pulses = 0;
virtual task run_phase(uvm_phase phase);
fork
super.run_phase(phase);
gen_sck();
join
endtask
// Resets signals
virtual task reset_signals();
forever begin
@(negedge cfg.vif.rst_n);
under_reset = 1'b1;
cfg.vif.sck <= cfg.sck_polarity[0];
cfg.vif.sio <= 'hz;
cfg.vif.csb <= 'hF;
sck_pulses = 0;
@(posedge cfg.vif.rst_n);
under_reset = 1'b0;
end
endtask
// generate sck
task gen_sck();
@(posedge cfg.vif.rst_n);
fork
forever begin
if (sck_pulses > 0 || cfg.sck_on) begin
cfg.vif.sck <= ~cfg.vif.sck;
#((cfg.sck_period_ps / 2 + get_rand_extra_delay_ns_btw_sck() * 1000) * 1ps);
cfg.vif.sck <= ~cfg.vif.sck;
#((cfg.sck_period_ps / 2 + get_rand_extra_delay_ns_btw_sck() * 1000) * 1ps);
if (sck_pulses > 0) sck_pulses--;
// dly after a word transfer is completed
if (sck_pulses % 32 == 0) #(get_rand_extra_delay_ns_btw_word() * 1ns);
end else begin
@(cfg.sck_on, sck_pulses);
if (sck_pulses > 0) begin
// drive half cycle first
cfg.vif.sck <= cfg.sck_polarity[0];
#(cfg.sck_period_ps / 2 * 1ps);
end
end
cfg.vif.sck_pulses = sck_pulses;
end
forever begin
@(cfg.sck_polarity[0]);
cfg.vif.sck_polarity = cfg.sck_polarity[0];
if (sck_pulses == 0) cfg.vif.sck <= cfg.sck_polarity[0];
end
forever begin
@(cfg.sck_phase[0]);
cfg.vif.sck_phase = cfg.sck_phase[0];
end
join
endtask : gen_sck
task get_and_drive();
forever begin
seq_item_port.get_next_item(req);
$cast(rsp, req.clone());
rsp.set_id_info(req);
`uvm_info(`gfn, $sformatf("spi_host_driver: rcvd item:\n%0s", req.sprint()), UVM_HIGH)
case (req.item_type)
SpiTransNormal: drive_normal_item();
SpiTransSckNoCsb: drive_sck_no_csb_item();
SpiTransCsbNoSck: drive_csb_no_sck_item();
SpiFlashTrans: drive_flash_item();
endcase
`uvm_info(`gfn, "spi_host_driver: item sent", UVM_HIGH)
seq_item_port.item_done(rsp);
end
endtask
task issue_data(bit [7:0] transfer_data[$]);
for (int i = 0; i < transfer_data.size(); i++) begin
bit [7:0] host_byte;
bit [7:0] device_byte;
int which_bit;
bit [3:0] num_bits;
if (cfg.partial_byte == 1) begin
num_bits = cfg.bits_to_transfer;
end else begin
num_bits = 8;
end
host_byte = transfer_data[i];
for (int j = 0; j < num_bits; j++) begin
// drive sio early so that it is stable at the sampling edge
which_bit = cfg.host_bit_dir ? j : 7 - j;
cfg.vif.sio[0] <= host_byte[which_bit];
// wait for sampling edge to sample sio (half cycle)
cfg.wait_sck_edge(SamplingEdge);
which_bit = cfg.device_bit_dir ? j : 7 - j;
device_byte[which_bit] = cfg.vif.sio[1];
// wait for driving edge to complete 1 cycle
if (i != transfer_data.size() - 1 || j != (num_bits - 1)) cfg.wait_sck_edge(DrivingEdge);
end
rsp.data[i] = device_byte;
end
endtask
task drive_normal_item();
cfg.vif.csb[cfg.csb_sel] <= 1'b0;
if ((req.data.size() == 1) && (cfg.partial_byte == 1)) begin
sck_pulses = cfg.bits_to_transfer;
end else begin
sck_pulses = req.data.size() * 8;
end
// for mode 1 and 3, get the leading edges out of the way
cfg.wait_sck_edge(LeadingEdge);
// drive data
issue_data(req.data);
wait(sck_pulses == 0);
if (cfg.csb_consecutive == 0) begin
cfg.vif.csb[cfg.csb_sel] <= 1'b1;
cfg.vif.sio[0] <= 1'bx;
end
endtask
task drive_flash_item();
bit [7:0] cmd_addr_bytes[$];
`uvm_info(`gfn, $sformatf("Driving flash item: \n%s", req.sprint()), UVM_MEDIUM)
cfg.vif.csb[cfg.csb_sel] <= 1'b0;
cmd_addr_bytes = {req.opcode, req.address_q};
sck_pulses = cmd_addr_bytes.size() * 8 + req.dummy_cycles;
if (req.write_command) begin
`DV_CHECK_EQ(req.num_lanes, 1)
sck_pulses += req.payload_q.size * 8;
end else begin
`DV_CHECK_EQ(req.payload_q.size, 0)
sck_pulses += req.read_size * (8 / req.num_lanes);
end
// for mode 1 and 3, get the leading edges out of the way
cfg.wait_sck_edge(LeadingEdge);
// driver cmd and address
issue_data(cmd_addr_bytes);
// align to DrivingEdge, if the item has more to send
if (req.dummy_cycles > 0 || req.payload_q.size > 0 ) cfg.wait_sck_edge(DrivingEdge);
repeat (req.dummy_cycles) begin
//cfg.vif.sio <= 'dz;
cfg.wait_sck_edge(DrivingEdge);
end
// drive data
issue_data(req.payload_q);
wait(sck_pulses == 0);
cfg.vif.csb[cfg.csb_sel] <= 1'b1;
cfg.vif.sio <= 'dx;
endtask
task drive_sck_no_csb_item();
repeat (req.dummy_clk_cnt) begin
#($urandom_range(1, 100) * 1ns);
cfg.vif.sck <= ~cfg.vif.sck;
end
cfg.vif.sck <= cfg.sck_polarity[0];
#1ps; // make sure sck and csb (for next item) not change at the same time
endtask
task drive_csb_no_sck_item();
cfg.vif.csb[cfg.csb_sel] <= 1'b0;
#(req.dummy_sck_length_ns * 1ns);
cfg.vif.csb[cfg.csb_sel] <= 1'b1;
endtask
function uint get_rand_extra_delay_ns_btw_sck();
if (cfg.en_extra_dly_btw_sck && ($urandom % 100) < cfg.extra_dly_chance_pc_btw_sck) begin
return $urandom_range(cfg.min_extra_dly_ns_btw_sck, cfg.max_extra_dly_ns_btw_sck);
end else begin
return 0;
end
endfunction
function uint get_rand_extra_delay_ns_btw_word();
if (cfg.en_extra_dly_btw_word && ($urandom % 100) < cfg.extra_dly_chance_pc_btw_word) begin
return $urandom_range(cfg.min_extra_dly_ns_btw_word, cfg.max_extra_dly_ns_btw_word);
end else begin
return 0;
end
endfunction
endclass