blob: edf6995238edd10ecd88c3884ff83f0aa5a40f6b [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;
bit [CSB_WIDTH-1:0] active_csb;
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 or negedge cfg.vif.disconnected);
if (cfg.vif.disconnected) continue;
under_reset = 1'b1;
active_csb = 1'b0;
cfg.vif.sck <= cfg.sck_polarity[0];
cfg.vif.sio_out <= 'x;
cfg.vif.csb <= '1;
sck_pulses = 0;
@(posedge cfg.vif.rst_n);
under_reset = 1'b0;
end
endtask
// generate sck
task gen_sck();
@(posedge cfg.vif.rst_n);
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[active_csb];
#(cfg.sck_period_ps / 2 * 1ps);
end
end
cfg.vif.sck_pulses = sck_pulses;
end
endtask : gen_sck
task switch_polarity_and_phase();
if (cfg.vif.sck != cfg.sck_polarity[active_csb]) begin
cfg.vif.sck <= cfg.sck_polarity[active_csb];
#TIME_SCK_STABLE_TO_CSB_NS;
end
endtask : switch_polarity_and_phase
task get_and_drive();
forever begin
seq_item_port.get_next_item(req);
`DV_CHECK_EQ(cfg.vif.disconnected, 0)
$cast(rsp, req.clone());
rsp.set_id_info(req);
`uvm_info(`gfn, $sformatf("spi_host_driver: rcvd item:\n%0s", req.sprint()), UVM_HIGH)
if (cfg.csb_sel_in_cfg) active_csb = cfg.csid;
else active_csb = req.csb_sel;
switch_polarity_and_phase();
// switch from x to z to release the IO
cfg.vif.sio_out <= 'z;
case (req.item_type)
SpiTransNormal: begin
case (cfg.spi_func_mode)
SpiModeGeneric: drive_normal_item();
SpiModeFlash: drive_flash_item();
SpiModeTpm: drive_tpm_item();
default: begin
`uvm_fatal(`gfn, $sformatf("Invalid mode %s", cfg.spi_func_mode.name))
end
endcase
end
SpiTransSckNoCsb: drive_sck_no_csb_item();
SpiTransCsbNoSck: drive_csb_no_sck_item();
SpiTransIncompleteOpcode: begin
// TODO(#15721), need to have at least 3 spi_clk when CSB is active, otherwise,
// this incomplete req may have bad side effect due to `sys_csb_deasserted_pulse_i`
// isn't set correctly
int num_bits = $urandom_range(3, 7);
// invoke `drive_normal_item` to send less than 8 bits data as incompleted opcode
drive_normal_item(.partial_byte(1), .num_bits(num_bits));
end
default: `uvm_fatal(`gfn, $sformatf("Invalid type %s", req.item_type.name))
endcase
cfg.vif.csb[active_csb] <= 1'b1;
cfg.vif.sio_out <= 'x;
#($urandom_range(cfg.max_idle_ns_after_csb_drop, cfg.min_idle_ns_after_csb_drop) * 1ns);
`uvm_info(`gfn, "spi_host_driver: item sent", UVM_HIGH)
seq_item_port.item_done(rsp);
end
endtask
task issue_data(input bit [7:0] transfer_data[$], output bit [7:0] returned_data[$],
input bit last_data = 1, input int num_bits = 8);
for (int i = 0; i < transfer_data.size(); i++) begin
bit [7:0] host_byte;
bit [7:0] device_byte;
int which_bit;
if (transfer_data.size == 0) return;
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_out[0] <= host_byte[which_bit];
// wait for sampling edge to sample sio (half cycle)
cfg.wait_sck_edge(SamplingEdge, active_csb);
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 (!last_data || i != transfer_data.size() - 1 || j != (num_bits - 1)) begin
cfg.wait_sck_edge(DrivingEdge, active_csb);
end
end
returned_data[i] = device_byte;
end
endtask
task drive_normal_item(bit partial_byte = cfg.partial_byte,
int num_bits = cfg.partial_byte ? cfg.bits_to_transfer : 8);
cfg.vif.csb[active_csb] <= 1'b0;
if ((req.data.size() == 1) && (partial_byte == 1)) begin
sck_pulses = num_bits;
num_bits = num_bits;
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, active_csb);
// drive data
issue_data(req.data, rsp.data, , num_bits);
wait(sck_pulses == 0);
endtask
task drive_flash_item();
bit [7:0] cmd_addr_bytes[$];
bit [7:0] dummy_return_q[$]; // nothing to return for flash cmd, addr and write
`uvm_info(`gfn, $sformatf("Driving flash item: \n%s", req.sprint()), UVM_MEDIUM)
cfg.vif.csb[active_csb] <= 1'b0;
cmd_addr_bytes = {req.opcode, req.address_q};
sck_pulses = cmd_addr_bytes.size() * 8 + req.dummy_cycles;
if (req.num_lanes > 0) begin
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
end
// for mode 1 and 3, get the leading edges out of the way
cfg.wait_sck_edge(LeadingEdge, active_csb);
// driver cmd and address
issue_data(cmd_addr_bytes, dummy_return_q);
// align to DrivingEdge, if the item has more to send
if (req.dummy_cycles || req.payload_q.size || req.read_size) begin
cfg.wait_sck_edge(DrivingEdge, active_csb);
cfg.vif.sio_out <= 'dz;
if (req.dummy_cycles > 0) begin
repeat (req.dummy_cycles) begin
cfg.wait_sck_edge(SamplingEdge, active_csb);
end
if (req.payload_q.size || req.read_size) cfg.wait_sck_edge(DrivingEdge, active_csb);
end
// drive data
if (req.write_command) begin
issue_data(req.payload_q, dummy_return_q);
end else begin
repeat (req.read_size) begin
logic [7:0] data;
cfg.read_byte(.num_lanes(req.num_lanes), .is_device_rsp(1),
.csb_id(active_csb), .data(data));
rsp.payload_q.push_back(data);
end
`uvm_info(`gfn, $sformatf("collect read data for flash: 0x%p", rsp.payload_q), UVM_MEDIUM)
end
end
wait(sck_pulses == 0);
endtask
task drive_tpm_item();
bit [7:0] cmd_bytes[$];
bit [7:0] returned_bytes[$];
byte data_num_byte;
bit [7:0] tpm_rsp;
`uvm_info(`gfn, $sformatf("Driving TPM item: \n%s", req.sprint()), UVM_MEDIUM)
`DV_CHECK_EQ_FATAL(req.address_q.size(), TPM_ADDR_WIDTH_BYTE)
// When issuing write command, data size cannot be 0.
`DV_CHECK(req.data.size() > 0 | !req.write_command)
data_num_byte = req.write_command ? req.data.size() : req.read_size;
cmd_bytes = {get_tpm_cmd(req.write_command, data_num_byte), req.address_q};
cfg.vif.csb[active_csb] <= 1'b0;
sck_pulses = cmd_bytes.size() * 8;
issue_data(.transfer_data(cmd_bytes), .returned_data(returned_bytes), .last_data(0));
// polling TPM_START
`DV_SPINWAIT(
do begin
bit [7:0] dummy_bytes[$];
dummy_bytes = {$urandom};
sck_pulses += 8;
issue_data(.transfer_data(dummy_bytes), .returned_data(returned_bytes), .last_data(0));
tpm_rsp = returned_bytes[0];
end while (tpm_rsp[0] == TPM_WAIT);
, , TPM_START_MAX_WAIT_TIME_NS)
`uvm_info(`gfn, "Received TPM START", UVM_HIGH)
sck_pulses += (req.write_command ? req.data.size : req.read_size) * 8;
if (req.write_command) begin
issue_data(.transfer_data(req.data), .returned_data(returned_bytes), .last_data(1));
// no data will return for write
foreach (returned_bytes[i]) `DV_CHECK_EQ(returned_bytes[i], 0)
end else begin // TPM read
bit [7:0] dummy_bytes[$];
repeat (req.read_size) dummy_bytes.push_back($urandom);
issue_data(.transfer_data(dummy_bytes), .returned_data(rsp.data), .last_data(1));
`uvm_info(`gfn, $sformatf("collect read data for TPM: 0x%p", rsp.data), UVM_HIGH)
end
wait(sck_pulses == 0);
endtask
task drive_sck_no_csb_item();
repeat (req.dummy_sck_cnt) begin
#($urandom_range(1, 100) * 1ns);
cfg.vif.sck <= ~cfg.vif.sck;
end
endtask
task drive_csb_no_sck_item();
cfg.vif.csb[active_csb] <= 1'b0;
#(req.dummy_csb_length_ns * 1ns);
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