//////////////////////////////////////////////////////////////////////////////////
//
// This file is part of the Next186 Soc PC project
// http://opencores.org/project,next186
//
// Filename: usb_phy.v
// Description: Part of the Next186 SoC PC project, USB host physical layer - low speed
// Version 1.0
// Creation date: 15:53:59 02/21/2019 
//
// Author: Nicolae Dumitrache 
// e-mail: ndumitrache@opencores.org
//
/////////////////////////////////////////////////////////////////////////////////
// 
// Copyright (C) 2019 Nicolae Dumitrache
// 
// This source file may be used and distributed without 
// restriction provided that this copyright statement is not 
// removed from the file and that any derivative work contains 
// the original copyright notice and the associated disclaimer.
// 
// This source file is free software; you can redistribute it 
// and/or modify it under the terms of the GNU Lesser General 
// Public License as published by the Free Software Foundation;
// either version 2.1 of the License, or (at your option) any 
// later version. 
// 
// This source is distributed in the hope that it will be 
// useful, but WITHOUT ANY WARRANTY; without even the implied 
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
// PURPOSE. See the GNU Lesser General Public License for more 
// details. 
// 
// You should have received a copy of the GNU Lesser General 
// Public License along with this source; if not, download it 
// from http://www.opencores.org/lgpl.shtml 
// 
///////////////////////////////////////////////////////////////////////////////////
// Additional Comments: 
// Low speed USB interface - hot plug, hot sawp
//////////////////////////////////////////////////////////////////////////////////
module USB_phy(
		input CLK48Mhz,		// USB clock	
		
		input clk, // CPU clock
		input com, // command select
		input wr,  // CPU read	
		input rd,  // CPU write
		input [7:0]din,		// CPU write data or command (000xxxxx=SE0, 001xxxxx=IDLE, 010xxxxx = K, 011xxxxx = connect, 1xxxxxxx=send bytes from queue until empty)
		
		output [7:0]dout, // CPU read data and status = xxxxdcba: a=send queue full, b=recv queue empty, c=busy, d=connected
		
		inout DM,
		inout DP
	);
	
	reg [4:0]div = 0;
	reg [4:0]sample = 0;
	wire [7:0]fi_data;
	wire [7:0]fo_data;
	wire [7:0]cmd;
	wire cmdempty;
	wire sfull;
	wire cmdbusy;
	reg rempty = 1'b1;
	reg sempty = 1'b1;
	reg [2:0]STATE;
	reg [2:0]rSTATE = 0;
	reg cmdread;
	reg dataread;
	reg [2:0]rZMP = 3'b100;
	reg [2:0]ZMP;
	reg connected = 1'b0;
	reg [3:0]mpmp = 0;  	// fast sampled input data
	reg [7:0]dcnt = 0;  	// disconnect/connect counter (SE0 or J for >= 2.5uS)
	reg [7:0]recshift = 0;	// receive shift register
	reg [8:0]sendshift = 9'b100000000;	// send shift register
	reg [2:0]reccount = 0;	// receive bit counter
	reg rdbyte = 1'b0;	// byte read ok
	reg receive = 0;		// receive in progress
	reg smp = 1'b0;   		// sampled input data
	reg [2:0]rstuff = 0;	// receive bit stuff counter
	reg [2:0]sstuff = 0;	// send bit stuff counter
	reg [2:0]rrZMP;			// extra output stage to be placed in IO registers
	wire endsend = (sendshift[8:1] == 8'b00000001) && (sstuff != 3'b110);	// end of the sent byte
	wire rstb = sample == div; // receive bit strobe (half bit)
	wire wstb = &div; // send bit strobe (start bit)
	
	assign dout = com ? {4'b0000, connected, cmdbusy, rempty, sfull} : fi_data;
	assign {DM, DP} = rrZMP[2] ? 2'bzz : rrZMP[1:0];
	
	wire _sempty;
	fifo_usb_io fifo_o
	(
		.Data(din),
		.WrClock(clk),
		.RdClock(CLK48Mhz),
		.WrEn(wr && !com),
		.RdEn(!_sempty && (dataread || sempty)),
		.Reset(1'b0),
		.RPReset(1'b0),
		.Q(fo_data),
		.Empty(_sempty),
		.Full(sfull)
	);

	always @(posedge CLK48Mhz) begin
		if(dataread || sempty) sempty <= _sempty;
	end
	
	wire _rempty;
	fifo_usb_io fifo_i
	(
		.Data(recshift),
		.WrClock(CLK48Mhz),
		.RdClock(clk),
		.WrEn(rdbyte),
		.RdEn(((rd && !com) || rempty) && !_rempty),
		.Reset(1'b0),
		.RPReset(1'b0),
		.Q(fi_data),
		.Empty(_rempty),
		.Full()
	);

	always @(posedge clk) begin
		if((rd && !com) || rempty) rempty <= _rempty;
	end


	q1 fifo_cmd
	(
		.wr_clk(clk),
		.wr_en(wr && com),
		.din(din),
		.full(cmdbusy),
		
		.rd_clk(CLK48Mhz),
		.rd_en(cmdread),
		.dout(cmd),
		.empty(cmdempty)
	);


	always @(*) begin
		cmdread = 1'b0;
		dataread = 1'b0;
		ZMP = rZMP;
		STATE = rSTATE;
		
		case(rSTATE)
			0: begin	// disconnected
				if(!cmdempty && cmd[7:5] == 3'b011) STATE = 1; // acknowledge disconnected
				cmdread = 1'b1;
			end
			1: if(dcnt[7]) STATE = 2; // low speed device plugged
			2: begin
				if(!cmdempty)
					if(!cmd[7]) begin // SE0, IDLE, K, connect(=idle)
						cmdread = 1'b1;
						ZMP = {cmd[5], cmd[5], cmd[5] ^ cmd[6]};
					end else begin	// send
						ZMP = 3'b010;	// J
						STATE = sempty ? 5 : 3;
					end
			   end
			3: begin
				dataread = 1;
				STATE = 4;
			end
			4: if(wstb && endsend) begin
				 dataread = 1;
				 if(sempty) STATE = 5;
			end
			5:if(wstb && sendshift[5]) begin
				cmdread = 1;
				STATE = 2;
			end
			
		endcase
	end


	always @(posedge CLK48Mhz) begin
		div <= div + 1'b1;
		if(!ZMP[2]) rZMP[1:0] <= ZMP[1:0];
		rZMP[2] <= ZMP[2];
		rSTATE <= STATE;
		connected <= rSTATE > 1;
		mpmp <= {mpmp[1:0], DM, DP};
		rrZMP <= rZMP;
		rdbyte <= &reccount && receive && rstb && (rstuff != 3'b110);
		
		if(connected && rZMP[2]) begin
			if(~|mpmp) receive <= 1'b0;
			else if(mpmp[3] ^ mpmp[1]) begin
				sample <= {!div[4], div[3:0]};
				receive <= 1'b1;
			end 
		end else receive <= 1'b0;
		
		if(!receive) begin
			reccount <= 3'b000;
			rstuff <= 3'b000;
			smp <= mpmp[3];
		end else if(rstb) begin
			smp <= mpmp[1];
				
			if(smp ^ mpmp[1]) rstuff <= 3'b000;
			else begin
				rstuff <= rstuff + 1'b1;
				if(rstuff == 3'b110) receive <= 1'b0;	// bit stuff error
			end
			
			if(rstuff != 3'b110) begin
				recshift <= {!(smp ^ mpmp[1]), recshift[7:1]};
				reccount <= reccount + 1'b1;
			end
		end
		
		case(rSTATE)
			2: sendshift <= 9'b1000xxxxx;
			3: begin
				sendshift <= {1'b1, fo_data};
				sstuff <= 0;
			end
			4: if(wstb) begin
					if(!sendshift[0] || sstuff == 3'b110) begin 
						rZMP[1:0] <= ~rZMP[1:0];
						sstuff <= 3'b000;
					end else sstuff <= sstuff + 1'b1;
					if(sstuff != 3'b110) sendshift <= sendshift >> 1;

					if(endsend) sendshift <= {1'b1, sempty ? 8'b000xxxxx : fo_data};
			end
			5: if(wstb) begin
				sendshift <= sendshift >> 1;
				rZMP <= {sendshift[5], |sendshift[6:5], 1'b0}; // EOP
			end
		endcase
		
		
		if(rSTATE > 1) begin // connect/disconnect logic
			if(!connected || !rZMP[2] || mpmp[1:0] != 2'b00) dcnt <= 0;
			else begin
				dcnt <= dcnt + !dcnt[7];
				if(dcnt[7]) rSTATE <= 0; // go to disconnected
			end
		end else begin
			if(connected || !mpmp[1]) dcnt <= 0;
			else dcnt <= dcnt + !dcnt[7];
		end
	end
	
	
endmodule
