//-----------------------------------------------------------------
//                       USB Serial Port
//                            V0.1
//                     Ultra-Embedded.com
//                       Copyright 2020
//
//                 Email: admin@ultra-embedded.com
//
//                         License: LGPL
//-----------------------------------------------------------------
//
// 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, write to the 
// Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
// Boston, MA  02111-1307  USA
//-----------------------------------------------------------------

//-----------------------------------------------------------------
//                          Generated File
//-----------------------------------------------------------------

module ulpi_wrapper
(
    // Inputs
     input           ulpi_clk60_i
    ,input           ulpi_rst_i
    ,input  [  7:0]  ulpi_data_out_i
    ,input           ulpi_dir_i
    ,input           ulpi_nxt_i
    ,input  [  7:0]  utmi_data_out_i
    ,input           utmi_txvalid_i
    ,input  [  1:0]  utmi_op_mode_i
    ,input  [  1:0]  utmi_xcvrselect_i
    ,input           utmi_termselect_i
    ,input           utmi_dppulldown_i
    ,input           utmi_dmpulldown_i

    // Outputs
    ,output [  7:0]  ulpi_data_in_o
    ,output          ulpi_stp_o
    ,output [  7:0]  utmi_data_in_o
    ,output          utmi_txready_o
    ,output          utmi_rxvalid_o
    ,output          utmi_rxactive_o
    ,output          utmi_rxerror_o
    ,output [  1:0]  utmi_linestate_o
);



//-----------------------------------------------------------------
// Module: UTMI+ to ULPI Wrapper
//
// Description:
//   - Converts from UTMI interface to reduced pin count ULPI.
//   - No support for low power mode.
//   - I/O synchronous to 60MHz ULPI clock input (from PHY)
//   - Tested against SMSC/Microchip USB3300 in device mode.
//-----------------------------------------------------------------

//-----------------------------------------------------------------
// States
//-----------------------------------------------------------------
localparam STATE_W          = 2;
localparam STATE_IDLE       = 2'd0;
localparam STATE_CMD        = 2'd1;
localparam STATE_DATA       = 2'd2;
localparam STATE_REG        = 2'd3;

reg [STATE_W-1:0]   state_q;

//-----------------------------------------------------------------
// Local Params
//-----------------------------------------------------------------
localparam REG_FUNC_CTRL = 8'h84;
localparam REG_OTG_CTRL  = 8'h8a;
localparam REG_TRANSMIT  = 8'h40;
localparam REG_WRITE     = 8'h80;
localparam REG_READ      = 8'hC0;

//-----------------------------------------------------------------
// UTMI Mode Select
//-----------------------------------------------------------------
reg         mode_update_q;
reg [1:0]   xcvrselect_q;
reg         termselect_q;
reg [1:0]   opmode_q;
reg         phy_reset_q;
reg         mode_write_q;

// Detect register write completion
wire mode_complete_w = (state_q == STATE_REG &&
                        mode_write_q         && 
                        ulpi_nxt_i           && 
                        !ulpi_dir_i);           // Not interrupted by a Rx

always @ (posedge ulpi_clk60_i or posedge ulpi_rst_i)
if (ulpi_rst_i)
begin
    mode_update_q   <= 1'b0;
    xcvrselect_q    <= 2'b0;
    termselect_q    <= 1'b0;
    opmode_q        <= 2'b11;
    phy_reset_q     <= 1'b1;
end
else
begin
    xcvrselect_q    <= utmi_xcvrselect_i;
    termselect_q    <= utmi_termselect_i;
    opmode_q        <= utmi_op_mode_i;

    if (mode_update_q && mode_complete_w)
    begin
        mode_update_q <= 1'b0;
        phy_reset_q   <= 1'b0;
    end
    else if (opmode_q     != utmi_op_mode_i     ||
             termselect_q != utmi_termselect_i ||
             xcvrselect_q != utmi_xcvrselect_i)
        mode_update_q <= 1'b1;
end

//-----------------------------------------------------------------
// UTMI OTG Control
//-----------------------------------------------------------------
reg otg_update_q;
reg dppulldown_q;
reg dmpulldown_q;
reg otg_write_q;

// Detect register write completion
wire otg_complete_w  = (state_q == STATE_REG &&
                        otg_write_q         && 
                        ulpi_nxt_i           && 
                        !ulpi_dir_i);           // Not interrupted by a Rx

always @ (posedge ulpi_clk60_i or posedge ulpi_rst_i)
if (ulpi_rst_i)
begin
    otg_update_q    <= 1'b0;
    dppulldown_q    <= 1'b1;
    dmpulldown_q    <= 1'b1;
end
else
begin
    dppulldown_q    <= utmi_dppulldown_i;
    dmpulldown_q    <= utmi_dmpulldown_i;

    if (otg_update_q && otg_complete_w)
        otg_update_q <= 1'b0;
    else if (dppulldown_q != utmi_dppulldown_i ||
             dmpulldown_q != utmi_dmpulldown_i)
        otg_update_q <= 1'b1;
end

//-----------------------------------------------------------------
// Bus turnaround detect
//-----------------------------------------------------------------
reg ulpi_dir_q;

always @ (posedge ulpi_clk60_i or posedge ulpi_rst_i)
if (ulpi_rst_i)
    ulpi_dir_q <= 1'b0;
else
    ulpi_dir_q <= ulpi_dir_i;

wire turnaround_w = ulpi_dir_q ^ ulpi_dir_i;

//-----------------------------------------------------------------
// Rx - Tx delay
//-----------------------------------------------------------------
localparam TX_DELAY_W       = 3;
localparam TX_START_DELAY   = 3'd7;

reg [TX_DELAY_W-1:0] tx_delay_q;

always @ (posedge ulpi_clk60_i or posedge ulpi_rst_i)
if (ulpi_rst_i)
    tx_delay_q <= {TX_DELAY_W{1'b0}};
else if (utmi_rxactive_o)
    tx_delay_q <= TX_START_DELAY;
else if (tx_delay_q != {TX_DELAY_W{1'b0}})
    tx_delay_q <= tx_delay_q - 1;

wire tx_delay_complete_w = (tx_delay_q == {TX_DELAY_W{1'b0}});

//-----------------------------------------------------------------
// Tx Buffer - decouple UTMI Tx from PHY I/O
//-----------------------------------------------------------------
reg [7:0] tx_buffer_q[0:1];
reg       tx_valid_q[0:1];
reg       tx_wr_idx_q;
reg       tx_rd_idx_q;

wire      utmi_tx_ready_w;
wire      utmi_tx_accept_w;

always @ (posedge ulpi_clk60_i or posedge ulpi_rst_i)
if (ulpi_rst_i)
begin
    tx_buffer_q[0] <= 8'b0;
    tx_buffer_q[1] <= 8'b0;
    tx_valid_q[0]  <= 1'b0;
    tx_valid_q[1]  <= 1'b0;
    tx_wr_idx_q    <= 1'b0;
    tx_rd_idx_q    <= 1'b0;
end    
else
begin
    // Push
    if (utmi_txvalid_i && utmi_txready_o)
    begin
        tx_buffer_q[tx_wr_idx_q] <= utmi_data_out_i;
        tx_valid_q[tx_wr_idx_q]  <= 1'b1;

        tx_wr_idx_q <= tx_wr_idx_q + 1'b1;
    end

    // Pop
    if (utmi_tx_ready_w && utmi_tx_accept_w)
    begin
        tx_valid_q[tx_rd_idx_q]  <= 1'b0;
        tx_rd_idx_q <= tx_rd_idx_q + 1'b1;
    end
end

// Tx buffer space (only accept after Rx->Tx turnaround delay)
assign utmi_txready_o  = ~tx_valid_q[tx_wr_idx_q] & tx_delay_complete_w;

assign utmi_tx_ready_w = tx_valid_q[tx_rd_idx_q];

wire [7:0] utmi_tx_data_w = tx_buffer_q[tx_rd_idx_q];

//-----------------------------------------------------------------
// Implementation
//-----------------------------------------------------------------

// Xilinx placement pragmas:
//synthesis attribute IOB of ulpi_data_q is "TRUE"
//synthesis attribute IOB of ulpi_stp_q is "TRUE"

reg [7:0]           ulpi_data_q;
reg                 ulpi_stp_q;
reg [7:0]           data_q;

reg                 utmi_rxvalid_q;
reg                 utmi_rxerror_q;
reg                 utmi_rxactive_q;
reg [1:0]           utmi_linestate_q;
reg [7:0]           utmi_data_q;

always @ (posedge ulpi_clk60_i or posedge ulpi_rst_i)
if (ulpi_rst_i)
begin
    state_q             <= STATE_IDLE;
    ulpi_data_q         <= 8'b0;
    data_q              <= 8'b0;
    ulpi_stp_q          <= 1'b1;

    utmi_rxvalid_q      <= 1'b0;
    utmi_rxerror_q      <= 1'b0;
    utmi_rxactive_q     <= 1'b0;
    utmi_linestate_q    <= 2'b0;
    utmi_data_q         <= 8'b0;

    mode_write_q        <= 1'b0;
    otg_write_q         <= 1'b0;
end
else
begin
    ulpi_stp_q          <= 1'b0;
    utmi_rxvalid_q      <= 1'b0;

    // Turnaround: Input + NXT - set RX_ACTIVE
    if (turnaround_w && ulpi_dir_i && ulpi_nxt_i)
    begin
        utmi_rxactive_q <= 1'b1;

        // Register write - abort
        if (state_q == STATE_REG)
        begin
            state_q       <= STATE_IDLE;
            ulpi_data_q   <= 8'b0;  // IDLE
        end
    end
    // Turnaround: Input -> Output - reset RX_ACTIVE
    else if (turnaround_w && !ulpi_dir_i)
    begin
        utmi_rxactive_q <= 1'b0;

        // Register write - abort
        if (state_q == STATE_REG)
        begin
            state_q       <= STATE_IDLE;
            ulpi_data_q   <= 8'b0;  // IDLE
        end
    end
    // Non-turnaround cycle
    else if (!turnaround_w)
    begin
        //-----------------------------------------------------------------
        // Input: RX_CMD (status)
        //-----------------------------------------------------------------
        if (ulpi_dir_i && !ulpi_nxt_i)
        begin
            // Phy status
            utmi_linestate_q <= ulpi_data_out_i[1:0];

            case (ulpi_data_out_i[5:4])
            2'b00:
            begin
                utmi_rxactive_q <= 1'b0;
                utmi_rxerror_q  <= 1'b0;
            end
            2'b01: 
            begin
                utmi_rxactive_q <= 1'b1;
                utmi_rxerror_q  <= 1'b0;
            end
            2'b11:
            begin
                utmi_rxactive_q <= 1'b1;
                utmi_rxerror_q  <= 1'b1;
            end
            default:
                ; // HOST_DISCONNECTED
            endcase
        end
        //-----------------------------------------------------------------
        // Input: RX_DATA
        //-----------------------------------------------------------------
        else if (ulpi_dir_i && ulpi_nxt_i)
        begin
            utmi_rxvalid_q  <= 1'b1;
            utmi_data_q     <= ulpi_data_out_i;
        end
        //-----------------------------------------------------------------
        // Output
        //-----------------------------------------------------------------
        else if (!ulpi_dir_i)
        begin        
            // IDLE: Pending mode update
            if ((state_q == STATE_IDLE) && mode_update_q)
            begin
                data_q        <= {1'b0, 1'b1, phy_reset_q, opmode_q, termselect_q, xcvrselect_q};
                ulpi_data_q   <= REG_FUNC_CTRL;

                otg_write_q   <= 1'b0;
                mode_write_q  <= 1'b1;

                state_q       <= STATE_CMD;
            end
            // IDLE: Pending OTG control update
            else if ((state_q == STATE_IDLE) && otg_update_q)
            begin
                data_q        <= {5'b0, dmpulldown_q, dppulldown_q, 1'b0};
                ulpi_data_q   <= REG_OTG_CTRL;

                otg_write_q   <= 1'b1;
                mode_write_q  <= 1'b0;

                state_q       <= STATE_CMD;
            end
            // IDLE: Pending transmit
            else if ((state_q == STATE_IDLE) && utmi_tx_ready_w)
            begin
                ulpi_data_q <= REG_TRANSMIT | {4'b0, utmi_tx_data_w[3:0]};
                state_q     <= STATE_DATA;
            end
            // Command
            else if ((state_q == STATE_CMD) && ulpi_nxt_i)
            begin
                // Write Register
                state_q     <= STATE_REG;
                ulpi_data_q <= data_q;
            end
            // Data (register write)
            else if (state_q == STATE_REG && ulpi_nxt_i)
            begin
                state_q       <= STATE_IDLE;
                ulpi_data_q   <= 8'b0;  // IDLE
                ulpi_stp_q    <= 1'b1;

                otg_write_q   <= 1'b0;
                mode_write_q  <= 1'b0;
            end
            // Data
            else if (state_q == STATE_DATA && ulpi_nxt_i)
            begin
                // End of packet
                if (!utmi_tx_ready_w)
                begin
                    state_q       <= STATE_IDLE;
                    ulpi_data_q   <= 8'b0;  // IDLE
                    ulpi_stp_q    <= 1'b1;
                end
                else
                begin
                    state_q        <= STATE_DATA;
                    ulpi_data_q    <= utmi_tx_data_w;
                end
            end
        end
    end
end

// Accept from buffer
assign utmi_tx_accept_w = ((state_q == STATE_IDLE) && !(mode_update_q || otg_update_q || turnaround_w) && !ulpi_dir_i) ||
                          (state_q == STATE_DATA && ulpi_nxt_i && !ulpi_dir_i);

//-----------------------------------------------------------------
// Assignments
//-----------------------------------------------------------------
// ULPI Interface
assign ulpi_data_in_o       = ulpi_data_q;
assign ulpi_stp_o           = ulpi_stp_q;

// UTMI Interface
assign utmi_linestate_o     = utmi_linestate_q;
assign utmi_data_in_o       = utmi_data_q;
assign utmi_rxerror_o       = utmi_rxerror_q;
assign utmi_rxactive_o      = utmi_rxactive_q;
assign utmi_rxvalid_o       = utmi_rxvalid_q;



endmodule
