aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWojciech Kosior <kwojtus@protonmail.com>2020-11-03 19:16:12 +0100
committerWojciech Kosior <kwojtus@protonmail.com>2020-11-03 19:16:12 +0100
commitcd0c787bcfc89a0a1e14f4404a59cb4697854621 (patch)
treeea96fd360441b7c6f171b642016b68039283cc8a
parent33f05839b7815a5a18a2b920dba4549d2bd7ce16 (diff)
downloadAGH-engineering-thesis-cd0c787bcfc89a0a1e14f4404a59cb4697854621.tar.gz
AGH-engineering-thesis-cd0c787bcfc89a0a1e14f4404a59cb4697854621.zip
add spi wishbone slave with a simplified flash memory chip model and a test bench
-rw-r--r--design/spi_slave.v262
-rw-r--r--models/flash_memory.v206
-rw-r--r--tests/spi_slave/Makefile9
-rw-r--r--tests/spi_slave/operations.memv62
-rw-r--r--tests/spi_slave/rom.mem258
-rw-r--r--tests/spi_slave/test.v141
6 files changed, 938 insertions, 0 deletions
diff --git a/design/spi_slave.v b/design/spi_slave.v
new file mode 100644
index 0000000..a53d9d2
--- /dev/null
+++ b/design/spi_slave.v
@@ -0,0 +1,262 @@
+`default_nettype none
+
+`define ADDR_WIDTH ($clog2(MEMORY_BLOCKS + 1) + 8)
+
+module spi_slave
+ #(
+ parameter MEMORY_BLOCKS = 1 /* 1 block stores 256 16-bit words */
+ )
+ (
+ /* Interface to flash memory chip */
+ output reg sdo,
+ input wire sdi,
+ output wire sck,
+ output reg ss_n,
+
+ /* Wishbone slave interface */
+ output wire ACK_O,
+ input wire [`ADDR_WIDTH - 1:0] ADR_I,
+ input wire CLK_I,
+ input wire [15:0] DAT_I,
+ output wire [15:0] DAT_O,
+ input wire RST_I,
+ input wire STB_I,
+ input wire WE_I,
+ output wire STALL_O
+ );
+
+ parameter MEMORY_IDX_WIDTH = $clog2(MEMORY_BLOCKS) + 8;
+ parameter MEMORY_MAX_ADDR = MEMORY_BLOCKS * 256 - 1;
+ parameter BIT_COUNT_WIDTH = MEMORY_IDX_WIDTH + 4;
+
+ reg [15:0] memory [MEMORY_MAX_ADDR:0];
+
+ reg [15:0] data_read_from_memory;
+ reg [15:0] data_read_from_regs;
+ wire [15:0] data_to_write_to_memory;
+
+ reg [`ADDR_WIDTH - 1:0] ADR_I_latched;
+ reg [15:0] DAT_I_latched;
+ reg [1:0] wb_read_memory;
+ reg wb_write_memory;
+ reg wb_read_regs;
+ reg wb_write_regs;
+
+ /*
+ * The wires below are only relevant if regs are being accessed, i.e.
+ * ADR_I_latched > MEMORY_MAX_ADDR.
+ */
+ wire reg_bytes_to_output;
+ wire reg_bytes_to_receive;
+ wire reg_operating;
+
+ assign reg_bytes_to_output = ADR_I_latched[1:0] == 2'b00;
+ assign reg_bytes_to_receive = ADR_I_latched[1:0] == 2'b01;
+ assign reg_operating = ADR_I_latched[1] == 1'b1;
+
+ reg ack;
+
+ reg [2:0] operating;
+
+ wire [1:0] start_operating;
+ assign start_operating = {operating[2:1] == 2'b01, operating[1:0] == 2'b01};
+
+ reg [15:0] out_data; // sdo data
+ reg [15:0] in_data; // sdi data
+
+ reg bytes_to_output_odd;
+ reg bytes_to_receive_odd;
+ reg [BIT_COUNT_WIDTH - 1:0] bits_to_output;
+ reg [BIT_COUNT_WIDTH - 1:0] bits_to_receive;
+ reg [MEMORY_IDX_WIDTH - 1:0] memory_idx;
+
+ reg [1:0] spi_read_memory;
+ reg spi_write_memory;
+
+ wire spi_write_1byte;
+ wire initial_spi_read_memory;
+ assign spi_write_1byte = bits_to_receive[3] == 0 && bytes_to_receive_odd;
+ assign initial_spi_read_memory = start_operating[0];
+
+ wire [MEMORY_IDX_WIDTH - 1:0] memory_read_idx;
+ wire [MEMORY_IDX_WIDTH - 1:0] memory_write_idx;
+
+ assign memory_read_idx = initial_spi_read_memory ? 0 :
+ spi_read_memory[0] ? memory_idx :
+ ADR_I_latched;
+
+ assign memory_write_idx = spi_write_memory ? memory_idx :
+ ADR_I_latched;
+
+ assign data_to_write_to_memory = spi_write_memory ?
+ (spi_write_1byte ?
+ {8'hXX, in_data[7:0]} :
+ {in_data[7:0], in_data[15:8]}) :
+ DAT_I_latched;
+
+ wire wb_reg_can_be_written;
+ assign wb_reg_can_be_written = !operating[0];
+
+ wire wb_mread_completes;
+ wire wb_rread_completes;
+ wire wb_mwrite_completes;
+ wire wb_rwrite_completes;
+
+ assign wb_mread_completes = !(spi_read_memory[0] || initial_spi_read_memory) &&
+ wb_read_memory[0];
+
+ assign wb_rread_completes = wb_read_regs; /* can always read immediately */
+
+ assign wb_mwrite_completes = !spi_write_memory && wb_write_memory;
+
+ assign wb_rwrite_completes = wb_reg_can_be_written && wb_write_regs;
+
+ wire wb_operation_accepted;
+ wire wb_operation_pending;
+ wire wb_operation_completes;
+
+ assign wb_operation_accepted = !STALL_O && STB_I;
+ assign wb_operation_pending = wb_read_memory || wb_read_regs ||
+ wb_write_memory || wb_write_regs;
+ assign wb_operation_completes = wb_mread_completes || wb_rread_completes ||
+ wb_mwrite_completes || wb_rwrite_completes;
+
+ assign DAT_O = wb_read_memory[1] ? data_read_from_memory :
+ data_read_from_regs;
+ assign ACK_O = ack;
+ assign STALL_O = wb_operation_pending && !wb_operation_completes;
+
+ parameter state_sending = 0;
+ parameter state_receiving = 1;
+ reg state;
+
+ assign sck = CLK_I;
+
+ always @ (posedge CLK_I) begin
+ operating[2:1] <= operating[1:0];
+
+ data_read_from_memory <= memory[memory_read_idx];
+
+ if (spi_write_memory || wb_write_memory)
+ memory[memory_write_idx] <= data_to_write_to_memory;
+
+ spi_read_memory[1] <= spi_read_memory[0] || initial_spi_read_memory;
+
+ if (operating[0]) begin
+ spi_read_memory[0] <= bits_to_output[BIT_COUNT_WIDTH - 1:4] != 0 &&
+ bits_to_output[3:0] ==
+ (bytes_to_output_odd ? 11 : 3);
+
+ spi_write_memory <= bits_to_receive == 1 ||
+ bits_to_receive[3:0] ==
+ (bytes_to_receive_odd ? 9 : 1);
+ end else begin
+ spi_read_memory[0] <= 0;
+ spi_write_memory <= 0;
+ end
+
+ out_data <= spi_read_memory[1] ?
+ {data_read_from_memory[7:0], data_read_from_memory[15:8]} :
+ {out_data[14:0], 1'bx};
+
+ in_data <= {in_data[14:0], sdi};
+
+ wb_read_memory[1] <= wb_read_memory[0];
+
+ if (reg_bytes_to_output)
+ data_read_from_regs <= bits_to_output[BIT_COUNT_WIDTH - 1:3];
+ else if (reg_bytes_to_receive)
+ data_read_from_regs <= bits_to_receive[BIT_COUNT_WIDTH - 1:3];
+ else if (reg_operating)
+ data_read_from_regs <= {16{operating[0]}};
+ else
+ data_read_from_regs <= 16'hXXXX;
+
+ if (wb_write_regs && wb_reg_can_be_written) begin
+ if (reg_bytes_to_output) begin
+ bits_to_output <= DAT_I_latched << 3;
+ bytes_to_output_odd <= DAT_I_latched[0];
+ end
+ if (reg_bytes_to_receive) begin
+ bits_to_receive <= (DAT_I_latched << 3);
+ bytes_to_receive_odd <= DAT_I_latched[0];
+ end
+ if (reg_operating)
+ operating[0] <= 1; /* might be overwritten below */
+ end
+
+ if (RST_I) begin
+ wb_read_memory[0] <= 0;
+ wb_write_memory <= 0;
+ wb_read_regs <= 0;
+ wb_write_regs <= 0;
+
+ operating[0] <= 0;
+
+ bits_to_receive <= 16'hXXXX;
+ bits_to_output <= 16'hXXXX;//{{(BIT_COUNT_WIDTH - 4){1'bx}}, 4'h0};
+ bytes_to_output_odd <= 1'bx;
+ bytes_to_receive_odd <= 1'bx;
+
+ ack <= 0;
+ end else begin // if (RST_I)
+ if (wb_operation_accepted) begin
+ ADR_I_latched <= ADR_I;
+ DAT_I_latched <= DAT_I;
+ wb_read_memory[0] <= !WE_I && ADR_I <= MEMORY_MAX_ADDR;
+ wb_write_memory <= WE_I && ADR_I <= MEMORY_MAX_ADDR;
+ wb_read_regs <= !WE_I && ADR_I > MEMORY_MAX_ADDR;
+ wb_write_regs <= WE_I && ADR_I > MEMORY_MAX_ADDR;
+ end else if (wb_operation_completes) begin
+ wb_read_memory[0] <= 0;
+ wb_write_memory <= 0;
+ wb_read_regs <= 0;
+ wb_write_regs <= 0;
+ end
+
+ ack <= wb_operation_completes;
+ end // else: !if(RST_I)
+
+ if (start_operating[1]) begin
+ memory_idx <= 1;
+ state <= state_sending;
+ end
+
+ if (operating == 3'b111) begin
+ if (state == state_sending) begin
+ bits_to_output <= bits_to_output - 1;
+
+ if (spi_read_memory[0])
+ memory_idx <= memory_idx + 1;
+
+ if (bits_to_output - 1 == 0) begin
+ state <= state_receiving;
+ memory_idx <= 0;
+
+ if (bits_to_receive == 0)
+ operating[0] <= 0;
+ end
+ end else if (state == state_receiving) begin
+ bits_to_receive <= bits_to_receive - 1;
+
+ if (spi_write_memory)
+ memory_idx <= memory_idx + 1;
+
+ if (bits_to_receive - 1 == 0)
+ operating[0] <= 0;
+ end
+ end // if (operating[2])
+ end // always @ (posedge CLK_I)
+
+ always @ (negedge CLK_I) begin
+ sdo <= out_data[15];
+ ss_n <= !(operating == 3'b111);
+ end
+
+`ifdef SIMULATION
+ initial begin
+ operating[0] <= 0;
+ ss_n <= 1;
+ end
+`endif
+endmodule // spi_slave
diff --git a/models/flash_memory.v b/models/flash_memory.v
new file mode 100644
index 0000000..3b6c659
--- /dev/null
+++ b/models/flash_memory.v
@@ -0,0 +1,206 @@
+`default_nettype none
+`timescale 1ns/1ns
+
+`include "messages.vh"
+
+`ifndef SIMULATION
+ `error_SIMULATION_not_defined
+; /* Cause syntax error */
+`endif
+
+module W25Q16BV_flash
+ #(
+ parameter BYTES_TO_INITIALIZE = 0,
+ parameter INITIAL_CONTENTS_FILE = "some_file.mem"
+ )
+ (
+ /* wires we're not using are omitted */
+ input wire sdo,
+ output wire sdi,
+ input wire sck,
+ input wire ss_n
+ );
+
+ reg outputting;
+ initial
+ outputting <= 0;
+ reg outputted_bit;
+ assign sdi = (outputting && !ss_n) ? outputted_bit : 1'bz;
+
+ reg powered_up;
+ initial
+ powered_up <= 0;
+
+ reg powering_up_in_progress;
+ initial
+ powering_up_in_progress <= 0;
+
+ parameter power_up_wait_time = 3000; /* ns */
+ integer power_up_time;
+
+ parameter idle = 0;
+ parameter error = 1;
+ parameter receiving_instruction = 2;
+ parameter releasing_power_down = 3;
+ parameter receiving_fast_read_address = 4;
+ parameter receiving_fast_read_dummy_byte = 5;
+ parameter responding_fast_read = 6;
+ integer state;
+ initial
+ state <= idle;
+
+ parameter release_power_down_instruction = 8'hAB;
+ parameter fast_read_instruction = 8'h0B;
+
+ reg [7:0] instruction;
+ integer instruction_bits_received;
+
+ reg [23:0] address;
+ integer address_bits_received;
+ reg [2:0] bit_in_byte;
+
+ integer dummy_bits_received;
+
+ parameter memory_size = 1024 * 1024 * 2; /* 2 megabytes */
+ reg [7:0] memory [memory_size - 1 : 0];
+ initial
+ $readmemh(INITIAL_CONTENTS_FILE, memory, 0, BYTES_TO_INITIALIZE - 1);
+
+ always @ (posedge sck) begin
+ if (!powered_up && power_up_time + power_up_wait_time < $time) begin
+ powered_up <= 1;
+ if (state == releasing_power_down)
+ state = idle;
+ end
+
+ case (state)
+ idle : begin
+ if (!ss_n) begin
+ state <= receiving_instruction;
+ instruction_bits_received <= 1;
+ instruction[7] <= sdo;
+ end
+ end
+ error : begin
+ if (ss_n) begin
+ state <= idle;
+ end
+ end
+ receiving_instruction : begin
+ if (ss_n) begin
+ state <= idle;
+ `MSG(("SPI: error: operation aborted after only %0d instruction bits received",
+ instruction_bits_received));
+ end else begin
+ instruction[7 - instruction_bits_received] <= sdo;
+ instruction_bits_received <= instruction_bits_received + 1;
+
+ if (instruction_bits_received + 1 == 8) begin
+ if (!powered_up && ({instruction[7:1], sdo} !=
+ release_power_down_instruction)) begin
+ state <= error;
+ `MSG(("SPI: error: attempted instruction 0x%x while in power down mode",
+ {instruction[7:1], sdo}));
+ end else begin
+ case ({instruction[7:1], sdo})
+ release_power_down_instruction : begin
+ state <= releasing_power_down;
+ power_up_time <= $time;
+ `DBG(("SPI: release power down issued"));
+ end
+ fast_read_instruction : begin
+ state <= receiving_fast_read_address;
+ address_bits_received <= 0;
+ `DBG(("SPI: fast read issued"));
+ end
+ default : begin
+ state <= error;
+ `MSG(("SPI: error: unknown instruction: 0x%x",
+ {instruction[7:1], sdo}));
+ end
+ endcase // case ({instruction[7:1], sdo})
+ end // else: !if(!powered_up && ({instruction[7:1], sdo} !=...
+ end // if (instruction_bits_received + 1 == 8)
+ end // else: !if(ss_n)
+ end // case: receiving_instruction
+ releasing_power_down : begin
+ if (!ss_n) begin
+ state <= receiving_instruction;
+ instruction_bits_received <= 1;
+ instruction[7] <= sdo;
+
+ if (power_up_time + power_up_wait_time >= $time) begin
+ `MSG(("SPI: error: release power down interrupted %0d ns before finishing",
+ power_up_time + power_up_wait_time - $time));
+ end
+ end // if (!ss_n)
+ end // case: releasing_power_down
+ receiving_fast_read_address : begin
+ if (ss_n) begin
+ state <= idle;
+ `MSG(("SPI: error: fast read instruction aborted after %0d bits of address were sent",
+ address_bits_received));
+ end else begin
+ address[23 - address_bits_received] <= sdo;
+ address_bits_received <= address_bits_received + 1;
+
+ if (address_bits_received + 1 == 24) begin
+ state <= receiving_fast_read_dummy_byte;
+ dummy_bits_received <= 0;
+ end
+ end
+ end
+ receiving_fast_read_dummy_byte : begin
+ if (ss_n) begin
+ state <= idle;
+ `MSG(("SPI: error: fast read instruction aborted after %0d bits of dummy byte were sent",
+ dummy_bits_received));
+ end else begin
+ dummy_bits_received <= dummy_bits_received + 1;
+
+ if (dummy_bits_received + 1 == 8) begin
+ bit_in_byte <= 7;
+
+ if (address >= memory_size) begin
+ state <= error;
+ `MSG(("SPI: error: memory address too high (0x%x)",
+ address));
+ end else begin
+ state <= responding_fast_read;
+ end
+ end
+ end // else: !if(ss_n)
+ end // case: receiving_fast_read_dummy_byte
+ responding_fast_read : begin
+ if (ss_n) begin
+ state <= idle;
+ end else begin
+ bit_in_byte <= bit_in_byte - 1;
+
+ if (bit_in_byte == 0) begin
+ address <= address + 1;
+
+ if (address + 1 >= memory_size) begin
+ state <= error;
+ `MSG(("SPI: error: memory address too high (0x%x)",
+ address));
+ end
+ end
+ end // else: !if(ss_n)
+ end // case: responding_fast_read
+ endcase // case (state)
+ end // always @ (posedge sck)
+
+ always @ (negedge sck) begin
+ if (ss_n) begin
+ outputting <= 0;
+ end else if (state == responding_fast_read) begin
+ outputting <= 1;
+ outputted_bit <= memory[address][bit_in_byte];
+ end else if (state == error) begin
+ outputted_bit <= 1'bx;
+ end else begin
+ outputting <= 0;
+ end
+ end // always @ (negedge sck)
+endmodule // W25Q16BV_flash
diff --git a/tests/spi_slave/Makefile b/tests/spi_slave/Makefile
new file mode 100644
index 0000000..8a8d3e8
--- /dev/null
+++ b/tests/spi_slave/Makefile
@@ -0,0 +1,9 @@
+DEPENDS = operations.mem master.v spi_slave.v flash_memory.v messages.vh rom.mem
+
+IVFLAGS = \
+ -DMASTER_OPERATIONS_COUNT=$(call FILE_LINES,operations.mem) \
+ -DROM_WORDS_COUNT=$(call FILE_LINES,rom.mem)
+
+TOP = spi_test
+
+include ../../Makefile.test
diff --git a/tests/spi_slave/operations.memv b/tests/spi_slave/operations.memv
new file mode 100644
index 0000000..084353b
--- /dev/null
+++ b/tests/spi_slave/operations.memv
@@ -0,0 +1,62 @@
+`include "macroasm.vh" // look into macroasm.vh for more info
+
+// power up flash ship
+`WRITE (00100, 0001) // set bytes_to_output to 1
+`WRITE (00101, 0000) // set bytes_to_receive to 0
+
+`WRITE (00000, 00AB) // release power-down spi command
+
+`WRITE (00102, 1111) // start spi operation
+
+
+// reading this reg during operation should yield 1's
+`READ (00102, FFFF)
+
+
+// the completions of first write after spi operation has been started
+// will be delayed until the operations has finished. This is of no concern
+// here, since the master will just gently wait for ACK from slave.
+
+
+// read contents at addresses h3A through h4A
+`WRITE (00101, 0010) // set bytes_to_receive to 16 (h10)
+`READ (00102, 0000) // see, that operation indeed finished
+
+`WRITE (00000, 000B) // fast read spi command + 8 high address bits
+`WRITE (00001, 3A00) // 16 low address bits
+`WRITE (00002, 0000) // dummy byte
+
+`WRITE (00100, 0005) // set bytes_to_output to 5
+
+// Cause delay b4 starting operation - chip's power-up takes 3000ns
+`DESELECT
+`DESELECT
+`WAIT
+`DESELECT
+`WAIT
+`DESELECT
+
+`WRITE (00102, 1111) // start spi operation (bits written to h102 don't matter)
+
+`WRITE (00100, 0000) // force wait for operation completion
+
+// check the data we've just read
+`READ (00000, 00CB)
+`READ (00001, 00D2)
+`READ (00002, 00D9)
+`READ (00003, 00E0)
+`READ (00004, 00E7)
+`READ (00005, 00EE)
+`READ (00006, 00F5)
+`READ (00007, 00FC)
+
+// how about reading a single byte?
+`WRITE (00100, 0005) // set bytes_to_output to 5
+`WRITE (00101, 0001) // set bytes_to_receive to 1
+`WRITE (00000, 000B) // fast read spi command + 8 high address bits
+`WRITE (00001, E400) // 16 low address bits
+//`WRITE (00002, 0000) // dummy byte
+`WRITE (00102, BEEF) // start spi operation
+`WRITE (00100, 0000) // force wait for operation completion
+
+`READ (00000, xx1E) // check the byte we've just read
diff --git a/tests/spi_slave/rom.mem b/tests/spi_slave/rom.mem
new file mode 100644
index 0000000..06fff24
--- /dev/null
+++ b/tests/spi_slave/rom.mem
@@ -0,0 +1,258 @@
+// numbers 0, 7, 2*7, 3*7, ..., 256*7 in hex (2 LE bytes for each number)
+0 0
+7 0
+e 0
+15 0
+1c 0
+23 0
+2a 0
+31 0
+38 0
+3f 0
+46 0
+4d 0
+54 0
+5b 0
+62 0
+69 0
+70 0
+77 0
+7e 0
+85 0
+8c 0
+93 0
+9a 0
+a1 0
+a8 0
+af 0
+b6 0
+bd 0
+c4 0
+cb 0
+d2 0
+d9 0
+e0 0
+e7 0
+ee 0
+f5 0
+fc 0
+3 1
+a 1
+11 1
+18 1
+1f 1
+26 1
+2d 1
+34 1
+3b 1
+42 1
+49 1
+50 1
+57 1
+5e 1
+65 1
+6c 1
+73 1
+7a 1
+81 1
+88 1
+8f 1
+96 1
+9d 1
+a4 1
+ab 1
+b2 1
+b9 1
+c0 1
+c7 1
+ce 1
+d5 1
+dc 1
+e3 1
+ea 1
+f1 1
+f8 1
+ff 1
+6 2
+d 2
+14 2
+1b 2
+22 2
+29 2
+30 2
+37 2
+3e 2
+45 2
+4c 2
+53 2
+5a 2
+61 2
+68 2
+6f 2
+76 2
+7d 2
+84 2
+8b 2
+92 2
+99 2
+a0 2
+a7 2
+ae 2
+b5 2
+bc 2
+c3 2
+ca 2
+d1 2
+d8 2
+df 2
+e6 2
+ed 2
+f4 2
+fb 2
+2 3
+9 3
+10 3
+17 3
+1e 3
+25 3
+2c 3
+33 3
+3a 3
+41 3
+48 3
+4f 3
+56 3
+5d 3
+64 3
+6b 3
+72 3
+79 3
+80 3
+87 3
+8e 3
+95 3
+9c 3
+a3 3
+aa 3
+b1 3
+b8 3
+bf 3
+c6 3
+cd 3
+d4 3
+db 3
+e2 3
+e9 3
+f0 3
+f7 3
+fe 3
+5 4
+c 4
+13 4
+1a 4
+21 4
+28 4
+2f 4
+36 4
+3d 4
+44 4
+4b 4
+52 4
+59 4
+60 4
+67 4
+6e 4
+75 4
+7c 4
+83 4
+8a 4
+91 4
+98 4
+9f 4
+a6 4
+ad 4
+b4 4
+bb 4
+c2 4
+c9 4
+d0 4
+d7 4
+de 4
+e5 4
+ec 4
+f3 4
+fa 4
+1 5
+8 5
+f 5
+16 5
+1d 5
+24 5
+2b 5
+32 5
+39 5
+40 5
+47 5
+4e 5
+55 5
+5c 5
+63 5
+6a 5
+71 5
+78 5
+7f 5
+86 5
+8d 5
+94 5
+9b 5
+a2 5
+a9 5
+b0 5
+b7 5
+be 5
+c5 5
+cc 5
+d3 5
+da 5
+e1 5
+e8 5
+ef 5
+f6 5
+fd 5
+4 6
+b 6
+12 6
+19 6
+20 6
+27 6
+2e 6
+35 6
+3c 6
+43 6
+4a 6
+51 6
+58 6
+5f 6
+66 6
+6d 6
+74 6
+7b 6
+82 6
+89 6
+90 6
+97 6
+9e 6
+a5 6
+ac 6
+b3 6
+ba 6
+c1 6
+c8 6
+cf 6
+d6 6
+dd 6
+e4 6
+eb 6
+f2 6
+f9 6
+0 7
diff --git a/tests/spi_slave/test.v b/tests/spi_slave/test.v
new file mode 100644
index 0000000..f489fee
--- /dev/null
+++ b/tests/spi_slave/test.v
@@ -0,0 +1,141 @@
+`default_nettype none
+`timescale 1ns/1ns
+
+`include "messages.vh"
+
+`ifndef MASTER_OPERATIONS_COUNT
+ `error_MASTER_OPERATIONS_COUNT_must_be_defined
+; /* Cause syntax error */
+`endif
+
+`ifndef ROM_WORDS_COUNT
+ `error_ROM_WORDS_COUNT_must_be_defined
+; /* Cause syntax error */
+`endif
+
+`ifndef SIMULATION
+ `error_SIMULATION_not_defined
+; /* Cause syntax error */
+`endif
+
+module spi_test();
+ wire M_ACK_I;
+ wire M_CLK_I;
+ wire [19:0] M_ADR_O;
+ wire [15:0] M_DAT_I;
+ wire [15:0] M_DAT_O;
+ wire M_SEL_O; /* Ignored, assumed always high */
+ wire M_RST_I;
+ wire M_STB_O;
+ wire M_CYC_O;
+ wire M_WE_O;
+ wire M_STALL_I;
+
+ wire S_ACK_O;
+ wire S_CLK_I;
+ wire [8:0] S_ADR_I;
+ wire [15:0] S_DAT_I;
+ wire [15:0] S_DAT_O;
+ wire S_RST_I;
+ wire S_STB_I;
+ wire S_WE_I;
+ wire S_STALL_O;
+
+ /* Non-wishbone */
+ wire sdo;
+ wire sdi;
+ wire sck;
+ wire ss_n;
+
+ wire M_finished;
+
+ master_model
+ #(
+ .MASTER_NR(0),
+ .OPERATIONS_FILE("operations.mem"),
+ .OPERATIONS_COUNT(`MASTER_OPERATIONS_COUNT)
+ ) master
+ (
+ .ACK_I(M_ACK_I),
+ .CLK_I(M_CLK_I),
+ .ADR_O(M_ADR_O),
+ .DAT_I(M_DAT_I),
+ .DAT_O(M_DAT_O),
+ .SEL_O(M_SEL_O),
+ .RST_I(M_RST_I),
+ .STB_O(M_STB_O),
+ .CYC_O(M_CYC_O),
+ .WE_O(M_WE_O),
+ .STALL_I(M_STALL_I),
+
+ .finished(M_finished)
+ );
+
+ spi_slave slave
+ (
+ .ACK_O(S_ACK_O),
+ .CLK_I(S_CLK_I),
+ .ADR_I(S_ADR_I),
+ .DAT_I(S_DAT_I),
+ .DAT_O(S_DAT_O),
+ .RST_I(S_RST_I),
+ .STB_I(S_STB_I),
+ .WE_I(S_WE_I),
+ .STALL_O(S_STALL_O),
+
+ .sdo(sdo),
+ .sdi(sdi),
+ .sck(sck),
+ .ss_n(ss_n)
+ );
+
+ W25Q16BV_flash
+ #(
+ .BYTES_TO_INITIALIZE(`ROM_WORDS_COUNT * 2),
+ .INITIAL_CONTENTS_FILE("rom.mem")
+ ) flash
+ (
+ .sdo(sdo),
+ .sdi(sdi),
+ .sck(sck),
+ .ss_n(ss_n)
+ );
+
+ reg CLK;
+ reg RST;
+
+ assign M_ACK_I = S_ACK_O;
+ assign M_CLK_I = CLK;
+ assign M_DAT_I = S_DAT_O;
+ assign M_RST_I = RST;
+ assign M_STALL_I = S_STALL_O;
+
+ assign S_CLK_I = CLK;
+ assign S_ADR_I = M_ADR_O[8:0]; /* Ignore 11 topmost bits */
+ assign S_DAT_I = M_DAT_O;
+ assign S_RST_I = RST;
+ assign S_STB_I = M_STB_O && M_CYC_O;
+ assign S_WE_I = M_WE_O;
+
+ integer i;
+
+ initial begin
+ CLK <= 0;
+ RST <= 1;
+
+ for (i = 0; i < 600; i++) begin
+ #100; /* longer wait time so that power-up requires less idle ticks */
+
+ CLK <= ~CLK;
+
+ if (CLK)
+ RST <= 0;
+
+ if (M_finished)
+ $finish;
+ end
+
+ $display("error: master hasn't finished its operations in 300 ticks");
+ $finish;
+ end
+endmodule // spi_test