///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------
//-- Title      : AXI Master Interface to PCIe
//-------------------------------------------------------------------------------
//-- File       : AXI_IF.v
//-- Author     : Corporate Applications Engineering
//-- Company    : Microsemi Corporation
//-- Device     : RTG4
//-- Standard   : Verilog
//-------------------------------------------------------------------------------
//-- Description: This code implementes the AXI master logic to do the read and write   
//--              transactions to PCIe.
//-------------------------------------------------------------------------------
//-- Copyright (c) 2015   Microsemi Corporation
//--                      All rights reserved.
//-------------------------------------------------------------------------------
//-- Revisions  : V1.0
//-------------------------------------------------------------------------------
/////////////////////////////////////////////////////////////////////////////////////////////////// 
module PCIe_AXI_IF(input CLK,
                   input RESETn,
                   //to controller for getting buffer descriptors
                   output PCIE_M_RVALID,
                   output[63 : 0] PCIE_M_RDATA,
                   //dma ch0 controller signals
                   input[2 : 0] DMA_DIR,
                   input[17 : 0] DMA_SIZE,
                   input[31 : 0] PC_BASE_ADDR,
                   output reg WRITE_DONE,
                   output reg READ_DONE,
                   output reg PCIE_READ_BUSY,
                   //handshaking signals for PCIe to DDR
                   input[8 : 0] PC2DDR_RAM_ADDR,
                   output reg PC_RD_BUSY,
                   input[13 : 0] DDR_WRITE_CNT,
                   output reg DDR_WRITE_START,
                   output[63 : 0] DATA_TO_DDR,
                   //dma ch1 controller signals
                   input[2 : 0] DMA_DIR_CH1,
                   input[17 : 0] DMA_SIZE_CH1,
                   input[31 : 0] PC_BASE_ADDR_CH1,
                   //handshaking signals for DDR to PCIe
                   input WRITE_START,
                   input[63 : 0] DATA_DDR,
                   input RD_BUSY,
                   output[8 : 0] RAM_ADDR,
                   output[13 : 0] WRITE_CNT,
                   output reg PCIE_WRITE_BUSY,
                   output reg PCIe_WRITE_RESP,
                   //AXI Master IF
                   // Write Address Channel 
                   output[3 : 0] AWID,
                   output reg[31 : 0] AWADDR,
                   output reg[3 : 0] AWLEN,
                   output[1 : 0] AWSIZE,
                   output[1 : 0] AWBURST,
                   output reg AWVALID,
                   input AWREADY,
                   // Write Data Channel 
                   output[3 : 0] WID,
                   output[7 : 0] WSTRB,
                   output reg WLAST,
                   output reg WVALID,
                   output[63 : 0] WDATA,
                   input WREADY,
                   // Write Response Channel
                   input[3 : 0] BID,
                   input[1 : 0] BRESP,
                   input BVALID,
                   output BREADY,
                   // Read Address Channel 
                   output[3 : 0] ARID,
                   output reg[31 : 0] ARADDR,
                   output reg[3 : 0] ARLEN,
                   output[1 : 0] ARSIZE,
                   output[1 : 0] ARBURST,
                   output reg ARVALID,
                   input ARREADY,
                   // Read Data Channel
                   input[3 : 0] RID,
                   input[63 : 0] RDATA,
                   input[1 : 0] RRESP,
                   input RLAST,
                   input RVALID,
                   output reg RREADY);
  // AXI FSM States
  reg[2 : 0] awdata_state;
  reg[2 : 0] ardata_state;
  reg[2 : 0] awaddr_state;
  reg[2 : 0] awresp_state;
  reg[2 : 0] araddr_state;
  reg[1 : 0] ddr_wr_st;
  //axi fsm
  localparam[2 : 0] Idle_0 = 3'b000,
                    Idle_1 = 3'b001,
                    Write_0 = 3'b010,
                    Write_1 = 3'b011,
                    Bresp_0 = 3'b100,
                    Write_last = 3'b110,
                    Read_0 = 3'b010,
                    Read_1 = 3'b011,
                    Read_last = 3'b110,
                    DMA_Reset = 3'b101;
  //hanshake to DDR AXI master logic
  localparam[1 : 0] ddr_wr_idle = 2'b00,
                    ddr_wr_start = 2'b01,
                    ddr_wr_reset = 2'b10;
  reg[31 : 0] AWADDR_int;
  reg[31 : 0] ARADDR_int;
  //write channel counters
  reg[3 : 0] burst_cnt;
  reg[17 : 0] WDATA_int;
  reg[17 : 0] waddr_cnt;
  reg addr_valid_1;
  reg[63 : 0] data_wready_low;
  reg[17 : 0] awvalid_cnt;
  reg[17 : 0] bvalid_cnt;
  //read channel counters
  reg[17 : 0] rdata_cnt;
  reg[17 : 0] raddr_cnt;
  //for variable write burst lengths
  reg[3 : 0] burst_len;
  reg[4 : 0] burst_len_wdata_first;
  reg[4 : 0] burst_len_wdata;
  reg[4 : 0] burst_len_wdata_last;
  //for variable read burst lengths
  reg[4 : 0] burst_len_rdata;
  reg[4 : 0] burst_len_rdata_last;
  //almost full flag
  reg mem_full;
  assign AWSIZE = 2'b11;
  assign AWBURST = 2'b01;
  assign AWID = 4'b0000;
  assign WID = 4'b0000;
  assign ARSIZE = 2'b11;
  assign ARBURST = 2'b01;
  assign ARID = 4'b0000;
  assign WSTRB = 8'hFF;
  assign BREADY = 1'b1;
  assign WDATA = addr_valid_1 ? DATA_DDR : data_wready_low;
  assign RAM_ADDR = WDATA_int[8 : 0];
  assign WRITE_CNT = WDATA_int[17 : 4];
  assign PCIE_M_RVALID = RVALID;
  assign PCIE_M_RDATA = RDATA;
  //for matching the assignment ranges
  reg temp1,
      temp2;
  wire[18 : 0] dma_wabytes_left;
  assign dma_wabytes_left = DMA_SIZE_CH1 - waddr_cnt;
  wire[19 : 0] dma_wbytes_left;
  assign dma_wbytes_left = DMA_SIZE_CH1 - WDATA_int - 2;
  wire[18 : 0] dma_rabytes_left;
  assign dma_rabytes_left = DMA_SIZE - raddr_cnt;
  //write address channel
  always
    @(posedge CLK)
    begin
      if (RESETn == 1'b0)
        begin
          AWADDR_int <= 0;
          AWADDR <= 0;
          AWLEN <= 4'b1111;
          AWVALID <= 1'b0;
          waddr_cnt <= 0;
          burst_len_wdata <= 16;
          burst_len_wdata_last <= 0;
          burst_len_wdata_first <= 0;
          awaddr_state <= Idle_0;
        end
      else
        begin
          case (awaddr_state)
            Idle_0:
              begin
                AWADDR_int[31 : 0] <= PC_BASE_ADDR_CH1;
                waddr_cnt <= 0;
                if (WRITE_START == 1'b1 || DMA_DIR_CH1 == 3'b110)
                  begin
                    awaddr_state <= Idle_1;
                  end
                else
                  begin
                    awaddr_state <= Idle_0;
                  end
                //to make 128byte boundary aligned address
                if (PC_BASE_ADDR_CH1[6 : 3] != 0)
                  begin
                    burst_len_wdata <= 16 - PC_BASE_ADDR_CH1[6 : 3];
                    burst_len_wdata_first <= 15 - PC_BASE_ADDR_CH1[6 : 3];
                  end
                else
                  begin
                    burst_len_wdata <= 16;
                    burst_len_wdata_first <= 15;
                  end
              end
            Idle_1:
              begin
                { temp1, AWLEN } <= burst_len_wdata - 1;
                if (waddr_cnt < DMA_SIZE_CH1)
                  begin
                    if (RD_BUSY == 1'b0)
                      begin
                        if (dma_wabytes_left > 15) //if number of beats are more than 15 i.e. maximum possible burst
                          begin
                            AWADDR[31 : 0] <= AWADDR_int;
                            AWVALID <= 1'b1;
                            awaddr_state <= Write_0;
                          end
                        else
                          begin //to complete last bytes transfer
                            burst_len_wdata_last <= dma_wabytes_left[4 : 0];
                            awaddr_state <= Write_last;
                          end
                      end
                  end
                else
                  begin
                    awaddr_state <= DMA_Reset;
                  end
              end
            Write_0:
              begin
                if (AWREADY == 1'b1)
                  begin
                    AWVALID <= 1'b0;
                    { temp1, AWADDR_int } <= AWADDR_int + { burst_len_wdata, 3'b0 }; //addr + burst length * 8       
                    { temp1, waddr_cnt } <= waddr_cnt + burst_len_wdata;
                    awaddr_state <= Write_1;
                  end
                else
                  begin
                    awaddr_state <= Write_0;
                  end
              end
            Write_1:
              begin
                awaddr_state <= Idle_1;
                burst_len_wdata <= 16; //maximum burst length   
              end
            Write_last:
              begin
                AWADDR[31 : 0] <= AWADDR_int;
                AWVALID <= 1'b1;
                awaddr_state <= Write_0;
                { temp1, AWLEN } <= burst_len_wdata_last - 1;
                burst_len_wdata <= burst_len_wdata_last;
              end
            DMA_Reset:
              begin
                if (DMA_DIR_CH1 == 3'b111) //to reset the DMA transaction                          
                  awaddr_state <= Idle_0;
              end
            default:
              awaddr_state <= Idle_0;
          endcase
        end
    end
  //write data channel
  always
    @(posedge CLK)
    begin
      if (RESETn == 1'b0)
        begin
          WDATA_int <= 0;
          WVALID <= 1'b0;
          WLAST <= 1'b0;
          PCIE_WRITE_BUSY <= 1'b0;
          burst_cnt <= 0;
          burst_len <= 0;
          addr_valid_1 <= 1;
          data_wready_low <= 0;
          awdata_state <= Idle_1;
        end
      else
        begin
          case (awdata_state)
            Idle_1:
              begin
                WDATA_int <= 0;
                burst_len <= burst_len_wdata_first[3 : 0];
                if ((WRITE_START == 1'b1 || DMA_DIR_CH1 == 3'b110) && AWVALID == 1'b1) //to start the DMA transaction
                  begin
                    PCIE_WRITE_BUSY <= 1'b1;
                    awdata_state <= Write_0;
                  end
                else
                  begin
                    PCIE_WRITE_BUSY <= 1'b0;
                    awdata_state <= Idle_1;
                  end
              end
            Write_0:
              begin
                if (WREADY == 1'b1 && RD_BUSY == 1'b0)
                  begin
                    if (WDATA_int < DMA_SIZE_CH1)
                      begin
                        burst_cnt <= 0;
                        WLAST <= 1'b0;
                        awdata_state <= Write_1;
                      end
                    else
                      begin
                        awdata_state <= DMA_Reset;
                      end
                  end
              end
            Write_1:
              begin
                if (WREADY == 1'b1)
                  begin
                    burst_cnt <= burst_cnt + 1;
                    addr_valid_1 <= 1'b1;
                    WVALID <= 1'b1;
                    if (burst_cnt == burst_len)
                      begin
                        WLAST <= 1'b1;
                        awdata_state <= Bresp_0;
                      end
                    else
                      begin
                        WDATA_int <= WDATA_int + 1;
                        awdata_state <= Write_1;
                      end
                  end
                else
                  begin
                    if (addr_valid_1 == 1)
                      begin
                        addr_valid_1 <= 1'b0;
                        data_wready_low <= DATA_DDR;
                        awdata_state <= Write_1;
                      end
                  end
              end
            Bresp_0:
              begin
                if (WREADY == 1'b1)
                  begin
                    addr_valid_1 <= 1'b1;
                    WVALID <= 1'b0;
                    WLAST <= 1'b0;
                    WDATA_int <= WDATA_int + 1;
                    awdata_state <= Write_0;
                    if (dma_wbytes_left < 15) //checking with 15(17) as count is not incremented for last beat
                      burst_len <= dma_wbytes_left[3 : 0];
                    else
                      burst_len <= 15;
                  end
                else
                  if (WLAST == 1'b1 && addr_valid_1 == 1)
                    begin
                      addr_valid_1 <= 1'b0;
                      data_wready_low <= DATA_DDR;
                    end
              end
            DMA_Reset:
              begin
                if (DMA_DIR_CH1 == 3'b111)
                  begin
                    WDATA_int <= 0;
                    awdata_state <= Idle_1;
                  end
              end
            default:
              awdata_state <= Idle_1;
          endcase
        end
    end
  //write response channel
  always
    @(posedge CLK)
    begin
      if (RESETn == 1'b0)
        begin
          awvalid_cnt <= 0;
          bvalid_cnt <= 0;
          awresp_state <= Idle_0;
        end
      else
        begin
          case (awresp_state)
            Idle_0:
              begin
                if (WRITE_START == 1'b1)
                  begin
                    awvalid_cnt <= 0;
                    bvalid_cnt <= 0;
                    awresp_state <= Write_0;
                  end
              end
            Write_0:
              begin
                if (AWVALID && AWREADY)
                  awvalid_cnt <= awvalid_cnt + 1'b1;
                if (BVALID)
                  bvalid_cnt <= bvalid_cnt + 1'b1;
                if (DMA_DIR_CH1 == 3'b111)
                  awresp_state <= Bresp_0;
              end
            Bresp_0:
              begin
                if (AWVALID && AWREADY)
                  awvalid_cnt <= awvalid_cnt + 1'b1;
                if (BVALID)
                  bvalid_cnt <= bvalid_cnt + 1'b1;
                if (WRITE_START == 1'b1)
                  awresp_state <= Idle_0;
              end
            default:
              awresp_state <= Idle_0;
          endcase
        end
    end
  //pcie write response for checking ACKs at end of DMA transfer
  always
    @(posedge CLK)
    begin
      if (RESETn == 1'b0)
        begin
          PCIe_WRITE_RESP <= 1'b0;
        end
      else
        begin
          if (awvalid_cnt == bvalid_cnt)
            PCIe_WRITE_RESP <= 1'b1;
          else
            PCIe_WRITE_RESP <= 1'b0;
        end
    end
  //Read address channel 
  always
    @(posedge CLK)
    begin
      if (RESETn == 1'b0)
        begin
          ARLEN <= 4'b1111;
          ARVALID <= 1'b0;
          ARADDR <= 0;
          ARADDR_int <= 0;
          raddr_cnt <= 0;
          burst_len_rdata <= 16;
          burst_len_rdata_last <= 0;
          araddr_state <= Idle_0;
        end
      else
        begin
          case (araddr_state)
            Idle_0:
              begin
                raddr_cnt <= 0;
                ARADDR_int <= PC_BASE_ADDR;
                if (DMA_DIR == 3'b001 || DMA_DIR == 3'b101)
                  begin
                    araddr_state <= Idle_1;
                  end
                else
                  begin
                    araddr_state <= Idle_0;
                  end
                //if 128 byte boundary
                if (PC_BASE_ADDR[6 : 3] != 0)
                  burst_len_rdata <= 16 - PC_BASE_ADDR[6 : 3];
                else
                  burst_len_rdata <= 16;
              end
            Idle_1:
              begin
                { temp2, ARLEN } <= burst_len_rdata - 1;
                ARADDR <= ARADDR_int;
                if (raddr_cnt < DMA_SIZE)
                  begin
                    if (mem_full == 1'b0)
                      begin
                        if (dma_rabytes_left > 15)
                          begin
                            { temp2, ARADDR_int } <= ARADDR_int + { burst_len_rdata, 3'b0 };
                            ARVALID <= 1'b1;
                            { temp2, raddr_cnt } <= raddr_cnt + burst_len_rdata;
                            araddr_state <= Read_0;
                          end
                        else
                          begin //to complete last bytes transfer
                            burst_len_rdata_last <= dma_rabytes_left[4 : 0];
                            araddr_state <= Read_last;
                          end
                      end
                  end
                else
                  begin
                    araddr_state <= DMA_Reset;
                  end
              end
            Read_0:
              begin
                if (ARREADY == 1'b1)
                  begin
                    ARVALID <= 1'b0;
                    burst_len_rdata <= 16;
                    araddr_state <= Idle_1;
                  end
                else
                  begin
                    araddr_state <= Read_0;
                  end
              end
            Read_last:
              begin
                ARADDR <= ARADDR_int;
                ARVALID <= 1'b1;
                { temp2, ARLEN } <= burst_len_rdata_last - 1;
                burst_len_rdata <= burst_len_rdata_last;
                { temp2, ARADDR_int } <= ARADDR_int + { burst_len_rdata_last, 3'b0 };
                { temp2, raddr_cnt } <= raddr_cnt + burst_len_rdata_last;
                araddr_state <= Read_1;
              end
            Read_1:
              begin
                if (ARREADY == 1'b1)
                  begin
                    ARVALID <= 1'b0;
                    araddr_state <= Idle_1;
                  end
              end
            DMA_Reset:
              begin
                if (DMA_DIR == 3'b111) //to reset the DMA transaction
                  araddr_state <= Idle_0;
              end
            default:
              araddr_state <= Idle_0;
          endcase
        end
    end
  //Read data channel
  always
    @(posedge CLK)
    begin
      if (RESETn == 1'b0)
        begin
          RREADY <= 1'b0;
          PCIE_READ_BUSY <= 1'b0;
          ardata_state <= Idle_0;
        end
      else
        begin
          case (ardata_state)
            Idle_0:
              begin
                if (DMA_DIR == 3'b001 || DMA_DIR == 3'b101)
                  begin
                    PCIE_READ_BUSY <= 1'b1;
                    ardata_state <= Read_1;
                  end
                else
                  begin
                    PCIE_READ_BUSY <= 1'b0;
                    ardata_state <= Idle_0;
                  end
              end
            Read_1:
              begin
                if (rdata_cnt < DMA_SIZE)
                  begin
                    RREADY <= 1'b1;
                  end
                else
                  begin
                    RREADY <= 1'b0;
                    ardata_state <= DMA_Reset;
                  end
              end
            DMA_Reset:
              begin
                if (DMA_DIR == 3'b111) //to reset the DMA transaction
                  begin
                    ardata_state <= Idle_0;
                  end
              end
            default:
              ardata_state <= Idle_0;
          endcase
        end
    end
  //read data count
  always
    @(posedge CLK)
    begin
      if (RESETn == 1'b0)
        begin
          rdata_cnt <= 0;
        end
      else
        begin
          if (ardata_state == Idle_0)
            begin
              rdata_cnt <= 0;
            end
          else
            begin
              if (RVALID == 1'b1)
                begin
                  rdata_cnt <= rdata_cnt + 1;
                end
            end
        end
    end
  //TPSRAM
  LSRAM LSRAM_1(
                // Inputs
                .CLK(CLK),
                .RADDR(PC2DDR_RAM_ADDR),
                .WADDR(rdata_cnt[8 : 0]),
                .WD(RDATA),
                .WEN(RVALID),
                // Outputs
                .RD(DATA_TO_DDR));
  //busy signal to avoid ram overlapping
  always
    @(posedge CLK)
    begin
      if (READ_DONE == 1'b1)
        PC_RD_BUSY <= 1'b0;
      else
        if ((DDR_WRITE_CNT + 3) >= (rdata_cnt[17 : 4] + 1) && DDR_WRITE_START == 1'b1)
          PC_RD_BUSY <= 1'b1;
        else
          PC_RD_BUSY <= 1'b0;
    end
  //busy signal to avoid mem overwriting
  always
    @(posedge CLK)
    begin
      if (rdata_cnt[17 : 4] - DDR_WRITE_CNT >= 24) //3K
        mem_full <= 1'b1;
      else
        mem_full <= 1'b0;
    end
  //initiating write to ddr
  always
    @(posedge CLK)
    begin
      if (RESETn == 1'b0)
        begin
          DDR_WRITE_START <= 1'b0;
          ddr_wr_st <= ddr_wr_idle;
        end
      else
        begin
          case (ddr_wr_st)
            ddr_wr_idle:
              begin
                if (DMA_DIR == 3'b001)
                  ddr_wr_st <= ddr_wr_start;
              end
            ddr_wr_start:
              begin
                if (rdata_cnt != 0 || DMA_SIZE == 0)
                  begin
                    DDR_WRITE_START <= 1'b1;
                    ddr_wr_st <= ddr_wr_reset;
                  end
              end
            ddr_wr_reset:
              begin
                if (DMA_DIR == 3'b111)
                  begin
                    DDR_WRITE_START <= 1'b0;
                    ddr_wr_st <= ddr_wr_idle;
                  end
              end
            default:
              ddr_wr_st <= ddr_wr_idle;
          endcase
        end
    end
  //DMA transaction complete signals generation to DMA controller
  always
    @(posedge CLK)
    begin
      if (RESETn == 1'b0)
        begin
          WRITE_DONE <= 1'b0;
          READ_DONE <= 1'b0;
        end
      else
        begin
          if (DMA_DIR == 3'b111)
            begin
              READ_DONE <= 1'b0;
            end
          else
            begin
              if (ardata_state == DMA_Reset)
                READ_DONE <= 1'b1;
            end
          if (DMA_DIR_CH1 == 3'b111)
            begin
              WRITE_DONE <= 1'b0;
            end
          else
            begin
              if (awdata_state == DMA_Reset)
                WRITE_DONE <= 1'b1;
            end
        end
    end
endmodule
