module ft245_host_interface (
  input                           clk,
  input                           rst,

  //FT245 Interface
  input                           i_ft245_clk,
  inout     [7:0]                 io_ft245_data,
  input                           i_ft245_txe_n,
  output                          o_ft245_wr_n,
  input                           i_ft245_rde_n,
  output                          o_ft245_rd_n,
  output                          o_ft245_oe_n,
  output                          o_ft245_siwu,

  //Ingress
  output                          ingress_clk,
  input       [1:0]               ingress_rdy,
  output  reg [1:0]               ingress_act,
  output  reg                     ingress_stb,
  input       [23:0]              ingress_size,
  output  reg [`DATA_SIZE - 1: 0] ingress_data,

  //Egress
  output                          egress_clk,
  input                           egress_rdy,
  output  reg                     egress_act,
  output  reg                     egress_stb,
  input       [23:0]              egress_size,
  input       [`DATA_SIZE - 1: 0] egress_data,

  output      [31:0]              ftdi_debug
);

//local parameters
localparam      BYTE_COUNT    = `DATA_SIZE / 8;

localparam      IDLE                    = 0;
localparam      INGRESS_READ            = 1;
localparam      EGRESS_PREPARE          = 2;
localparam      EGRESS_SEND             = 3;
localparam      EGRESS_SEND_TEMP_DATA   = 4;
localparam      EGRESS_SEND_IMMEDIATELY = 5;

//registes/wires

reg   [3:0]state = IDLE;
reg   [23:0]                    count = 0;
reg   [7:0]                     byte_count = 0;


reg   [`DATA_SIZE - 1: 0]       r_ft245_ingress_data_dword;
reg   [`DATA_SIZE - 1: 0]       r_ft245_egress_data_dword;
reg   [7:0]                     r_ft245_egress_data = 8'h0;
reg   [7:0]                     r_ft245_egress_tmp_data = 8'h0;
reg                             r_ft245_read_tmp_data;

wire                            w_ft245_ingress_avail;
reg                             r_ft245_ingress_stb = 0;
reg                             r_ft245_output_enable = 0;

wire                            w_ft245_egress_avail;
reg                             r_ft245_egress_stb = 0;
reg                             r_ft245_egress_send_now = 0;


wire                            w_ingress_ready;

wire                            ft245_clk;

//submodules
//asynchronous logic
assign  ft245_clk             = i_ft245_clk;
//IBUFG ft245_clock_buf(.I(i_ft245_clk), .O(ft245_clk));
assign  ingress_clk           = ft245_clk;
assign  egress_clk            = ft245_clk;

assign  w_ft245_ingress_avail = !i_ft245_rde_n;
assign  w_ft245_egress_avail  = !i_ft245_txe_n;

assign  o_ft245_wr_n          = !r_ft245_egress_stb;
assign  o_ft245_rd_n          = !r_ft245_ingress_stb;
assign  o_ft245_oe_n          = !r_ft245_output_enable;
assign  o_ft245_siwu          = !r_ft245_egress_send_now;


assign  io_ft245_data         = r_ft245_output_enable ? 8'hZZ         : (r_ft245_read_tmp_data) ? r_ft245_egress_tmp_data: r_ft245_egress_data;


assign  w_ingress_ready       = (count < ingress_size) && !((count + 1 == ingress_size) && (byte_count == 3));

//assign  ftdi_debug            = 32'h0;

assign  ftdi_debug[3:0]       = state;
assign  ftdi_debug[5:4]       = ingress_rdy;
assign  ftdi_debug[7:6]       = ingress_act;
assign  ftdi_debug[8]         = ingress_stb;
assign  ftdi_debug[9]         = egress_rdy;
assign  ftdi_debug[10]        = egress_act;
assign  ftdi_debug[11]        = w_ingress_ready;
assign  ftdi_debug[13:12]     = ingress_data[1:0];
assign  ftdi_debug[15:14]     = egress_data[1:0];
assign  ftdi_debug[31:16]     = 16'h0;


//synchronous logic

always @ (posedge i_ft245_clk) begin
  r_ft245_egress_stb            <=  0;
  r_ft245_ingress_stb           <=  0;
  r_ft245_egress_send_now       <=  0;

  ingress_stb                   <=  0;
  egress_stb                    <=  0;

  if (rst) begin
    state                       <=  IDLE;
    count                       <=  0;
    byte_count                  <=  0;

    ingress_data                <=  0;
    r_ft245_output_enable       <=  0;
    r_ft245_egress_data         <=  0;
    r_ft245_egress_data_dword   <=  0;
    r_ft245_ingress_data_dword  <=  0;

    r_ft245_egress_tmp_data     <=  0;
    r_ft245_read_tmp_data       <=  0;

    ingress_act                 <=  0;

    egress_act                  <=  0;
  end
  else begin
    case (state)
      IDLE: begin
        count                   <=  0;
        byte_count              <=  0;
        egress_act              <=  1'b0;
        r_ft245_output_enable   <=  1'b0;
        r_ft245_read_tmp_data   <= 0;

        //If you can grab an incomming FIFO get one
        if ((ingress_rdy > 0) && (ingress_act == 2'b00)) begin
          if (ingress_rdy[0]) begin
            ingress_act[0]      <=  1;
          end
          else begin
            ingress_act[1]      <=  1;
          end
        end

        //Incomming Data and we have room for it
        if (w_ft245_ingress_avail && (ingress_act > 0)) begin
          r_ft245_output_enable   <=  1;
          state                   <=  INGRESS_READ;
        end
        //Outgoing Data
        else if (egress_rdy && !egress_act) begin    //Will the chip lock up when it wants to send and I want to send?
          egress_act              <=  1;
          state                   <=  EGRESS_PREPARE;
        end
      end
      INGRESS_READ: begin
        if (count < ingress_size) begin
          if (r_ft245_ingress_stb && w_ft245_ingress_avail) begin
            byte_count            <=  byte_count + 1;
            r_ft245_ingress_data_dword  <=  {r_ft245_ingress_data_dword[23:0], io_ft245_data};
            if (byte_count >= (BYTE_COUNT - 1)) begin
              ingress_data        <=  {r_ft245_ingress_data_dword[23:0], io_ft245_data};
              byte_count          <=  0;
              count               <=  count + 1;
              ingress_stb         <=  1;
            end
          end
          if (w_ft245_ingress_avail && !(((count + 1) == ingress_size) && (byte_count == 3))) begin
            r_ft245_ingress_stb   <=  1;
          end
        end
        else begin
          ingress_act             <=  0;
          state                   <=  IDLE;
        end

        if (!w_ft245_ingress_avail && (byte_count == 0)) begin
          ingress_act             <=  0;
          state                   <=  IDLE;
        end
      end
      EGRESS_PREPARE: begin
        r_ft245_egress_data_dword <=  egress_data;
        //count                     <=  count + 1;
        byte_count                <=  0;
        if (w_ft245_egress_avail) begin
            state                 <=  EGRESS_SEND;
            egress_stb            <=  1;
        end
      end
      EGRESS_SEND: begin
        r_ft245_read_tmp_data     <=  0;
        r_ft245_egress_data       <=  r_ft245_egress_data_dword[31:24];
        if (w_ft245_egress_avail) begin
          if (byte_count < (BYTE_COUNT - 1)) begin
            r_ft245_egress_stb        <=  1;
            r_ft245_egress_data_dword <=  {r_ft245_egress_data_dword[23:0], 8'h00};
            byte_count                <=  byte_count + 1;
          end
          else if (count < egress_size) begin
            //Last get the next piece of data
            if (count == (egress_size - 1)) begin
              egress_act              <=  0;
              state                   <=  EGRESS_SEND_IMMEDIATELY;
            end
            r_ft245_egress_data_dword <=  egress_data;
            count                     <=  count + 1;
            byte_count                <=  0;
            egress_stb                <=  1;
            r_ft245_egress_stb        <=  1;
         end
        end
        else if (!r_ft245_read_tmp_data && egress_act && r_ft245_egress_stb) begin
          r_ft245_read_tmp_data   <=  1;
          r_ft245_egress_tmp_data <=  r_ft245_egress_data;
          state                   <= EGRESS_SEND_TEMP_DATA;
        end
        if (count >= egress_size) begin
          egress_act              <=  0;
          byte_count              <=  0;
          r_ft245_egress_stb      <=  1;
          state                   <=  EGRESS_SEND_IMMEDIATELY;
        end
      end
      EGRESS_SEND_TEMP_DATA: begin
        if (w_ft245_egress_avail) begin
            r_ft245_egress_stb      <=  1;
            state                   <=  EGRESS_SEND;
        end
      end
      EGRESS_SEND_IMMEDIATELY: begin
        if (w_ft245_egress_avail) begin
          r_ft245_egress_send_now   <=  1;
        end
        state                       <=  IDLE;
      end
      default: begin
        state                       <=  IDLE;
      end
    endcase
  end
end

endmodule