// spi.v		- an SPI 8 bit send/receive port
// (c) 2017 David R. Hunter
//
// Revisions: 
// 09/07/14		DH - original version
// 09/01/17		DH - made some design improvements
//						- reworked FSM, etc.
//
// this is a basic SPI port that clocks data out and in
// it was designed for driving an SPI EEPROM
// it supports SPI mode 0 only
// mode 0: 	samples input on rising edge
//				shifts output on falling edge
//
// The SPI is 1/4 the rate of clk4x
// Notes:
//		1. The clk4x input should be chosen so the max speed of the 
// 		SPI device is not exceeded
// 	2. The load input must be at least two periods of clk4x


module spi (reset_n,clk4x,load,ready,din,dout,sdo,sdi,sck);

	input		reset_n;			// reset input
	input		clk4x;			// 4x clock in
	input		load;				// load pulse to load tx register and start I/O
	output	reg ready;		// signal that operation is complete
	input		[7:0]din;		// data to be sent
	output	reg [7:0]dout;	// data received
	output	sdo;				// serial data out
	input		sdi;				// serial data in
	output	reg sck;			// serial data clock
	
	
	// internal registers and wires
	
	// tx and rx registers
	reg		[7:0]txreg;	// transmit register
	reg		[7:0]rxreg;	// receive register
	
	reg		[3:0]BitCount;	// bit counter
	
	// fsm
	reg		[2:0]current;	// current state
	reg		[2:0]next;		// next state
	
	parameter IDLE 	= 3'b000;
	parameter START 	= 3'b001;
	parameter LOW0		= 3'b010;
	parameter LOW1		= 3'b011;
	parameter HIGH0	= 3'b100;
	parameter HIGH1 	= 3'b101;
	parameter SAVE		= 3'b110;
	
	// control signals
	reg		load_sync;	// synchronized load
	reg		loadSR;		// load the TX register
	reg		shiftIn;		// shift data in
	reg		shiftOut;	// shift data out
	reg		nextBit;		// clock next bit
	reg		saveEn;		// save from RX register
	
	
	// synchronize load to the clock
	always @(posedge clk4x)
	begin
		load_sync <= load;
	end
	
	// set sdo idles low unless shifting in progress
	assign sdo = (!reset_n) ? 1'b0 : txreg[7];		// tx MSB or 0
	

	// save byte received
	always @(posedge clk4x)
	begin
		if (!reset_n)
			dout <= 8'h00;
		else if (saveEn)
			dout <= rxreg;
	end


	// fsm implementation on falling edge of clock
	// because SPI clock is on the positive edges
	always @(posedge clk4x)
	begin
		if (!reset_n)
			current <= IDLE;
		else
			current <= next;
	end
		
	always @(current or load_sync or BitCount)
	begin		
		case (current)
			IDLE:
				if (load_sync)
					next <= START;
				else
					next <= IDLE;
					
			START:
				if (!load_sync)	// wait for load to deassert before shifting
					next <= LOW0;
				else
					next <= START;
			
			LOW0:						// SPI clock low
				next <= LOW1;

			LOW1:
				next <= HIGH0;

			HIGH0:					// SPI clock high
				next <= HIGH1;

			HIGH1:
				if (BitCount == 0)
					next <= SAVE;
				else
					next <= LOW0;

			SAVE:
				next <= IDLE;
				
			default:
				next <= IDLE;
		endcase
		
	end
		
		
	always @(current)
	begin
		case (current)
			IDLE:
				begin
					ready 		<= 1'b1;		// show SPI is not in progress
					loadSR 		<= 1'b0;	
					shiftIn 		<= 1'b0;
					shiftOut 	<= 1'b0;
					nextBit 		<= 1'b0;
					saveEn 		<= 1'b0;
					sck 			<= 1'b0;
					saveEn 		<= 1'b0;
				end
				
			START:
				begin
					ready 		<= 1'b0;
					loadSR 		<= 1'b1;		// load output data, set bit counter
					shiftIn 		<= 1'b0;
					shiftOut 	<= 1'b0;
					nextBit 		<= 1'b0;
					saveEn 		<= 1'b0;
					sck 			<= 1'b0;
					saveEn 		<= 1'b0;
				end
				
			LOW0:
				begin
					ready 		<= 1'b0;
					loadSR 		<= 1'b0;	
					shiftIn 		<= 1'b0;
					shiftOut 	<= 1'b0;
					nextBit 		<= 1'b0;
					saveEn 		<= 1'b0;
					sck 			<= 1'b0;		// set clock low
					saveEn 		<= 1'b0;
				end
				
			LOW1:
				begin
					ready 		<= 1'b0;
					loadSR 		<= 1'b0;	
					shiftIn 		<= 1'b0;
					shiftOut 	<= 1'b0;
					nextBit 		<= 1'b0;
					saveEn 		<= 1'b0;
					sck 			<= 1'b0;
					saveEn 		<= 1'b0;
				end
				
			HIGH0:
				begin
					ready 		<= 1'b0;
					loadSR 		<= 1'b0;	
					shiftIn 		<= 1'b1;		// capture data on rising edge
					shiftOut 	<= 1'b0;
					nextBit 		<= 1'b1;		// decrement bit count
					saveEn 		<= 1'b0;
					sck 			<= 1'b1;		// set clock high
					saveEn 		<= 1'b0;
				end
				
			HIGH1:
				begin
					ready 		<= 1'b0;
					loadSR 		<= 1'b0;	
					shiftIn 		<= 1'b0;
					shiftOut 	<= 1'b1;		// shift data out
					nextBit 		<= 1'b0;
					saveEn 		<= 1'b0;
					sck 			<= 1'b1;
					saveEn 		<= 1'b0;
				end
				
			SAVE:
				begin
					ready 		<= 1'b0;
					loadSR 		<= 1'b0;	
					shiftIn 		<= 1'b0;
					shiftOut 	<= 1'b0;
					nextBit 		<= 1'b0;
					saveEn 		<= 1'b0;
					sck 			<= 1'b0;
					saveEn 		<= 1'b1;		// save data received
				end
				
			default:
				begin
					ready 		<= 1'b0;
					loadSR 		<= 1'b0;	
					shiftIn 		<= 1'b0;
					shiftOut 	<= 1'b0;
					nextBit 		<= 1'b0;
					saveEn 		<= 1'b0;
					sck 			<= 1'b0;
					saveEn 		<= 1'b0;
				end
		endcase
	end
		
		
		
	// bit counter -- counts 8 data bits
	always @(posedge clk4x)
	begin
		if (loadSR)
			BitCount <= 4'd8;								// set for 8 bits
		else if (nextBit && (BitCount > 4'd0))
			BitCount <= BitCount - 4'd1;				// decrement bit count
	end
	

	// shift registers for SPI in/out

	// MODE 0  (data out on falling edge, data in on rising edge)
	// transmit register
	always @(posedge clk4x)
	begin
		if (!reset_n)
			txreg <= 8'h00;
		else if (loadSR)
			txreg <= din;					// load puts MSB out
		else if (shiftOut)
			txreg <= { txreg[6:0], 1'b0 };		// shift left
	end

	// receive register
	always @(posedge clk4x)
	begin
		if (!reset_n)
			rxreg <= 8'h00;
		else if (shiftIn)
			rxreg <= { rxreg[6:0], sdi };		// shift left
	end


endmodule