blob: 1d355e0183995eb7e568ae29dc3085e856ab09e5 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// The spi_device_driver responds to bus transactions initiated by a SPI host,
// and its responses depend on cfg.spi_func_mode.
//
// For SpiModeGeneric, the driver waits for CSB to be asserted, then drives
// the contents of spi_item.data sequentially, timed with the SPI host's clock
// output.
//
// For SpiModeFlash, the spi_item must be submitted at exactly the moment when
// the driver is to begin driving the response. The driver sends the contents
// of spi_item.payload_q sequentially, timed with the SPI host's clock output.
// Note that spi_device_driver does NOT observe the command, address, or dummy
// cycles, and it assumes those phases have already passed. For this mode,
// spi_device_driver is anticipated to be used in conjunction with the
// spi_monitor's req_analysis_port, which writes a spi_item into the
// spi_sequencer's connected req_analysis_fifo at the moment those phases have
// completed.
class spi_device_driver extends spi_driver;
`uvm_component_utils(spi_device_driver)
`uvm_component_new
bit [CSB_WIDTH-1:0] active_csb = 0;
virtual task reset_signals();
forever begin
@(negedge cfg.vif.rst_n);
`uvm_info(`gfn, "\n dev_drv: in reset progress", UVM_DEBUG)
under_reset = 1'b1;
cfg.vif.sio_out = 'z;
@(posedge cfg.vif.rst_n);
under_reset = 1'b0;
`uvm_info(`gfn, "\n dev_drv: out of reset", UVM_DEBUG)
end
endtask
virtual task get_and_drive();
spi_item req, rsp;
forever begin
seq_item_port.get_next_item(req);
`DV_CHECK_EQ(cfg.vif.disconnected, 0)
if (cfg.csb_sel_in_cfg) active_csb = cfg.csid;
else active_csb = req.csb_sel;
$cast(rsp, req.clone());
rsp.set_id_info(req);
// The response should read the payload actually consumed, not start with
// it filled out.
rsp.payload_q = '{};
wait (!under_reset && !cfg.vif.csb[active_csb]);
fork
begin: iso_fork
fork
case (cfg.spi_func_mode)
SpiModeGeneric: send_rx_item(req, rsp);
SpiModeFlash: send_flash_item(req, rsp);
SpiModeTpm: send_tpm_item(req, rsp);
default: begin
`uvm_fatal(`gfn, $sformatf("Invalid mode %s", cfg.spi_func_mode.name))
end
endcase
drive_bus_to_highz();
drive_bus_for_reset();
join_any
disable fork;
end: iso_fork
join
seq_item_port.item_done();
seq_item_port.put_response(rsp);
`uvm_info(`gfn, "\n dev_drv: item done", UVM_HIGH)
end
endtask : get_and_drive
virtual task send_rx_item(spi_item item, spi_item rsp);
logic [3:0] sio_bits;
bit bits_q[$];
int max_tx_bits;
if (cfg.byte_order) cfg.swap_byte_order(item.data);
bits_q = {>> 1 {item.data}};
max_tx_bits = cfg.get_sio_size();
`uvm_info(`gfn, $sformatf("\n dev_drv: send_rx_item, return %0d bits to dut",
bits_q.size()), UVM_DEBUG)
// pop enough bits do drive all needed sio
while (bits_q.size() > 0) begin
if (bits_q.size() > 0) cfg.wait_sck_edge(DrivingEdge, active_csb);
for (int i = 0; i < 4; i++) begin
sio_bits[i] = (i < max_tx_bits) ? bits_q.pop_front() : 1'bz;
end
send_data_to_sio(cfg.spi_mode, sio_bits);
`uvm_info(`gfn, $sformatf("\n dev_drv: assert data bit[%0d] %b",
bits_q.size(), sio_bits[0]), UVM_DEBUG)
end
// TODO, ideally we should capture the data device receives and put in rsp.
endtask : send_rx_item
virtual task send_flash_item(spi_item item, spi_item rsp);
logic [3:0] sio_bits;
bit bits_q[$];
logic [7:0] data[$] = {item.payload_q};
spi_mode_e spi_mode;
`uvm_info(`gfn, $sformatf("sending rx_item:\n%s", item.sprint()), UVM_MEDIUM)
fork
begin : thread_collect_payload
forever begin
logic [7:0] byte_data;
cfg.read_byte(item.num_lanes, !item.write_command, active_csb, byte_data);
rsp.payload_q.push_back(byte_data);
if (!item.write_command) rsp.read_size++;
end
end : thread_collect_payload
begin : thread_send_read_data
if (!item.write_command) begin
if (cfg.byte_order) cfg.swap_byte_order(data);
bits_q = {>> 1 {data}};
if (item.num_lanes == 1) spi_mode = Standard;
else if (item.num_lanes == 2) spi_mode = Dual;
else spi_mode = Quad;
forever begin
cfg.wait_sck_edge(DrivingEdge, active_csb);
// The first bit in bits_q is the MSB, which is driven on the sio
// lane with the highest active index.
for (int i = $high(sio_bits); i >= $low(sio_bits); i--) begin
if (i >= item.num_lanes) begin
sio_bits[i] = 1'bz;
end else begin
sio_bits[i] = bits_q.size > 0 ? bits_q.pop_front() : $urandom_range(0, 1);
end
end
send_data_to_sio(spi_mode, sio_bits);
end
end
end : thread_send_read_data
join
endtask : send_flash_item
virtual task send_tpm_item(spi_item item, spi_item rsp);
// TODO, this mode isn't used in OT project
`uvm_fatal(`gfn, "TPM device mode isn't supported")
endtask : send_tpm_item
virtual task send_data_to_sio(spi_mode_e mode, input logic [3:0] sio_bits);
case (mode)
Standard: cfg.vif.sio_out[1] <= sio_bits[0];
Dual: cfg.vif.sio_out[1:0] <= sio_bits[1:0];
default: cfg.vif.sio_out <= sio_bits;
endcase
endtask : send_data_to_sio
virtual task drive_bus_to_highz();
@(posedge cfg.vif.csb[active_csb]);
cfg.vif.sio_out = 'z;
`uvm_info(`gfn, "\n dev_drv: drive_bus_to_highz is done", UVM_DEBUG)
endtask : drive_bus_to_highz
virtual task drive_bus_for_reset();
@(negedge cfg.vif.rst_n);
cfg.vif.sio_out = 'z;
`uvm_info(`gfn, "\n dev_drv: drive_bus_for_reset is done", UVM_DEBUG)
endtask : drive_bus_for_reset
endclass : spi_device_driver