///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------
//-- Title      : AXI Master Interface to DDR_FIC
//-------------------------------------------------------------------------------
//-- File       : DDR_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 MDDR through DDR_FIC.
//-------------------------------------------------------------------------------
//-- Copyright (c) 2015   Microsemi Corporation
//--                      All rights reserved.
//-------------------------------------------------------------------------------
//-- Revisions  : V1.0
//-------------------------------------------------------------------------------
/////////////////////////////////////////////////////////////////////////////////////////////////// 
module DDR_AXI_IF(input CLK,
                  input RESETn,
                  //dma CH0 controller signals
                  input[23 : 0] DDR_OFFSET,
                  input[2 : 0] DMA_DIR,
                  input[17 : 0] DMA_SIZE,
                  output reg WRITE_DONE,
                  //handshaking signals for PCIe to DDR
                  input WRITE_START,
                  output[8 : 0] PC2DDR_RAM_ADDR,
                  input PC_RD_BUSY,
                  output[13 : 0] DDR_WRITE_CNT,
                  input[63 : 0] DATA_PCIe,
                  output reg DDR_WRITE_BUSY,
                  //dma CH1 controller signals
                  input[23 : 0] DDR_OFFSET_CH1,
                  input[2 : 0] DMA_DIR_CH1,
                  input[17 : 0] DMA_SIZE_CH1,
                  //handshaking signals for DDR to PCIe
                  input[13 : 0] WRITE_CNT,
                  input[8 : 0] RAM_ADDR,
                  output reg RD_BUSY,
                  output reg PCI_WRITE_START,
                  output[63 : 0] DATA_TO_PCIe,
                  output reg DDR_READ_BUSY,
                  //AXI Master IF
                  // Write Address Channel
                  output[3 : 0] AWID,
                  output reg[31 : 0] AWADDR,
                  output reg[3 : 0] AWLEN,
                  output[2 : 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[2 : 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] awaddr_state;
  reg[2 : 0] awdata_state;
  reg[2 : 0] ardata_state;
  reg[2 : 0] araddr_state;
  reg[1 : 0] pcie_wr_st;
  //ddr axi fsm
  localparam[2 : 0] Idle_0 = 3'b000,
                    Idle_1 = 3'b001,
                    Wait_PC = 3'b100,
                    Write_0 = 3'b010,
                    Write_1 = 3'b011,
                    Write_last = 3'b110,
                    Read_0 = 3'b010,
                    Read_1 = 3'b011,
                    Read_last = 3'b110,
                    DMA_Reset = 3'b101;
  //hanshake to PCIe AXI master logic
  localparam[1 : 0] pcie_wr_idle = 2'b00,
                    pcie_wr_start = 2'b01,
                    pcie_wr_reset = 2'b10;
  reg[31 : 0] AWADDR_int;
  reg[31 : 0] ARADDR_int;
  reg addr_valid_1;
  reg[63 : 0] data_wready_low;
  //write channel counters
  reg[3 : 0] burst_cnt;
  reg[17 : 0] WDATA_int;
  reg[17 : 0] waddr_cnt;
  //read channel counters
  reg[17 : 0] rdata_cnt;
  reg[17 : 0] raddr_cnt;
  //for variable write burst lengths
  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;
  //ddr read complete
  reg READ_DONE;
  //for matching the assignment ranges
  reg temp1,
      temp2;
  wire[18 : 0] dma_wabytes_left;
  assign dma_wabytes_left = DMA_SIZE - 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_CH1 - raddr_cnt;
  //assigning burst length and ID's
  assign AWID = 4'b0000;
  assign AWSIZE = 3'b011;
  assign AWBURST = 2'b01;
  assign WID = 4'b0000;
  assign WSTRB = 8'hFF;
  assign ARSIZE = 3'b011;
  assign ARBURST = 2'b01;
  assign ARID = 4'b0000;
  assign WDATA = addr_valid_1 ? DATA_PCIe : data_wready_low;
  assign DDR_WRITE_CNT = WDATA_int[17 : 4];
  assign PC2DDR_RAM_ADDR = WDATA_int[8 : 0];
  assign BREADY = 1'b1;
  //Write address channel
  always
    @(posedge CLK)
    begin
      if (RESETn == 1'b0)
        begin
          AWADDR_int <= 32'h00000000;
          AWADDR <= 0;
          AWLEN <= 4'b1111;
          AWVALID <= 1'b0;
          waddr_cnt <= 0;
          burst_len_wdata <= 16;
          burst_len_wdata_last <= 0;
          awaddr_state <= Idle_0;
        end
      else
        begin
          case (awaddr_state)
            Idle_0:
              begin
                AWADDR_int <= { 8'h00, DDR_OFFSET }; //DDR buffer for writing
                waddr_cnt <= 0;
                if (WRITE_START == 1'b1)
                  begin
                    awaddr_state <= Idle_1;
                  end
                //to make 128byte boundary aligned address
                if (DDR_OFFSET[6 : 3] != 0)
                  burst_len_wdata <= 16 - DDR_OFFSET[6 : 3];
                else
                  burst_len_wdata <= 16;
              end
            Idle_1:
              begin
                { temp1, AWLEN } <= burst_len_wdata - 1;
                AWADDR <= AWADDR_int;
                if (waddr_cnt < DMA_SIZE)
                  begin
                    if (PC_RD_BUSY == 1'b0)
                      begin
                        if (dma_wabytes_left > 15) //if number of beats are more than 15 i.e. maximum possible burst
                          begin
                            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
                burst_len_wdata <= 16; //maximum burst length 
                if (WLAST && WREADY)
                  awaddr_state <= Idle_1;
              end
            Write_last:
              begin
                AWADDR <= AWADDR_int;
                AWVALID <= 1'b1;
                { temp1, AWLEN } <= burst_len_wdata_last - 1;
                burst_len_wdata <= burst_len_wdata_last;
                awaddr_state <= Write_0;
              end
            DMA_Reset:
              begin
                if (DMA_DIR == 3'b111) //to reset the DMA transaction
                  awaddr_state <= Idle_0;
              end
            default:
              awaddr_state <= Idle_0;
          endcase
        end
    end
  //Write data and response channels
  always
    @(posedge CLK)
    begin
      if (RESETn == 1'b0)
        begin
          WDATA_int <= 0;
          WVALID <= 1'b0;
          WLAST <= 1'b0;
          DDR_WRITE_BUSY <= 1'b0;
          addr_valid_1 <= 1;
          burst_cnt <= 0;
          data_wready_low <= 0;
          awdata_state <= Idle_1;
        end
      else
        begin
          case (awdata_state)
            Idle_0:
              begin
                if (WREADY == 1'b1)
                  begin
                    addr_valid_1 <= 1'b1;
                    WLAST <= 1'b0;
                    WVALID <= 1'b0;
                    awdata_state <= Write_0;
                  end
                else
                  if (WLAST == 1'b1 && addr_valid_1 == 1)
                    begin
                      addr_valid_1 <= 1'b0;
                      data_wready_low <= DATA_PCIe;
                    end
              end
            Idle_1:
              begin
                WDATA_int <= 0;
                if (WRITE_START == 1'b1)
                  begin
                    DDR_WRITE_BUSY <= 1'b1;
                    awdata_state <= Write_0;
                  end
                else
                  begin
                    DDR_WRITE_BUSY <= 1'b0;
                    awdata_state <= Idle_1;
                  end
              end
            Write_0:
              begin
                burst_cnt <= 0;
                if (awaddr_state == Write_0)
                  begin
                    if (DMA_DIR == 3'b001)
                      awdata_state <= Wait_PC;
                    else
                      awdata_state <= Write_1;
                  end
                else
                  begin
                    if (awaddr_state == DMA_Reset)
                      awdata_state <= DMA_Reset;
                  end
              end
            Wait_PC:
              begin
                if (PC_RD_BUSY == 1'b0)
                  awdata_state <= Write_1;
              end
            Write_1:
              begin
                if (WREADY == 1'b1)
                  begin
                    burst_cnt <= burst_cnt + 1;
                    addr_valid_1 <= 1'b1;
                    WVALID <= 1'b1;
                    WDATA_int <= WDATA_int + 1;
                    if (burst_cnt == AWLEN)
                      begin
                        WLAST <= 1'b1;
                        awdata_state <= Idle_0;
                      end
                    else
                      begin
                        awdata_state <= Write_1;
                      end
                  end
                else
                  begin
                    if (addr_valid_1 == 1)
                      begin
                        addr_valid_1 <= 1'b0;
                        data_wready_low <= DATA_PCIe;
                        awdata_state <= Write_1;
                      end
                  end
              end
            DMA_Reset:
              begin
                if (DMA_DIR == 3'b111) //to reset the DMA transaction
                  begin
                    awdata_state <= Idle_1;
                  end
              end
            default:
              awdata_state <= Idle_1;
          endcase
        end
    end
  //Read address channel
  always
    @(posedge CLK)
    begin
      if (RESETn == 1'b0)
        begin
          ARLEN <= 4'b1111;
          ARADDR_int <= 32'h00000000; //DDR buffer for reading
          ARADDR <= 32'h00000000;
          ARVALID <= 1'b0;
          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;
                if (DMA_DIR_CH1 == 3'b010)
                  begin
                    ARADDR_int <= { 8'h00, DDR_OFFSET_CH1 }; //DDR buffer for reading
                    araddr_state <= Idle_1;
                  end
                //to make 128byte boundary aligned address
                if (DDR_OFFSET_CH1[6 : 3] != 0)
                  burst_len_rdata <= 16 - DDR_OFFSET_CH1[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_CH1)
                  begin
                    if (mem_full == 1'b0)
                      begin
                        if (dma_rabytes_left > 15)
                          begin
                            ARVALID <= 1'b1;
                            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
                burst_len_rdata <= 16;
                if (ARREADY == 1'b1)
                  begin
                    { temp2, raddr_cnt } <= raddr_cnt + burst_len_rdata;
                    ARVALID <= 1'b0;
                    { temp2, ARADDR_int } <= ARADDR_int + { burst_len_rdata, 3'b0 }; //addr + burst length * 8    
                    araddr_state <= Idle_1;
                  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;
                araddr_state <= Read_1;
              end
            Read_1:
              begin
                if (ARREADY == 1'b1)
                  begin
                    { temp2, raddr_cnt } <= raddr_cnt + burst_len_rdata;
                    ARVALID <= 1'b0;
                    { temp2, ARADDR_int } <= ARADDR_int + { burst_len_rdata, 3'b0 }; //addr + burst length * 8
                    araddr_state <= Idle_1;
                  end
              end
            DMA_Reset:
              begin
                if (DMA_DIR_CH1 == 3'b111) //to reset the DMA transaction
                  araddr_state <= Idle_0;
              end
            default:
              araddr_state <= Idle_0;
          endcase
        end
    end
  //Read data channels
  always
    @(posedge CLK)
    begin
      if (RESETn == 1'b0)
        begin
          RREADY <= 1'b0;
          DDR_READ_BUSY <= 1'b0;
          rdata_cnt <= 0;
          ardata_state <= Idle_0;
        end
      else
        begin
          case (ardata_state)
            Idle_0:
              begin
                rdata_cnt <= 0;
                if (DMA_DIR_CH1 == 3'b010)
                  begin
                    DDR_READ_BUSY <= 1'b1;
                    ardata_state <= Idle_1;
                  end
                else
                  begin
                    DDR_READ_BUSY <= 1'b0;
                    ardata_state <= Idle_0;
                  end
              end
            Idle_1:
              begin
                if (rdata_cnt < DMA_SIZE_CH1)
                  begin
                    RREADY <= 1'b1;
                    ardata_state <= Read_1;
                  end
                else
                  begin
                    ardata_state <= DMA_Reset;
                  end
              end
            Read_1:
              begin
                if (RVALID == 1'b1)
                  begin
                    rdata_cnt <= rdata_cnt + 1;
                  end
                if (RLAST == 1'b1 && RVALID == 1'b1)
                  begin
                    RREADY <= 1'b0;
                    ardata_state <= Idle_1;
                  end
                else
                  begin
                    ardata_state <= Read_1;
                  end
              end
            DMA_Reset:
              begin
                if (DMA_DIR_CH1 == 3'b111) //to reset the DMA transaction
                  begin
                    ardata_state <= Idle_0;
                  end
              end
            default:
              ardata_state <= Idle_0;
          endcase
        end
    end
  //TPSRAM
  LSRAM LSRAM_0(
                // Inputs
                .CLK(CLK),
                .RADDR(RAM_ADDR),
                .WADDR(rdata_cnt[8 : 0]),
                .WD(RDATA),
                .WEN(RVALID),
                // Outputs
                .RD(DATA_TO_PCIe));
  //busy signal to avoid ram overlapping
  always
    @(posedge CLK)
    begin
      if (READ_DONE == 1'b1)
        RD_BUSY <= 1'b0;
      else
        if ((WRITE_CNT + 3) >= (rdata_cnt[17 : 4] + 1) && PCI_WRITE_START == 1'b1)
          RD_BUSY <= 1'b1;
        else
          RD_BUSY <= 1'b0;
    end
  //busy signal to avoid mem overwriting
  always
    @(posedge CLK)
    begin
      if (rdata_cnt[17 : 4] - WRITE_CNT >= 24) //3K
        mem_full <= 1'b1;
      else
        mem_full <= 1'b0;
    end
  //initiating write to pcie
  always
    @(posedge CLK)
    begin
      if (RESETn == 1'b0)
        begin
          PCI_WRITE_START <= 1'b0;
          pcie_wr_st <= pcie_wr_idle;
        end
      else
        begin
          case (pcie_wr_st)
            pcie_wr_idle: //idle
              begin
                if (DMA_DIR_CH1 == 3'b010)
                  pcie_wr_st <= pcie_wr_start;
              end
            pcie_wr_start: //start pcie write
              begin
                if (rdata_cnt != 0 || DMA_SIZE_CH1 == 0)
                  begin
                    PCI_WRITE_START <= 1'b1;
                    pcie_wr_st <= pcie_wr_reset;
                  end
              end
            pcie_wr_reset: //reset
              begin
                if (DMA_DIR_CH1 == 3'b111)
                  begin
                    PCI_WRITE_START <= 1'b0;
                    pcie_wr_st <= pcie_wr_idle;
                  end
              end
            default:
              pcie_wr_st <= pcie_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
          //read_done
          if (DMA_DIR_CH1 == 3'b111)
            begin
              READ_DONE <= 1'b0;
            end
          else
            begin
              if (ardata_state == DMA_Reset)
                READ_DONE <= 1'b1;
            end
          //write_done
          if (DMA_DIR == 3'b111)
            begin
              WRITE_DONE <= 1'b0;
            end
          else
            begin
              if (awdata_state == DMA_Reset)
                WRITE_DONE <= 1'b1;
            end
        end
    end
endmodule
