/* SD Card controller module. Allows reading from and writing to a microSD card
through SPI mode. 
	// MicroSD SPI/SD Mode/Nexys 4
	// 1: Unused / DAT2 / SD_DAT[2]
	// 2: CS / DAT3 / SD_DAT[3]
	// 3: MOSI / CMD / SD_CMD
	// 4: VDD / VDD / ~SD_RESET
	// 5: SCLK / SCLK / SD_SCK
	// 6: GND / GND / - 
	// 7: MISO / DAT0 / SD_DAT[0]
	// 8: UNUSED / DAT1 / SD_DAT[1]*/
module sd_controller(
	output reg cs, // Connect to SD_DAT[3].
	output mosi, // Connect to SD_CMD.
	input miso, // Connect to SD_DAT[0].
	output sclk, // Connect to SD_SCK.
				// For SPI mode, SD_DAT[2] and SD_DAT[1] should be held HIGH. 
				// SD_RESET should be held LOW.

	input rd,   // Read-enable. When [ready] is HIGH, asseting [rd] will 
				// begin a 512-byte READ operation at [address]. 
				// [byte_available] will transition HIGH as a new byte has been
				// read from the SD card. The byte is presented on [dout].
	output reg [7:0] dout, // Data output for READ operation.
	output reg byte_available, // A new byte has been presented on [dout].

	input wr,   // Write-enable. When [ready] is HIGH, asserting [wr] will
				// begin a 512-byte WRITE operation at [address].
				// [ready_for_next_byte] will transition HIGH to request that
				// the next byte to be written should be presentaed on [din].
	input [7:0] din, // Data input for WRITE operation.
	output reg ready_for_next_byte, // A new byte should be presented on [din].

	input reset, // Resets controller on assertion.
	output ready, // HIGH if the SD card is ready for a read or write operation.
	input [31:0] address,   // Memory address for read/write operation. This MUST 
							// be a multiple of 512 bytes, due to SD sectoring.
	input clk,  // 25 MHz clock.
	output [4:0] status // For debug purposes: Current state of controller.
);

	parameter RST = 0;
	parameter INIT = 1;
	parameter CMD0 = 2;
	parameter CMD55 = 3;
	parameter CMD41 = 4;
	parameter POLL_CMD = 5;
	
	parameter IDLE = 6;
	parameter READ_BLOCK = 7;
	parameter READ_BLOCK_WAIT = 8;
	parameter READ_BLOCK_DATA = 9;
	parameter READ_BLOCK_CRC = 10;
	parameter SEND_CMD = 11;
	parameter RECEIVE_BYTE_WAIT = 12;
	parameter RECEIVE_BYTE = 13;
	parameter WRITE_BLOCK_CMD = 14;
	parameter WRITE_BLOCK_INIT = 15;
	parameter WRITE_BLOCK_DATA = 16;
	parameter WRITE_BLOCK_BYTE = 17;
	parameter WRITE_BLOCK_WAIT = 18;
	
	parameter WRITE_DATA_SIZE = 515;
	
	reg [4:0] state = RST;
	assign status = state;
	reg [4:0] return_state;
	reg sclk_sig = 0;
	reg [55:0] cmd_out;
	reg [7:0] recv_data;
	reg cmd_mode = 1;
	reg [7:0] data_sig = 8'hFF;
	
	reg [9:0] byte_counter;
	reg [9:0] bit_counter;
	
	reg [26:0] boot_counter = 27'd100_000_000;
	always @(posedge clk) begin
		if(reset == 1) begin
			state <= RST;
			sclk_sig <= 0;
			boot_counter <= 27'd100_000_000;
		end
		else begin
			case(state)
				RST: begin
					if(boot_counter == 0) begin
						sclk_sig <= 0;
						cmd_out <= {56{1'b1}};
						byte_counter <= 0;
						byte_available <= 0;
						ready_for_next_byte <= 0;
						cmd_mode <= 1;
						bit_counter <= 160;
						cs <= 1;
						state <= INIT;
					end
					else begin
						boot_counter <= boot_counter - 1;
					end
				end
				INIT: begin
					if(bit_counter == 0) begin
						cs <= 0;
						state <= CMD0;
					end
					else begin
						bit_counter <= bit_counter - 1;
						sclk_sig <= ~sclk_sig;
					end
				end
				CMD0: begin
					cmd_out <= 56'hFF_40_00_00_00_00_95;
					bit_counter <= 55;
					return_state <= CMD55;
					state <= SEND_CMD;
				end
				CMD55: begin
					cmd_out <= 56'hFF_77_00_00_00_00_01;
					bit_counter <= 55;
					return_state <= CMD41;
					state <= SEND_CMD;
				end
				CMD41: begin
					cmd_out <= 56'hFF_69_00_00_00_00_01;
					bit_counter <= 55;
					return_state <= POLL_CMD;
					state <= SEND_CMD;
				end
				POLL_CMD: begin
					if(recv_data[0] == 0) begin
						state <= IDLE;
					end
					else begin
						state <= CMD55;
					end
				end
				IDLE: begin
					if(rd == 1) begin
						state <= READ_BLOCK;
					end
					else if(wr == 1) begin
						state <= WRITE_BLOCK_CMD;
					end
					else begin
						state <= IDLE;
					end
				end
				READ_BLOCK: begin
					cmd_out <= {16'hFF_51, address, 8'hFF};
					bit_counter <= 55;
					return_state <= READ_BLOCK_WAIT;
					state <= SEND_CMD;
				end
				READ_BLOCK_WAIT: begin
					if(sclk_sig == 1 && miso == 0) begin
						byte_counter <= 511;
						bit_counter <= 7;
						return_state <= READ_BLOCK_DATA;
						state <= RECEIVE_BYTE;
					end
					sclk_sig <= ~sclk_sig;
				end
				READ_BLOCK_DATA: begin
					dout <= recv_data;
					byte_available <= 1;
					if (byte_counter == 0) begin
						bit_counter <= 7;
						return_state <= READ_BLOCK_CRC;
						state <= RECEIVE_BYTE;
					end
					else begin
						byte_counter <= byte_counter - 1;
						return_state <= READ_BLOCK_DATA;
						bit_counter <= 7;
						state <= RECEIVE_BYTE;
					end
				end
				READ_BLOCK_CRC: begin
					bit_counter <= 7;
					return_state <= IDLE;
					state <= RECEIVE_BYTE;
				end
				SEND_CMD: begin
					if (sclk_sig == 1) begin
						if (bit_counter == 0) begin
							state <= RECEIVE_BYTE_WAIT;
						end
						else begin
							bit_counter <= bit_counter - 1;
							cmd_out <= {cmd_out[54:0], 1'b1};
						end
					end
					sclk_sig <= ~sclk_sig;
				end
				RECEIVE_BYTE_WAIT: begin
					if (sclk_sig == 1) begin
						if (miso == 0) begin
							recv_data <= 0;
							bit_counter <= 6;
							state <= RECEIVE_BYTE;
						end
					end
					sclk_sig <= ~sclk_sig;
				end
				RECEIVE_BYTE: begin
					byte_available <= 0;
					if (sclk_sig == 1) begin
						recv_data <= {recv_data[6:0], miso};
						if (bit_counter == 0) begin
							state <= return_state;
						end
						else begin
							bit_counter <= bit_counter - 1;
						end
					end
					sclk_sig <= ~sclk_sig;
				end
				WRITE_BLOCK_CMD: begin
					cmd_out <= {16'hFF_58, address, 8'hFF};
					bit_counter <= 55;
					return_state <= WRITE_BLOCK_INIT;
					state <= SEND_CMD;
			ready_for_next_byte <= 1;
				end
				WRITE_BLOCK_INIT: begin
					cmd_mode <= 0;
					byte_counter <= WRITE_DATA_SIZE; 
					state <= WRITE_BLOCK_DATA;
					ready_for_next_byte <= 0;
				end
				WRITE_BLOCK_DATA: begin
					if (byte_counter == 0) begin
						state <= RECEIVE_BYTE_WAIT;
						return_state <= WRITE_BLOCK_WAIT;
					end
					else begin
						if ((byte_counter == 2) || (byte_counter == 1)) begin
							data_sig <= 8'hFF;
						end
						else if (byte_counter == WRITE_DATA_SIZE) begin
							data_sig <= 8'hFE;
						end
						else begin
							data_sig <= din;
							ready_for_next_byte <= 1;
						end
						bit_counter <= 7;
						state <= WRITE_BLOCK_BYTE;
						byte_counter <= byte_counter - 1;
					end
				end
				WRITE_BLOCK_BYTE: begin
					if (sclk_sig == 1) begin
						if (bit_counter == 0) begin
							state <= WRITE_BLOCK_DATA;
							ready_for_next_byte <= 0;
						end
						else begin
							data_sig <= {data_sig[6:0], 1'b1};
							bit_counter <= bit_counter - 1;
						end;
					end;
					sclk_sig <= ~sclk_sig;
				end
				WRITE_BLOCK_WAIT: begin
					if (sclk_sig == 1) begin
						if (miso == 1) begin
							state <= IDLE;
							cmd_mode <= 1;
						end
					end
					sclk_sig = ~sclk_sig;
				end
			endcase
		end
	end

	assign sclk = sclk_sig;
	assign mosi = cmd_mode ? cmd_out[55] : data_sig[7];
	assign ready = (state == IDLE);
endmodule

module clock_divider(input clk_in, output reg clk_out = 0);
	always @(posedge clk_in) begin
		clk_out <= ~clk_out;
	end
endmodule