////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2013-2023 Efinix Inc. All rights reserved.              
//
// This   document  contains  proprietary information  which   is        
// protected by  copyright. All rights  are reserved.  This notice       
// refers to original work by Efinix, Inc. which may be derivitive       
// of other work distributed under license of the authors.  In the       
// case of derivative work, nothing in this notice overrides the         
// original author's license agreement.  Where applicable, the           
// original license agreement is included in it's original               
// unmodified form immediately below this header.                        
//                                                                       
// WARRANTY DISCLAIMER.                                                  
//     THE  DESIGN, CODE, OR INFORMATION ARE PROVIDED “AS IS” AND        
//     EFINIX MAKES NO WARRANTIES, EXPRESS OR IMPLIED WITH               
//     RESPECT THERETO, AND EXPRESSLY DISCLAIMS ANY IMPLIED WARRANTIES,  
//     INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF          
//     MERCHANTABILITY, NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR    
//     PURPOSE.  SOME STATES DO NOT ALLOW EXCLUSIONS OF AN IMPLIED       
//     WARRANTY, SO THIS DISCLAIMER MAY NOT APPLY TO LICENSEE.           
//                                                                       
// LIMITATION OF LIABILITY.                                              
//     NOTWITHSTANDING ANYTHING TO THE CONTRARY, EXCEPT FOR BODILY       
//     INJURY, EFINIX SHALL NOT BE LIABLE WITH RESPECT TO ANY SUBJECT    
//     MATTER OF THIS AGREEMENT UNDER TORT, CONTRACT, STRICT LIABILITY   
//     OR ANY OTHER LEGAL OR EQUITABLE THEORY (I) FOR ANY INDIRECT,      
//     SPECIAL, INCIDENTAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES OF ANY    
//     CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF      
//     GOODWILL, DATA OR PROFIT, WORK STOPPAGE, OR COMPUTER FAILURE OR   
//     MALFUNCTION, OR IN ANY EVENT (II) FOR ANY AMOUNT IN EXCESS, IN    
//     THE AGGREGATE, OF THE FEE PAID BY LICENSEE TO EFINIX HEREUNDER    
//     (OR, IF THE FEE HAS BEEN WAIVED, $100), EVEN IF EFINIX SHALL HAVE 
//     BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES.  SOME STATES DO 
//     NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR            
//     CONSEQUENTIAL DAMAGES, SO THIS LIMITATION AND EXCLUSION MAY NOT   
//     APPLY TO LICENSEE.                                                
//
////////////////////////////////////////////////////////////////////////////////

//FIXME: tvalid shouldn't depend on tready
module ethernet_ctrl #(
    parameter   ADDR_WIDTH  = 12,
    parameter   DATA_WIDTH  = 32,
    parameter   NUM_REG     = 11,
    parameter   NUM_FRAME   = 10,
    parameter   S_ETH_RX_CLK_FREQ_MHZ = 100,
    parameter   COALESCE_US = 4000
) (
    //APB3 Slave Interface
    input                           s_apb3_clk,
    input                           s_apb3_rstn,
    input         [ADDR_WIDTH-1:0]  s_apb3_paddr,
    input                           s_apb3_psel,
    input                           s_apb3_penable,
    input                           s_apb3_pwrite,//0:rd; 1:wr;
    input         [DATA_WIDTH-1:0]  s_apb3_pwdata,
    output                          s_apb3_pready,
    output        [DATA_WIDTH-1:0]  s_apb3_prdata,
    output                          s_apb3_pslverror,

    //TODO: generate different reset signals for different clock domains
    input                           eth_mac_rst,
    input                           s_eth_tx_clk,
    input                           s_eth_tx_tvalid,
    output                          s_eth_tx_tready,
    input    [7:0]                  s_eth_tx_tdata,
    input    [0:0]                  s_eth_tx_tkeep,
    input    [3:0]                  s_eth_tx_tdest,
    input                           s_eth_tx_tlast,
    output                          m_eth_tx_clk,
    output                          m_eth_tx_tvalid,
    input                           m_eth_tx_tready,
    output    [7:0]                 m_eth_tx_tdata,
    output    [3:0]                 m_eth_tx_tdest,
    output                          m_eth_tx_tlast,


    output                          mac_sw_rst,
    output                          phy_sw_rst,
    output                          dma_rx_rst,
    output                          dma_tx_rst,
    input                           dma_descriptor_update,
    output reg                      error
);


///////////////////////////////////////////////////////////////////////////////

localparam APB3_SLV_MAC_SW_RST       = 0;    //RW
localparam APB3_SLV_PHY_SW_RST       = 1;    //RW
localparam APB3_SLV_DMA_RX_RESET     = 2;    //RW
localparam APB3_SLV_DMA_TX_RESET     = 3;    //RW

localparam [1:0]    IDLE   = 2'b00;
localparam [1:0]    SETUP  = 2'b01;
localparam [1:0]    ACCESS = 2'b10;

localparam DATA_DEPTH = NUM_FRAME*1540;
localparam SIZE_DEPTH = NUM_FRAME*5;
localparam DATA_DEPTH_WID = $clog2(DATA_DEPTH);
localparam SIZE_DEPTH_WID = $clog2(SIZE_DEPTH);

localparam COALESCE_CNT = (COALESCE_US*S_ETH_RX_CLK_FREQ_MHZ);


reg [DATA_WIDTH-1:0]    slaveReg [0:NUM_REG-1];
reg [DATA_WIDTH-1:0]    slaveRegOut = 0;
reg [1:0]               busState = 0;
reg [1:0]               busState_last = 0;
reg [1:0]               busNext = 0;
reg                     slaveReady = 0;
wire                    actWrite;
wire                    actRead;
genvar                  byteIndex;
(* async_reg = "true" *) reg [1:0] intr_en_axis = 0;
(* async_reg = "true" *) reg [1:0] intr_clr_axis = 0;
(* async_reg = "true" *) reg [1:0] start_send_axis = 0;
(* async_reg = "true" *) reg [1:0] start_recv_axis = 0;
(* async_reg = "true" *) reg [10:0] read_count_axis_r1 = 0;
(* async_reg = "true" *) reg [10:0] read_count_axis_r2 = 0;
reg  r_size_empty = 0;
reg  r_size_rd_en = 0;

assign actWrite = s_apb3_pwrite  & (busState == ACCESS);
assign actRead  = !s_apb3_pwrite & (busState == ACCESS);
assign s_apb3_pslverror = 1'b0;
assign s_apb3_prdata = slaveRegOut;
assign s_apb3_pready = slaveReady & (busState !== IDLE);

assign mac_sw_rst = slaveReg[APB3_SLV_MAC_SW_RST];
assign phy_sw_rst = slaveReg[APB3_SLV_PHY_SW_RST];
assign dma_rx_rst = slaveReg[APB3_SLV_DMA_RX_RESET];
assign dma_tx_rst = slaveReg[APB3_SLV_DMA_TX_RESET];

always@(posedge s_apb3_clk or negedge s_apb3_rstn) begin
    if(!s_apb3_rstn) begin
        busState <= IDLE;
        busState_last <= IDLE;
    end else begin
        busState <= busNext;
        busState_last <= busState;
    end
end

always @(*) begin
    busNext = busState;

    case(busState)
        IDLE: begin
            if(s_apb3_psel && !s_apb3_penable)
                busNext = SETUP;
            else
                busNext = IDLE;
        end

        SETUP: begin
            if(s_apb3_psel && s_apb3_penable)
                busNext = ACCESS;
            else
                busNext = IDLE;
        end

        ACCESS: begin
            if(s_apb3_pready)
                busNext = IDLE;
            else
                busNext = ACCESS;
        end

        default: begin
            busNext = IDLE;
        end
    endcase
end

always @(posedge s_apb3_clk) begin
    slaveReady <= actWrite | actRead;
end

generate
    for(byteIndex = 0; byteIndex < NUM_REG; byteIndex = byteIndex + 1) begin
        always @(posedge s_apb3_clk or negedge s_apb3_rstn) begin
            if (!s_apb3_rstn)
                slaveReg[byteIndex] <= {DATA_WIDTH{1'b0}};
            else begin
                if(actWrite && (s_apb3_paddr[ADDR_WIDTH-1:2] == byteIndex))
                    slaveReg[byteIndex] <= s_apb3_pwdata;
                else
                    slaveReg[byteIndex] <= slaveReg[byteIndex];
            end
        end
    end
endgenerate

always@ (posedge s_apb3_clk or negedge s_apb3_rstn) begin
    if(!s_apb3_rstn)
        slaveRegOut <= {DATA_WIDTH{1'b0}};
    else begin
        r_size_rd_en <= 0;
        if(actRead) begin
            case(s_apb3_paddr[6:2])
            APB3_SLV_MAC_SW_RST : slaveRegOut <= slaveReg[APB3_SLV_MAC_SW_RST];
            APB3_SLV_PHY_SW_RST : slaveRegOut <= slaveReg[APB3_SLV_PHY_SW_RST];
            APB3_SLV_DMA_RX_RESET : slaveRegOut <= slaveReg[APB3_SLV_DMA_RX_RESET];
            APB3_SLV_DMA_TX_RESET : slaveRegOut <= slaveReg[APB3_SLV_DMA_TX_RESET];
            default : slaveRegOut <= 0;
            endcase
        end else begin
            slaveRegOut <= slaveRegOut;
        end
    end
end

///////////////////////////////////////////////////////////////////////////
////////////////////////    MAC TX Region   ///////////////////////////////
///////////////////////////////////////////////////////////////////////////
assign m_eth_tx_clk = s_eth_tx_clk;
wire w_eth_mac_rst;

reset #(
    .IN_RST_ACTIVE("HIGH"),
    .OUT_RST_ACTIVE("HIGH"),
    .CYCLE(2)
) inst_reset (
    .i_arst(eth_mac_rst),
    .i_clk(s_eth_tx_clk),
    .o_srst(w_eth_mac_rst)
);

wire [3:0] w_trans_rdcnt;
wire w_trans_rst_busy;
wire w_trans_full;
wire w_trans_empty;
wire w_tx_full;
wire w_tx_empty;
wire w_tx_busy;
wire [11:0] w_txdata_rd_datacount;
wire w_rd_en;
wire        w_eth_tx_tlast;
wire        w_eth_tx_tkeep;
wire [3:0]  w_eth_tx_tdest;
wire [7:0]  w_eth_tx_tdata;
reg         r_tx_empty = 0;
reg         r_eth_tx_tlast = 0;
reg         r_eth_tx_tkeep = 0;
reg  [3:0]  r_eth_tx_tdest = 0;
reg  [7:0]  r_eth_tx_tdata = 0;
assign s_eth_tx_tready = !w_tx_full;
assign w_rd_en = !w_tx_empty && m_eth_tx_tready && !w_trans_empty;
assign m_eth_tx_tvalid = w_trans_empty ? 0 : !r_tx_empty && r_eth_tx_tkeep;
assign m_eth_tx_tdata = r_eth_tx_tdata;
assign m_eth_tx_tdest = r_eth_tx_tdest;
assign m_eth_tx_tlast = !w_tx_empty && !w_eth_tx_tkeep && w_eth_tx_tlast ? 1 : r_eth_tx_tlast;

fwft_fifo_1 u_fwft_tx_fifo_trans (
    .a_rst_i    (w_eth_mac_rst),
    .wr_clk_i   (s_eth_tx_clk),
    .wr_en_i    (s_eth_tx_tvalid && s_eth_tx_tready && (s_eth_tx_tkeep || s_eth_tx_tlast)),
    .wdata      ({s_eth_tx_tlast, s_eth_tx_tkeep,s_eth_tx_tdest, s_eth_tx_tdata}),
    .rd_clk_i   (m_eth_tx_clk),
    .rd_en_i    (w_rd_en),
    .rdata      ({w_eth_tx_tlast, w_eth_tx_tkeep, w_eth_tx_tdest, w_eth_tx_tdata}),
    .full_o     (w_tx_full),
    .empty_o    (w_tx_empty),
    .wr_datacount_o(),
    .rd_datacount_o(w_txdata_rd_datacount),
    .rst_busy(w_tx_size_busy)
);

always @(posedge s_eth_tx_clk or posedge w_eth_mac_rst) begin
    if (w_eth_mac_rst) begin
        r_tx_empty <= 0;
        r_eth_tx_tlast <= 0;
        r_eth_tx_tkeep <= 0;
        r_eth_tx_tdest <= 0;
        r_eth_tx_tdata <= 0;
    end else begin
        r_tx_empty <= w_tx_empty;
        if (w_rd_en) begin
            r_eth_tx_tlast <= w_eth_tx_tlast;
            r_eth_tx_tkeep <= w_eth_tx_tkeep;
            r_eth_tx_tdest <= w_eth_tx_tdest;
            r_eth_tx_tdata <= w_eth_tx_tdata;
        end else if (w_trans_empty) begin
            r_eth_tx_tlast <= 0;
            r_eth_tx_tkeep <= 0;
            r_eth_tx_tdest <= 0;
            r_eth_tx_tdata <= 0;
        end
    end
end

fwft_fifo_2 u_fwft_tx_fifo_data (
    .a_rst_i    (w_eth_mac_rst),
    .wr_clk_i   (s_eth_tx_clk),
    .wr_en_i    (s_eth_tx_tvalid && s_eth_tx_tready && s_eth_tx_tlast),
    .wdata      (1),
    .rd_clk_i   (m_eth_tx_clk),
    .rd_en_i    (m_eth_tx_tvalid && m_eth_tx_tready && m_eth_tx_tlast),
    .rdata      (),
    .full_o     (w_trans_full),
    .empty_o    (w_trans_empty),
    .wr_datacount_o(),
    .rd_datacount_o(w_trans_rdcnt),
    .rst_busy(w_trans_rst_busy)
);

always @(posedge s_eth_tx_clk or posedge w_eth_mac_rst) begin
    if (w_eth_mac_rst) begin
        error <= 0;
    end else begin
        if (w_trans_full || w_trans_rst_busy)
            error <= 1;
    end
end

endmodule
