blob: 26c8219cf1f6cc6961eacc7de0e9d08101c0c10b [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// This sequence tests read commands that pass through and write commands that
// are uploaded to software. Software is then expected to relay write commands
// back out through the spi_host IP to the device agent.
class chip_sw_spi_passthrough_collision_vseq extends chip_sw_spi_passthrough_vseq;
`uvm_object_utils(chip_sw_spi_passthrough_collision_vseq)
`uvm_object_new
// Generate a random permutation of the following opcodes, then insert
// a SpiFlashWriteEnable before each write command and a SpiFlashReadSts1
// after each write command:
// - SpiFlashReadNormal
// - SpiFlashReadFast
// - SpiFlashReadDual
// - SpiFlashReadQuad
// - SpiFlashChipErase
// - SpiFlashSectorErase
// - SpiFlashPageProgram
// - SpiFlashWriteSts1
virtual function void generate_spi_flash_sequence();
spi_flash_cmd_e opcodes[] = '{
SpiFlashReadNormal,
SpiFlashReadFast,
SpiFlashReadDual,
SpiFlashReadQuad,
SpiFlashChipErase,
SpiFlashSectorErase,
SpiFlashPageProgram,
SpiFlashWriteSts1
};
opcodes.shuffle();
foreach (opcodes[i]) begin
case (opcodes[i])
SpiFlashChipErase,
SpiFlashSectorErase,
SpiFlashPageProgram,
SpiFlashWriteSts1: begin
// Write commands use the following sequence:
// - WriteEnable
// - The write command
// - ReadSts1 (to check for busy, which is always 0 from the agent)
test_opcodes.push_back(SpiFlashWriteEnable);
test_opcodes.push_back(opcodes[i]);
test_opcodes.push_back(SpiFlashReadSts1);
end
default:
test_opcodes.push_back(opcodes[i]);
endcase
end
endfunction
// Send the sequence of commands indicated by the `test_opcodes` member.
// These commands must be registered with the host and device agents, as the
// command info determines the actions takes for each opcode. Random data of
// random size will be generated for each command that has a payload phase.
virtual task execute_spi_flash_sequence();
const int max_payload_size = 256;
spi_device_flash_seq m_spi_device_seq;
spi_host_flash_seq m_spi_host_seq;
spi_item host_rsp, device_rsp;
spi_item host_rsp_q[$], device_rsp_q[$];
spi_agent_cfg agent_cfg = cfg.m_spi_host_agent_cfg;
bit defer_response_compare;
spi_flash_cmd_e device_ops[$] = test_opcodes;
fork begin : isolation_fork
fork
// The device agent handles the incoming command with random data,
// except for SpiFlashReadSts1, which always returns 0. The software
// sends the exact same sequence of commands as the host agent, except
// for eliminating repeated SpiFlashReadSts1 commands.
begin
while (device_ops.size() > 0) begin
bit [7:0] device_op = device_ops.pop_front();
`uvm_create_on(m_spi_device_seq, p_sequencer.spi_device_sequencer_hs[0]);
if (device_op == SpiFlashReadSts1) begin
// For simplicity, report that the device agent is never busy.
m_spi_device_seq.byte_data_q = {8'h0};
end
`uvm_send(m_spi_device_seq);
`DV_CHECK_EQ(m_spi_device_seq.rsp.opcode, device_op);
device_rsp_q.push_back(m_spi_device_seq.rsp);
end
end
join_none
while (test_opcodes.size() > 0) begin
bit [7:0] opcode = test_opcodes.pop_front();
// Starting with the WriteEnable in a write command sequence, defer
// response comparison until the WIP and WEL status bits are cleared.
// Software will issue commands to the device agent later. The sequence
// from the DUT spi_host will be WriteEnable, <write command>, ReadSts1.
if (opcode == SpiFlashWriteEnable) begin
defer_response_compare = 1'b1;
end else if (opcode == SpiFlashReadSts1) begin
defer_response_compare = 1'b0;
end
`uvm_create_on(m_spi_host_seq, p_sequencer.spi_host_sequencer_h);
// Prepare for specific opcode. The address_q is kept empty, which will
// trigger m_spi_host_seq to do the lookup and supply a random value.
if (opcode == SpiFlashWriteSts1 || opcode == SpiFlashReadSts1) begin
`DV_CHECK_RANDOMIZE_WITH_FATAL(m_spi_host_seq,
opcode == local::opcode;
address_q.size() == 0;
payload_q.size() == 1;
read_size == payload_q.size(););
end else begin
`DV_CHECK_RANDOMIZE_WITH_FATAL(m_spi_host_seq,
opcode == local::opcode;
address_q.size() == 0;
payload_q.size() <= local::max_payload_size;
read_size == payload_q.size(););
end
`uvm_send(m_spi_host_seq);
if (opcode == SpiFlashReadSts1 && m_spi_host_seq.rsp.payload_q[0][0] != 0) begin
// Wait until software clears the WIP and WEL bits.
spi_host_wait_on_busy();
end
host_rsp_q.push_back(m_spi_host_seq.rsp);
// Check that uploaded commands modify CSRs as expected.
case (opcode)
SpiFlashWriteSts1,
SpiFlashChipErase,
SpiFlashSectorErase,
SpiFlashPageProgram: begin
// Ensure there is an interrupt.
csr_spinwait(
.ptr(ral.spi_device.intr_state.upload_cmdfifo_not_empty),
.exp_data(1'b1),
.backdoor(1),
.spinwait_delay_ns(20));
// Check the depths.
csr_rd_check(
.ptr(ral.spi_device.upload_status.cmdfifo_depth),
.backdoor(1),
.compare_value(1)
);
csr_rd_check(
.ptr(ral.spi_device.upload_status.addrfifo_depth),
.backdoor(1),
.compare_value((m_spi_host_seq.rsp.address_q.size() == 0) ? 0 : 1)
);
csr_rd_check(
.ptr(ral.spi_device.upload_status2.payload_depth),
.backdoor(1),
.compare_value(m_spi_host_seq.rsp.payload_q.size())
);
csr_rd_check(
.ptr(ral.spi_device.upload_status2.payload_start_idx),
.backdoor(1),
.compare_value(0)
);
`DV_CHECK_EQ(device_rsp_q.size(), 0);
end
default: begin
// Software is only involved for uploaded commands. For commands
// that aren't uploaded, wait for a small delay to allow the device
// agent to push the response into the queue.
#1ps;
end
endcase
if (!defer_response_compare) begin
// Check that the command, address, and data sent matches on both sides.
`DV_CHECK_EQ(host_rsp_q.size(), device_rsp_q.size());
while (host_rsp_q.size() > 0) begin
host_rsp = host_rsp_q.pop_front();
device_rsp = device_rsp_q.pop_front();
if (opcode == SpiFlashReadSts1) begin
// Nullify the comparison of status registers between host and
// device for reads, as the DUT will track changes, but the device
// agent and this sequence do not.
host_rsp.payload_q = {device_rsp.payload_q};
end
if (!host_rsp.compare(device_rsp)) begin
`uvm_error(`gfn, $sformatf("Compare mismatch\nhost_rsp:\n%sdevice_rsp:\n%s",
host_rsp.sprint(), device_rsp.sprint()))
end
end
end
end
disable fork;
// Ensure all responses have been checked.
`DV_CHECK_EQ(device_rsp_q.size(), 0);
`DV_CHECK_EQ(host_rsp_q.size(), 0);
end join
endtask
virtual task cpu_init();
// Set up passthrough filters and the upload configuration. Pass this
// information down to software.
bit [7:0] sw_filter_config[4];
bit [7:0] upload_config[1] = {8'h1};
super.cpu_init();
passthrough_filters[spi_device_pkg::CmdInfoReadStatus3:spi_device_pkg::CmdInfoReadStatus1] = '1;
passthrough_filters[spi_device_pkg::CmdInfoReadCmdEnd:spi_device_pkg::CmdInfoReadCmdStart] = '0;
passthrough_filters[spi_device_pkg::NumTotalCmdInfo-1:spi_device_pkg::CmdInfoReserveStart] = '1;
sw_filter_config = {<<byte{passthrough_filters}};
sw_symbol_backdoor_overwrite("kFilteredCommands", sw_filter_config);
sw_symbol_backdoor_overwrite("kUploadWriteCommands", upload_config);
endtask
endclass : chip_sw_spi_passthrough_collision_vseq