test(spi_device): Skeleton of PageProgram test in Passthrough
This commit adds `test_program` to the passthrough pre_dv.
It currently has skeleton only. Does not have Host and SW sequence yet.
But it implements the SPIFlash program behavior and adds random delay to
imitate SPI Flash's Program Latency (not to the Byte Program level).
Signed-off-by: Eunchan Kim <eunchan@opentitan.org>
diff --git a/hw/ip/spi_device/pre_dv/program/prog_passthrough_host.sv b/hw/ip/spi_device/pre_dv/program/prog_passthrough_host.sv
index 196d5b5..70ae94b 100644
--- a/hw/ip/spi_device/pre_dv/program/prog_passthrough_host.sv
+++ b/hw/ip/spi_device/pre_dv/program/prog_passthrough_host.sv
@@ -83,6 +83,10 @@
test_wel(subtask_pass);
end
+ "program": begin
+ test_program(subtask_pass);
+ end
+
default: begin
$display("Passthrough test requires '+TESTNAME=%%s'");
end
@@ -330,6 +334,15 @@
endtask : test_wel
+ static task test_program(output bit pass);
+
+ // Test sequence:
+ // Issue commands w/ random WEL
+ // It is expected for SW to catch program and upload to Mailbox buffer.
+ // Read from SPIFlash and from Mailbox buffer then compare
+
+ endtask : test_program
+
// Access to Mirrored storage
function automatic void write_byte(int unsigned addr, spi_data_t data);
mirrored_storage[addr] = data;
diff --git a/hw/ip/spi_device/pre_dv/program/prog_passthrough_sw.sv b/hw/ip/spi_device/pre_dv/program/prog_passthrough_sw.sv
index f18256c..6c4b787 100644
--- a/hw/ip/spi_device/pre_dv/program/prog_passthrough_sw.sv
+++ b/hw/ip/spi_device/pre_dv/program/prog_passthrough_sw.sv
@@ -16,6 +16,8 @@
);
initial begin
+ automatic string testname;
+
h2d = tlul_pkg::TL_H2D_DEFAULT;
// Wait reset relase
@@ -25,6 +27,25 @@
$display("Intializing SPI_DEVICE: PassThrough mode");
init_spidevice_passthrough();
+ $value$plusargs("TESTNAME=%s", testname);
+
+ case (testname)
+ "readbasic": begin
+ end
+
+ "addr_4b": begin
+ end
+
+ "wel": begin
+ end
+
+ "program": begin
+ // Need to disable Status return from SPI_DEVICE and issue directly to
+ // SPIFlash
+ test_program();
+ end
+ endcase
+
forever begin
@(posedge clk);
end
@@ -186,4 +207,24 @@
end
endfunction : init_dpsram
+ static task test_program();
+ // Turning off INTERCEPT
+ tlul_write(
+ clk, h2d, d2h,
+ 32'(spi_device_reg_pkg::SPI_DEVICE_INTERCEPT_EN_OFFSET),
+ 32'h 0000_0000, // MBX, STATUS
+ 4'b 0001
+ );
+
+ // FILTER
+ // Turn off Read Status / Page Program commands filter
+ tlul_write(
+ clk, h2d, d2h,
+ 32'(spi_device_reg_pkg::SPI_DEVICE_CMD_FILTER_0_OFFSET),
+ 32'h 0000_0000, // [5] := 0 / [2] := 0
+ 4'b 1111
+ );
+
+ endtask : test_program
+
endprogram : prog_passthrough_sw
diff --git a/hw/ip/spi_device/pre_dv/program/spiflash.sv b/hw/ip/spi_device/pre_dv/program/spiflash.sv
index 2d79123..40fa2c8 100644
--- a/hw/ip/spi_device/pre_dv/program/spiflash.sv
+++ b/hw/ip/spi_device/pre_dv/program/spiflash.sv
@@ -6,6 +6,9 @@
program spiflash #(
parameter int unsigned FlashSize = 1024*1024, // 1MB
+ parameter int unsigned PageSize = 256, // Bytes
+
+ parameter bit EnFastSim = 1'b 1, // Reduce the latencies
// Timing information s_ ms_ us
// Program Latency
@@ -44,6 +47,31 @@
// TODO: Add WP
);
+ // Local parameter : affected by EnFastSim
+
+ // Timing information s_ ms_ us
+ localparam int unsigned tPPDiv = (EnFastSim) ? 1_000 : 1 ;
+ localparam int unsigned tSEDiv = (EnFastSim) ? 5_000 : 1 ;
+ localparam int unsigned tBE32Div = (EnFastSim) ? 10_000 : 1 ;
+ localparam int unsigned tBE64Div = (EnFastSim) ? 100_000 : 1 ;
+ localparam int unsigned tCEDiv = (EnFastSim) ? 10_000_000 : 1 ;
+ // Program Latency
+ localparam int unsigned LocalPPTyp = tPPTyp / tPPDiv; // us
+ localparam int unsigned LocalPPMax = tPPMax / tPPDiv; // us
+ // Sector Erase(4kB) Latency
+ localparam int unsigned LocalSETyp = tSETyp / tSEDiv; // us
+ localparam int unsigned LocalSEMax = tSEMax / tSEDiv; // us
+ // Block Erase 32kB Latency
+ localparam int unsigned LocalBE32Typ = tBE32Typ / tBE32Div; // us
+ localparam int unsigned LocalBE32Max = tBE32Max / tBE32Div; // us
+ // Block Erase 64kB Latency
+ localparam int unsigned LocalBE64Typ = tBE64Typ / tBE64Div; // us
+ localparam int unsigned LocalBE64Max = tBE64Max / tBE64Div; // us
+ // Chip Erase Latency
+ localparam int unsigned LocalCETyp = tCETyp / tCEDiv; // us
+ localparam int unsigned LocalCEMax = tCEMax / tCEDiv; // us
+
+
typedef enum int unsigned {
Addr3B,
Addr4B,
@@ -90,19 +118,31 @@
spiflash_byte_t [2:0] status; // 24 bit
spiflash_byte_t storage[spiflash_addr_t]; // Associative Array to store data
+ spiflash_byte_t page_buffer[PageSize]; // page buffer to be used
+ int unsigned page_buffer_size;
+
spiflash_byte_t sfdp[256]; // SFDP storage 256Byte
+ mailbox backend_process; // Start delayed process (after CSb release)
+ mailbox backend_data; // Deliver the address to the backend task
+
initial begin
print_banner();
// Init values
status = '0;
+ backend_process = new(1); // Only one process at a time but multiple data
+ backend_data = new(1);
+
// SFDP initialization
// FIXME: Appropriate SFDP table rather than random data
foreach(sfdp[i]) sfdp[i] = $urandom_range(255, 0);
// Should wait ??
- main();
+ fork
+ main();
+ backend();
+ join
end
function automatic void print_banner();
@@ -155,6 +195,8 @@
automatic spiflash_byte_t opcode;
automatic bit early_termination;
automatic int unsigned excessive_sck = 0;
+ // Indication of process needed after CSb release
+ automatic bit process_after_csb = 1'b 0;
// Big loop. Wait transaction
sd_en = 4'h 0; // Off the output by default
wait(csb == 1'b 0);
@@ -169,7 +211,7 @@
begin
// Main
cmdparse(opcode); // Store incoming into opcod
- process_cmd(opcode);
+ process_cmd(opcode, process_after_csb);
// Count remaining edges of SCK. opcode completed command should
// have no additional data, or should be discarded
@@ -189,14 +231,35 @@
// CSb. In this case, early_termination is set too. If the command is
// output command, do not process early_termination.
- // Process program, etc
- // If excessive sck, then discard.
-
// Kill the forked process
disable fork;
+
+ // Process program, etc
+ // If excessive sck, then discard.
+ if (process_after_csb) begin
+ if (backend_process.num() != 0) begin
+ $display("T+%0d] SPIFlash: Can't handle two backend commands", $time);
+ $display(" Discarding the latter %2Xh", opcode);
+ continue;
+ end
+ backend_process.put(opcode);
+ end
end
endtask : main
+ static task backend();
+ forever begin
+ automatic spiflash_byte_t opcode;
+ backend_process.get(opcode);
+
+ case (opcode)
+ spi_device_pkg::CmdPageProgram: begin
+ backend_page_program();
+ end
+ endcase
+ end
+ endtask : backend
+
task automatic cmdparse(
output spiflash_byte_t opcode
);
@@ -207,9 +270,11 @@
endtask : cmdparse
task automatic process_cmd(
- input spiflash_byte_t opcode
+ input spiflash_byte_t opcode,
+ output bit en_backend
);
// Main case block to call subtasks depending on the opcode
+ en_backend = 1'b 0;
case (opcode)
spi_device_pkg::CmdReadStatus1: begin
// Return status data
@@ -229,13 +294,19 @@
read(opcode);
end
- // TODO: EN4B/ EX4B
spi_device_pkg::CmdEn4B, spi_device_pkg::CmdEx4B: begin
addr_4b(opcode);
end
- // TODO: WREN/ WRDI
- // TODO: PageProgram
+ spi_device_pkg::CmdWriteEnable, spi_device_pkg::CmdWriteDisable: begin
+ wel(opcode);
+ end
+
+ spi_device_pkg::CmdPageProgram: begin
+ page_program(opcode);
+ en_backend = 1'b 1;
+ end
+
// TODO: SectorErase
// TODO: BlockErase32
// TODO: BlockErase64
@@ -246,6 +317,11 @@
endtask : process_cmd
+ task automatic process_cmd_after_csb(
+
+ );
+ endtask : process_cmd_after_csb
+
task automatic get_byte(
input io_mode_e io_mode,
output spiflash_byte_t data
@@ -351,8 +427,12 @@
task automatic return_status(int unsigned idx);
// Starting from the index and wraps %3 until host releases CSb
automatic int unsigned s_idx = idx;
+
+ // Copy the status into internal variable to not be corrupted by on-going
+ // backend tasks.
+ automatic spiflash_byte_t [2:0] committed_status = status;
forever begin
- return_byte(status[s_idx], IoSingle);
+ return_byte(committed_status[s_idx], IoSingle);
s_idx = (s_idx + 1)%3;
end
endtask : return_status
@@ -440,4 +520,98 @@
endcase
endtask : addr_4b
+ task automatic wel(spiflash_byte_t opcode);
+ // TODO: Set only when CSb is deasserted right after 8th beat
+ case (opcode)
+ spi_device_pkg::CmdWriteEnable: status[0][StatusWEL] = 1'b 1;
+ spi_device_pkg::CmdWriteDisable: status[0][StatusWEL] = 1'b 0;
+ default: $display("WEL: Unrecognized Cmd (%2Xh)", opcode);
+ endcase
+ endtask : wel
+
+ task automatic page_program(spiflash_byte_t opcode);
+ // Return Read data
+ automatic logic [31:0] address;
+ automatic logic [7:0] buffer_idx;
+
+ // Set BUSY
+ status[0][StatusBUSY] = 1'b 1;
+
+ // get address field
+ get_address(IoSingle, AddrCfg, address);
+ $display("T:%0d] SPIFlash: Program Address %8Xh", $time, address);
+ if (address[31:StorageAw] != '0) begin
+ $display("Out-of-Bound Address received");
+ $display("Discarding the contents");
+ return;
+ end
+
+ backend_data.put({opcode, address});
+
+ // Starting with buffer offset and wraps 256B always
+ buffer_idx = address[7:0];
+ page_buffer_size = 0;
+
+ // Stack bytes
+ forever begin
+ automatic spiflash_byte_t data;
+ get_byte(IoSingle, data);
+ page_buffer[buffer_idx % PageSize] = data;
+
+ buffer_idx = (buffer_idx + 1 ) % PageSize;
+
+ if (page_buffer_size != PageSize) page_buffer_size++;
+ end
+
+ endtask : page_program
+
+ task automatic backend_page_program();
+ automatic spiflash_addr_t spi_addr;
+ automatic spiflash_byte_t opcode;
+ automatic logic [39:0] mbx_data;
+ automatic logic [31:0] host_address;
+ automatic logic [7:0] buffer_offset;
+
+ if (backend_data.num() == 0) begin
+ // The transaction got cancelled before receiving the address.
+ // Skip the backend task
+ status[0][StatusBUSY] = 1'b 0;
+ return;
+ end
+ // Fetch address from mailbox
+ backend_data.get(mbx_data);
+ opcode = mbx_data[39:32];
+ host_address = mbx_data[31:0];
+
+ // if opcode is not PageProgram, somehow mailbox corrupted. Report error and put it back to mailbox
+ if (opcode != spi_device_pkg::CmdPageProgram) begin
+ $display("SPIFlash: Opcode is not PageProgram, putting the data back to backend_data");
+ backend_data.put({opcode, host_address});
+ return;
+ end
+
+ // Write data into storage
+ buffer_offset = host_address[7:0];
+ repeat (page_buffer_size) begin
+ storage[{host_address[31:8], buffer_offset%PageSize}] = page_buffer[buffer_offset%256];
+ buffer_offset = (buffer_offset+1) % PageSize;
+ end
+
+ // Determines delay
+ // Didn't use $dist_normal() as Typ is most likely lower bound.
+ // Maybe Poisson Distribution is appropriate?
+ #($urandom_range(LocalPPTyp*1.1, LocalPPTyp*0.9) * 1us)
+
+ // At the end, clear BUSY
+ status[0][StatusBUSY] = 1'b 0;
+ endtask : backend_page_program
+
+ initial begin
+ assert(PageSize == 256)
+ else begin
+ $display("SPIFlash currently supports 256B Page only");
+ $finish();
+ end
+ end
+
endprogram : spiflash
diff --git a/hw/ip/spi_device/pre_dv/spid_passthrough_sim_cfg.hjson b/hw/ip/spi_device/pre_dv/spid_passthrough_sim_cfg.hjson
index d939cf7..e7b5aca 100644
--- a/hw/ip/spi_device/pre_dv/spid_passthrough_sim_cfg.hjson
+++ b/hw/ip/spi_device/pre_dv/spid_passthrough_sim_cfg.hjson
@@ -34,6 +34,15 @@
"+TESTNAME=wel"
]
}
+ {
+ name: spid_passthrough_program
+ // WEL + PageProgram test
+ // Randomly issue Program with/without WEL, then check if the values are
+ // written or not by reading them back.
+ run_opts: [
+ "+TESTNAME=program"
+ ]
+ }
]
regressions: [
@@ -41,7 +50,8 @@
name: smoke
tests: [
"spid_passthrough_readbasic",
- "spid_passthrough_addr4b"
+ "spid_passthrough_addr4b",
+ "spid_passthrough_wel"
]
}
{
diff --git a/hw/ip/spi_device/pre_dv/tb/spid_common.sv b/hw/ip/spi_device/pre_dv/tb/spid_common.sv
index 462670c..2b9d5c1 100644
--- a/hw/ip/spi_device/pre_dv/tb/spid_common.sv
+++ b/hw/ip/spi_device/pre_dv/tb/spid_common.sv
@@ -1072,7 +1072,7 @@
h2d.a_data = wdata;
h2d.a_mask = wstrb;
h2d.a_param = '0;
- h2d.a_size = 2;
+ h2d.a_size = $clog2($countones(wstrb));
h2d.a_source = '0;
h2d.d_ready = 1'b 0;