/*
 *  PicoSoC - A simple example SoC using PicoRV32
 *
 *  Copyright (C) 2017  Clifford Wolf <clifford@clifford.at>
 *
 *  Permission to use, copy, modify, and/or distribute this software for any
 *  purpose with or without fee is hereby granted, provided that the above
 *  copyright notice and this permission notice appear in all copies.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */

`timescale 1 ns / 1 ps

module testbench;
	reg flash_csb = 1;
	reg flash_clk = 0;

	wire flash_io0;
	wire flash_io1;
	wire flash_io2;
	wire flash_io3;

	reg flash_io0_oe = 0;
	reg flash_io1_oe = 0;
	reg flash_io2_oe = 0;
	reg flash_io3_oe = 0;

	reg flash_io0_dout = 0;
	reg flash_io1_dout = 0;
	reg flash_io2_dout = 0;
	reg flash_io3_dout = 0;

	assign flash_io0 = flash_io0_oe ? flash_io0_dout : 1'bz;
	assign flash_io1 = flash_io1_oe ? flash_io1_dout : 1'bz;
	assign flash_io2 = flash_io2_oe ? flash_io2_dout : 1'bz;
	assign flash_io3 = flash_io3_oe ? flash_io3_dout : 1'bz;

	spiflash uut (
		.csb(flash_csb),
		.clk(flash_clk),
		.io0(flash_io0),
		.io1(flash_io1),
		.io2(flash_io2),
		.io3(flash_io3)
	);

	localparam [23:0] offset = 24'h100000;
	localparam [31:0] word0 = 32'h 01300293;
	localparam [31:0] word1 = 32'h 00502223;

	reg [7:0] rdata;
	integer errcount = 0;

	task expect;
		input [7:0] data;
		begin
			if (data !== rdata) begin
				$display("ERROR: Got %x (%b) but expected %x (%b).", rdata, rdata, data, data);
				errcount = errcount + 1;
			end
		end
	endtask

	task xfer_begin;
		begin
			#5;
			flash_csb = 0;
			$display("-- BEGIN");
			#5;
		end
	endtask

	task xfer_dummy;
		begin
			flash_io0_oe = 0;
			flash_io1_oe = 0;
			flash_io2_oe = 0;
			flash_io3_oe = 0;

			#5;
			flash_clk = 1;
			#5;
			flash_clk = 0;
			#5;
		end
	endtask

	task xfer_end;
		begin
			#5;
			flash_csb = 1;
			flash_io0_oe = 0;
			flash_io1_oe = 0;
			flash_io2_oe = 0;
			flash_io3_oe = 0;
			$display("-- END");
			$display("");
			#5;
		end
	endtask

	task xfer_spi;
		input [7:0] data;
		integer i;
		begin
			flash_io0_oe = 1;
			flash_io1_oe = 0;
			flash_io2_oe = 0;
			flash_io3_oe = 0;

			for (i = 0; i < 8; i=i+1) begin
				flash_io0_dout = data[7-i];
				#5;
				flash_clk = 1;
				rdata[7-i] = flash_io1;
				#5;
				flash_clk = 0;
			end

			$display("--  SPI SDR  %02x %02x", data, rdata);
			#5;
		end
	endtask

	task xfer_qspi_wr;
		input [7:0] data;
		integer i;
		begin
			flash_io0_oe = 1;
			flash_io1_oe = 1;
			flash_io2_oe = 1;
			flash_io3_oe = 1;

			flash_io0_dout = data[4];
			flash_io1_dout = data[5];
			flash_io2_dout = data[6];
			flash_io3_dout = data[7];

			#5;
			flash_clk = 1;

			#5;
			flash_clk = 0;
			flash_io0_dout = data[0];
			flash_io1_dout = data[1];
			flash_io2_dout = data[2];
			flash_io3_dout = data[3];

			#5;
			flash_clk = 1;
			#5;
			flash_clk = 0;

			$display("-- QSPI SDR  %02x --", data);
			#5;
		end
	endtask

	task xfer_qspi_rd;
		integer i;
		begin
			flash_io0_oe = 0;
			flash_io1_oe = 0;
			flash_io2_oe = 0;
			flash_io3_oe = 0;

			#5;
			flash_clk = 1;
			rdata[4] = flash_io0;
			rdata[5] = flash_io1;
			rdata[6] = flash_io2;
			rdata[7] = flash_io3;

			#5;
			flash_clk = 0;

			#5;
			flash_clk = 1;
			rdata[0] = flash_io0;
			rdata[1] = flash_io1;
			rdata[2] = flash_io2;
			rdata[3] = flash_io3;

			#5;
			flash_clk = 0;

			$display("-- QSPI SDR  -- %02x", rdata);
			#5;
		end
	endtask

	task xfer_qspi_ddr_wr;
		input [7:0] data;
		integer i;
		begin
			flash_io0_oe = 1;
			flash_io1_oe = 1;
			flash_io2_oe = 1;
			flash_io3_oe = 1;

			flash_io0_dout = data[4];
			flash_io1_dout = data[5];
			flash_io2_dout = data[6];
			flash_io3_dout = data[7];

			#5;
			flash_clk = 1;
			flash_io0_dout = data[0];
			flash_io1_dout = data[1];
			flash_io2_dout = data[2];
			flash_io3_dout = data[3];

			#5;
			flash_clk = 0;

			$display("-- QSPI DDR  %02x --", data);
			#5;
		end
	endtask

	task xfer_qspi_ddr_rd;
		integer i;
		begin
			flash_io0_oe = 0;
			flash_io1_oe = 0;
			flash_io2_oe = 0;
			flash_io3_oe = 0;

			#5;
			flash_clk = 1;
			rdata[4] = flash_io0;
			rdata[5] = flash_io1;
			rdata[6] = flash_io2;
			rdata[7] = flash_io3;

			#5;
			flash_clk = 0;
			rdata[0] = flash_io0;
			rdata[1] = flash_io1;
			rdata[2] = flash_io2;
			rdata[3] = flash_io3;

			$display("-- QSPI DDR  -- %02x", rdata);
			#5;
		end
	endtask

	initial begin
		$dumpfile("spiflash_tb.vcd");
		$dumpvars(0, testbench);
		$display("");

		$display("Reset (FFh)");
		xfer_begin;
		xfer_spi(8'h ff);
		xfer_end;

		$display("Power Up (ABh)");
		xfer_begin;
		xfer_spi(8'h ab);
		xfer_end;

		$display("Read Data (03h)");
		xfer_begin;
		xfer_spi(8'h 03);
		xfer_spi(offset[23:16]);
		xfer_spi(offset[15:8]);
		xfer_spi(offset[7:0]);
		xfer_spi(8'h 00); expect(word0[7:0]);
		xfer_spi(8'h 00); expect(word0[15:8]);
		xfer_spi(8'h 00); expect(word0[23:16]);
		xfer_spi(8'h 00); expect(word0[31:24]);
		xfer_spi(8'h 00); expect(word1[7:0]);
		xfer_spi(8'h 00); expect(word1[15:8]);
		xfer_spi(8'h 00); expect(word1[23:16]);
		xfer_spi(8'h 00); expect(word1[31:24]);
		xfer_end;

		$display("Quad I/O Read (EBh)");
		xfer_begin;
		xfer_spi(8'h eb);
		xfer_qspi_wr(offset[23:16]);
		xfer_qspi_wr(offset[15:8]);
		xfer_qspi_wr(offset[7:0]);
		xfer_qspi_wr(8'h a5);
		xfer_dummy;
		xfer_qspi_rd; expect(word0[7:0]);
		xfer_qspi_rd; expect(word0[15:8]);
		xfer_qspi_rd; expect(word0[23:16]);
		xfer_qspi_rd; expect(word0[31:24]);
		xfer_qspi_rd; expect(word1[7:0]);
		xfer_qspi_rd; expect(word1[15:8]);
		xfer_qspi_rd; expect(word1[23:16]);
		xfer_qspi_rd; expect(word1[31:24]);
		xfer_end;

		$display("Continous Quad I/O Read");
		xfer_begin;
		xfer_qspi_wr(offset[23:16]);
		xfer_qspi_wr(offset[15:8]);
		xfer_qspi_wr(offset[7:0]);
		xfer_qspi_wr(8'h ff);
		xfer_dummy;
		xfer_qspi_rd; expect(word0[7:0]);
		xfer_qspi_rd; expect(word0[15:8]);
		xfer_qspi_rd; expect(word0[23:16]);
		xfer_qspi_rd; expect(word0[31:24]);
		xfer_qspi_rd; expect(word1[7:0]);
		xfer_qspi_rd; expect(word1[15:8]);
		xfer_qspi_rd; expect(word1[23:16]);
		xfer_qspi_rd; expect(word1[31:24]);
		xfer_end;

		$display("DDR Quad I/O Read (EDh)");
		xfer_begin;
		xfer_spi(8'h ed);
		xfer_qspi_ddr_wr(offset[23:16]);
		xfer_qspi_ddr_wr(offset[15:8]);
		xfer_qspi_ddr_wr(offset[7:0]);
		xfer_qspi_ddr_wr(8'h a5);
		xfer_dummy;
		xfer_qspi_ddr_rd; expect(word0[7:0]);
		xfer_qspi_ddr_rd; expect(word0[15:8]);
		xfer_qspi_ddr_rd; expect(word0[23:16]);
		xfer_qspi_ddr_rd; expect(word0[31:24]);
		xfer_qspi_ddr_rd; expect(word1[7:0]);
		xfer_qspi_ddr_rd; expect(word1[15:8]);
		xfer_qspi_ddr_rd; expect(word1[23:16]);
		xfer_qspi_ddr_rd; expect(word1[31:24]);
		xfer_end;

		$display("Continous DDR Quad I/O Read");
		xfer_begin;
		xfer_qspi_ddr_wr(offset[23:16]);
		xfer_qspi_ddr_wr(offset[15:8]);
		xfer_qspi_ddr_wr(offset[7:0]);
		xfer_qspi_ddr_wr(8'h ff);
		xfer_dummy;
		xfer_qspi_ddr_rd; expect(word0[7:0]);
		xfer_qspi_ddr_rd; expect(word0[15:8]);
		xfer_qspi_ddr_rd; expect(word0[23:16]);
		xfer_qspi_ddr_rd; expect(word0[31:24]);
		xfer_qspi_ddr_rd; expect(word1[7:0]);
		xfer_qspi_ddr_rd; expect(word1[15:8]);
		xfer_qspi_ddr_rd; expect(word1[23:16]);
		xfer_qspi_ddr_rd; expect(word1[31:24]);
		xfer_end;

		#5;

		if (errcount) begin
			$display("FAIL");
			$stop;
		end else begin
			$display("PASS");
		end
	end
endmodule