/*
 This is the FX2 slave FIFO interface and the local bus master. Implements
 reading from and writing to FX2 device and local bus transactions. Current
 version supports pipelined write (with zero wait-state peripherals two bus
 clocks per write). Reading is not pipelined, thus three clocks per zero WS
 read.

 Copyright (c) 2006 Dimtel, Inc., All Rights Reserved

 $Id: fx2.v,v 1.4 2013/04/02 07:10:51 dim Exp $
 $Date: 2013/04/02 07:10:51 $
 $Author: dim $
 $Revision: 1.4 $
 $Log: fx2.v,v $
 Revision 1.4  2013/04/02 07:10:51  dim
 Added flag monitoring to address/count phases to enable multiple transactions
 in a single packet.

 Revision 1.3  2010/10/05 16:13:50  dim
 Updates prior to iGp12 release

 Revision 1.2  2009/06/04 04:36:42  dim
 Fixed a bug where a new access could start before a long wait-state write completes

 Revision 1.1  2009/06/02 20:04:33  dim
 Added IOB constraints to simplify timing

 Revision 1.2  2006/11/16 21:29:09  dim
 Modifications to support EP2/EP6 operation with proper one cycle wait
 after fifoadr change.
 General cleanup.

 Revision 1.1.1.1  2006/11/16 19:38:15  dim
 Initial import

*/

module fx2(
  // CY7C68013A interface
  input		clk,		// USB clock at 48 MHz
  (* IOB = "TRUE" *)
  output reg	slrd,		// Read control
  (* IOB = "TRUE" *)
  output reg	slwr,		// Write control
  (* IOB = "TRUE" *)
  output reg	sloe,		// FPGA output enable
  (* IOB = "TRUE" *)
  output reg	pktend,		// Packet end control
  input		flag_ep2,	// From USB to FPGA
  input		flag_ep6,	// From FPGA to USB
  (* IOB = "TRUE" *)
  output reg [1:0] fifoadr,	// FIFO selection address
  inout [15:0]	fd,		// Data bus
  
  // Local interface
  output reg [31:0] data_in,	// Local data in, from USB!!!
  input [31:0]	data_out,	// Local data out, to USB!!!
  output reg	strobe,		// Local bus strobe
  output reg	rd,		// Local bus read enable
  output reg [31:0] addr,	// Local bus address
  input		ack,		// Local bus ack
  output reg [3:0] state,	// For debugging

  input		rst		// Reset from FX2
);
  reg strobe0 = 1'b0;
  reg [15:0] readout;
  reg [31:0] rd_word = 32'b0, data_in0 = 32'b0;
  reg [31:0] cnt = 32'hffffffff;

  // USB FIFO and local bus controller state machine
  parameter IDLE      = 4'b0000; parameter SETFADDR = 4'b0001;
  parameter CNT0      = 4'b0010; parameter ADDR0    = 4'b0011;
  parameter ADDR1     = 4'b0100; parameter READ     = 4'b0101;
  parameter WR_IDLE   = 4'b0110; parameter WR_DATA  = 4'b0111;
  parameter WR_STROBE = 4'b1000; parameter READ_START = 4'b1001;
  parameter CNT1      = 4'b1010; parameter FIFOADDR = 4'b1011;
  parameter CNT0W     = 4'b1100; parameter CNT1W    = 4'b1101;
  parameter ADDR0W    = 4'b1110; parameter ADDR1W   = 4'b1111;

  initial begin
    state   = IDLE;
    slrd    = 1'b1;
    sloe    = 1'b1;
    slwr    = 1'b1;
    pktend  = 1'b1;
    fifoadr = 2'b0;
    
    strobe  = 1'b0;
    data_in = 32'b0;
    addr    = 32'b0;
    rd      = 1'b0;
  end

  always @(posedge clk) begin
    if (rst) begin
      state  <= IDLE;
      pktend <= 1'b1;
      slwr   <= 1'b1;
      sloe   <= 1'b1;
      slrd   <= 1'b1;
      strobe <= 1'b0;
      rd     <= 1'b0;
    end
    case (state)
      IDLE: begin
        pktend  <= 1'b1;
        slwr    <= 1'b1;
        fifoadr <= 2'b00;
        if (flag_ep2 & ~strobe) begin // Make sure previous write is done
          state <= CNT0; // Read length and transaction direction
          sloe  <= 1'b0;
          slrd  <= 1'b0;
        end
      end
      CNT0: begin
        cnt[16:0] <= {fd, 1'b0};
        state <= CNT1;
      end
      CNT1:
        if (flag_ep2) begin
          cnt[31:17] <= fd[14:0];
          rd <= fd[15]; // Reading or writing selector
          state <= slrd ? CNT1W : ADDR0;
          slrd  <= 1'b0;
        end
        else
          slrd  <= 1'b1;
      CNT1W:
          state <= ADDR0;
      ADDR0:
        if (flag_ep2) begin
          addr[15:0] <= fd;
          state <= slrd ? ADDR0W : ADDR1;
          slrd  <= 1'b0;
        end
        else
          slrd  <= 1'b1;
      ADDR0W: state <= ADDR1;
      ADDR1:
        if (flag_ep2) begin
          addr[31:16] <= fd;
          if (rd) begin
            state    <= READ_START;
            strobe   <= 1'b1; // Initiate local bus read
            slrd     <= 1'b1; // Done reading
            sloe     <= 1'b1; // Disable FIFO outputs
            fifoadr  <= 2'b10; // Select EP6
          end
          else
            if (flag_ep2) begin
              state <= slrd ? ADDR1W : WR_DATA;
              slrd  <= 1'b0;
            end
            else begin
              state <= WR_IDLE;
              slrd  <= 1'b1;
            end
        end
        else
          slrd <= 1'b1;
      ADDR1W: state <= WR_DATA;
      WR_IDLE: begin
        if (strobe0) begin
          if (~strobe)
            strobe <= 1'b1;
          else
            state  <= WR_STROBE;
        end
        if (cnt == 32'b0)
          state <= IDLE;
        else if (flag_ep2) begin
          state <= WR_DATA;
          slrd <= 1'b0;
        end
      end
      WR_DATA: begin
        if (flag_ep2) begin
          cnt <= (cnt == 32'b0) ? 32'b0 : cnt - 32'b1; // Sample read out
          if (cnt[0]) begin
            data_in0[31:16] <= fd;   // Next cycle data_in0 has a valid word
            strobe0         <= 1'b1; // Next cycle strobe0 says - write
            if (cnt[31:1] == 31'b0) begin // Done with transaction, more data flowing
              slrd  <= 1'b1;
              state <= WR_STROBE;
            end
          end
          else begin
            data_in0[15:0] <= fd; // Read the LSB
          end
        end
        else begin
          slrd  <= 1'b1;
          state <= WR_IDLE;
        end
        if (strobe0) begin // Data ready for writing
          if (~strobe) // Previous write is done
            strobe <= 1'b1;
          else begin
            state  <= WR_STROBE;
            slrd   <= 1'b1;
          end
        end
      end
      WR_STROBE: begin
        if (~strobe) begin
          strobe <= 1'b1;
          if (cnt == 32'b0)
            state <= IDLE;
          else begin
            if (flag_ep2) begin
              slrd  <= 1'b0;
              state <= WR_DATA;
            end
            else
              state <= WR_IDLE;
          end
        end
      end
      READ_START: state <= READ; // One cycle wait to allow fifoaddr change
      READ: begin
        if (~strobe) begin // Data available
          if (flag_ep6) begin
            slwr <= 1'b0; // Write data
            cnt  <= cnt - 32'b1;
            if (cnt[0]) begin
              readout <= rd_word[31:16];
              strobe <= (cnt != 32'b1);
              if (cnt == 32'b1) begin
                state <= IDLE;
                pktend <= 1'b0;
              end
            end
            else
              readout <= rd_word[15:0];
          end
          else
            slwr <= 1'b1;
        end
        else
          slwr <= 1'b1;
      end
    endcase
    // Pipelined writing code
    if (strobe0) begin
      data_in <= data_in0;
      strobe0 <= 1'b0;
    end
    if (strobe & ack) begin
      rd_word <= data_out; // Sample readback data (only useful for reads)
      strobe <= 1'b0; // Reset strobe
      addr <= addr + 32'b1; // Increment address counter
    end
  end
  
  assign fd = (sloe) ? readout: 16'hzzzz;
endmodule // fx2
