`timescale 1ns / 1ps
module dma_engine_256 (
      axi4l_intf.slave ctrl_if,
      axi4s_intf.slave axis,
      axi4_intf.master axim
   );
      
reg [ctrl_if.AXI_DATA_WIDTH-1:0] ctrl_reg[3:0];
wire [ctrl_if.AXI_DATA_WIDTH-1:0] status_reg[4:0];

reg ctrl_fifo_wren;
reg [127:0] ctrl_fifo_din;

wire dma_rst;
wire dma_enable;

wire ctrl_fifo_rst;
reg ctrl_fifo_rden;
wire [71:0] ctrl_fifo_dout;

wire ctrl_fifo_full;
wire ctrl_fifo_empty;
wire [9:0] ctrl_fifo_count;

wire stat_fifo_rst;

reg stat_fifo_wren;
reg [71:0] stat_fifo_din;
reg stat_fifo_rden;
wire [71:0] stat_fifo_dout;

wire stat_fifo_full;
wire stat_fifo_empty;
wire [9:0] stat_fifo_count;

reg [1:0] state;

reg [71:0] active_buf;
reg [22:0] remaining_len;
reg [22:0] bytes_written;

wire s_axis_s2mm_cmd_tvalid;
wire s_axis_s2mm_cmd_tready;
reg [79:0]  s_axis_s2mm_cmd_tdata;

wire m_axis_s2mm_sts_tvalid;
wire m_axis_s2mm_sts_tready;
wire [7:0] m_axis_s2mm_sts_tdata;


// ------------------------------
// AXIL Decode
// ------------------------------
wire [ctrl_if.AXI_ADDR_WIDTH-1 : 0]		raddr;
wire [ctrl_if.AXI_ADDR_WIDTH-1 : 0]		waddr;
wire 						    rden;
wire 						    wren;
wire [ctrl_if.AXI_DATA_WIDTH-1 : 0]		wdata;
reg [ctrl_if.AXI_DATA_WIDTH-1 : 0]		rdata;


axil_slave 
# (
	.DATA_WIDTH(ctrl_if.AXI_DATA_WIDTH),
	.ADDR_WIDTH(ctrl_if.AXI_ADDR_WIDTH)
) axil_slave_i 
(
	// AXIL Slave
	.S_AXI_ACLK(ctrl_if.clk),
	.S_AXI_ARESETN(ctrl_if.resetn),
	.S_AXI_AWADDR(ctrl_if.awaddr),
	.S_AXI_AWPROT(ctrl_if.awprot),
	.S_AXI_AWVALID(ctrl_if.awvalid),
	.S_AXI_AWREADY(ctrl_if.awready),
	.S_AXI_WDATA(ctrl_if.wdata),
	.S_AXI_WSTRB(ctrl_if.wstrb),
	.S_AXI_WVALID(ctrl_if.wvalid),
	.S_AXI_WREADY(ctrl_if.wready),
	.S_AXI_BRESP(ctrl_if.bresp),
	.S_AXI_BVALID(ctrl_if.bvalid),
	.S_AXI_BREADY(ctrl_if.bready),
	.S_AXI_ARADDR(ctrl_if.araddr),
	.S_AXI_ARPROT(ctrl_if.arprot),
	.S_AXI_ARVALID(ctrl_if.arvalid),
	.S_AXI_ARREADY(ctrl_if.arready),
	.S_AXI_RDATA(ctrl_if.rdata),
	.S_AXI_RRESP(ctrl_if.rresp),
	.S_AXI_RVALID(ctrl_if.rvalid),
	.S_AXI_RREADY(ctrl_if.rready),	
	
	.raddr(raddr),
	.waddr(waddr),
	.wren(wren),
	.rden(rden),
	.wdata(wdata),
	.rdata(rdata)
);
	
always @ (posedge ctrl_if.clk) begin
   if (~ctrl_if.resetn) begin
      ctrl_reg[0] <= 'b111;
      ctrl_reg[1] <= 'b0;
      ctrl_reg[2] <= 'b0;
      ctrl_reg[3] <= 'b0;

      ctrl_fifo_wren <= 'b0;
      ctrl_fifo_din <= 'b0;
      
      stat_fifo_rden <= 1'b0;
               
      rdata <= 'b0; 
   end else begin
      ctrl_fifo_wren <= 'b0;

      if (wren) begin
         if ( waddr[7:0] == 'h00 ) 
            ctrl_reg[0] <= wdata;
         if ( waddr[7:0] == 'h04 ) 
            ctrl_reg[1] <= wdata;
         if ( waddr[7:0] == 'h08 ) 
            ctrl_reg[2] <= wdata;
         if ( waddr[7:0] == 'h0C ) 
            ctrl_reg[3] <= wdata;
         if ( waddr[7:0] == 'h20 ) 
            ctrl_fifo_din[31:0] <= wdata;
         if ( waddr[7:0] == 'h24 ) 
            ctrl_fifo_din[63:32] <= wdata;
         if ( waddr[7:0] == 'h28 ) begin
            ctrl_fifo_din[95:64] <= wdata;
            ctrl_fifo_wren <= 1'b1;
         end                                               
      end
   end

   stat_fifo_rden <= 1'b0;
   if (rden) begin
      if (raddr[7:0] == 'h00)
         rdata <= ctrl_reg[0];
      if (raddr[7:0] == 'h04)
         rdata <= ctrl_reg[1];
      if (raddr[7:0] == 'h08)
         rdata <= ctrl_reg[2];        
      if (raddr[7:0] == 'h0C)
         rdata <= ctrl_reg[3];           
      if (raddr[7:0] == 'h10)
         rdata <= status_reg[0];       
      if (raddr[7] == 'h14)
         rdata <= status_reg[1];    
      if (raddr[7:0] == 'h18)
         rdata <= status_reg[2];       
      if (raddr[7:0] == 'h1C)
         rdata <= status_reg[3];   

      if (raddr[7:0] == 'h20)
         rdata <= stat_fifo_dout[31:0];  
      if (raddr[7:0] == 'h24)
         rdata <= stat_fifo_dout[63:32];     
      if (raddr[7:0] == 'h28) begin
         rdata <= {24'b0, stat_fifo_dout[71:64]};
         stat_fifo_rden <= 1'b1; 
      end
      if (raddr[7:0] == 'h2C)
         rdata <= status_reg[4];                                                                                                                                    
   end
end

      
   assign ctrl_fifo_rst = ctrl_reg[0][0];
   assign stat_fifo_rst = ctrl_reg[0][1];
   assign dma_rst = ctrl_reg[0][2];
   
   assign dma_enable = ctrl_reg[0][8];
  
   wire loop_mode = ctrl_reg[1][0];

   assign status_reg[0] = {30'h00, state};
   assign status_reg[1] = {16'h00, 8'd23, 8'd9};   
   assign status_reg[2] = {6'b0, ctrl_fifo_count, 15'b0, ctrl_fifo_full};
   assign status_reg[3] = {6'b0, stat_fifo_count, 15'b0, stat_fifo_empty};
   assign status_reg[4] = 32'hBEEF_BEEF;

      
   // State Machines
   parameter DMA_IDLE = 0;   
   parameter DMA_START = 1;   
   parameter DMA_ACTIVE = 2;   
   parameter DMA_COMP = 3;   

   axi4s_intf # (
      .AXI_DATA_WIDTH(axis.AXI_DATA_WIDTH)
      ) 
   axis_gated(
      .clk(axis.clk)
      );     	
   
	always @( posedge ctrl_if.clk ) 
   begin
      ctrl_fifo_rden <= 1'b0;
      stat_fifo_wren <= 1'b0;
      
      if ( dma_rst == 1'b1 ) begin
         state <= DMA_IDLE;
	   end
	   else begin   
         case (state) 
            DMA_IDLE: begin
               s_axis_s2mm_cmd_tdata <= 'b0;
               s_axis_s2mm_cmd_tdata[71:32] <= ctrl_fifo_dout[71:32];
               s_axis_s2mm_cmd_tdata[30] <= 1'b1;              
               s_axis_s2mm_cmd_tdata[23] <= 1'b1;      
               s_axis_s2mm_cmd_tdata[22:0] <= ctrl_fifo_dout[31:9];    
                  
               active_buf <= ctrl_fifo_dout;
               remaining_len <= ctrl_fifo_dout[31:9];
               bytes_written <= 'b0;                  
                           
               if ((ctrl_fifo_empty == 1'b0) && ( dma_enable == 1'b1 )) begin
                  ctrl_fifo_rden <= 1'b1; 
                  state <= DMA_START;
               end            
            end
            DMA_START : begin
               if (s_axis_s2mm_cmd_tready == 1'b1) begin      
                  state  <= DMA_ACTIVE;
               end
            end    
                    
            DMA_ACTIVE : begin
               if ((axis_gated.tready == 1'b1) && (axis_gated.tvalid == 1'b1)) begin     
                  if ( dma_enable == 1'b1 ) begin
                     remaining_len <= remaining_len - (axis.AXI_DATA_WIDTH/8);
                     bytes_written <= bytes_written + (axis.AXI_DATA_WIDTH/8);
                  
                     // if (remaining_len == (axis.AXI_DATA_WIDTH/8))  begin 
                     if (axis_gated.tlast)  begin 
                        state  <= DMA_COMP;
                     end
                  end else begin
                     state <= DMA_COMP;
                  end                  
               end
            end            

            DMA_COMP: begin
               if (m_axis_s2mm_sts_tvalid == 1'b1) begin //TODO: this is a bug
                  stat_fifo_wren <= 1'b1;
                  
                  stat_fifo_din <= active_buf;
                  stat_fifo_din[31:9] <= bytes_written;    
                  
                  state <= DMA_IDLE;                 
               end   
            end            

            default : begin
               state <= DMA_IDLE;
            end      
         endcase                      
      end             
   end	   
   
   assign s_axis_s2mm_cmd_tvalid = (state == DMA_START) ? 1'b1 : 1'b0;
   
   assign axis_gated.tdata = axis.tdata;
   assign axis_gated.tkeep = axis.tkeep;
   // assign axis_gated.tlast = ((remaining_len == (axis.AXI_DATA_WIDTH/8)) || ( dma_enable == 1'b0 )) ? 1'b1 : 1'b0;
   assign axis_gated.tlast = ((remaining_len == (axis.AXI_DATA_WIDTH/8)) || axis.tlast) ? 1'b1 : 1'b0;
   assign axis_gated.tvalid = (state == DMA_ACTIVE) ? (( dma_enable == 1'b0 ) ? 1'b1 : axis.tvalid) : 1'b0;
   assign axis.tready = (state == DMA_ACTIVE) ? axis_gated.tready : (( dma_enable == 1'b0 ) ? 1'b1 : 1'b0);
                                                                           // This can be removed and changed back to just 1'b0;
   assign m_axis_s2mm_sts_tready = (state == DMA_COMP) ? 1'b1 : 1'b0;  
      
   // basic idea behind datamover control;
   // control fifo allows dma driver to allocate PS DDR buffer and load the physical address 
   //    into the fifo; once the transfer is complete the result fifo will be loaded with completion result 
   //    that will also contain the physical DDR buffer address 
   wire [71:0] ctrl_din;
   wire ctrl_wren;
   assign ctrl_din = loop_mode ? ctrl_fifo_dout : ctrl_fifo_din[71:0];
   assign ctrl_wren = loop_mode ? ctrl_fifo_rden : ctrl_fifo_wren;

   dma_ctrl_status_fifo_0 ctrl_fifo_i (
      .clk( ctrl_if.clk ), // : IN STD_LOGIC;
      .srst( ctrl_fifo_rst ), // : IN STD_LOGIC;
      // .din( ctrl_fifo_din[71:0] ), // : IN STD_LOGIC_VECTOR(71 DOWNTO 0);
      // .wr_en( ctrl_fifo_wren ), // : IN STD_LOGIC;
      .din( ctrl_din ), // : IN STD_LOGIC_VECTOR(71 DOWNTO 0);
      .wr_en( ctrl_wren ), // : IN STD_LOGIC;
      .rd_en( ctrl_fifo_rden ), // : IN STD_LOGIC;
      .dout( ctrl_fifo_dout ), // : OUT STD_LOGIC_VECTOR(71 DOWNTO 0);
      .full( ctrl_fifo_full ), // : OUT STD_LOGIC;
      .empty( ctrl_fifo_empty ), // : OUT STD_LOGIC;
      .data_count( ctrl_fifo_count ), // : OUT STD_LOGIC_VECTOR(9 DOWNTO 0);
      .wr_rst_busy( ), 
      .rd_rst_busy( )
   );
   
   dma_ctrl_status_fifo_0 stat_fifo_i (
      .clk( ctrl_if.clk ), // : IN STD_LOGIC;
      .srst( stat_fifo_rst ), // : IN STD_LOGIC;
      .din( stat_fifo_din ), // : IN STD_LOGIC_VECTOR(71 DOWNTO 0);
      .wr_en( stat_fifo_wren ), // : IN STD_LOGIC;
      .rd_en( stat_fifo_rden ), // : IN STD_LOGIC;
      .dout( stat_fifo_dout ), // : OUT STD_LOGIC_VECTOR(71 DOWNTO 0);
      .full( stat_fifo_full ), // : OUT STD_LOGIC;
      .empty( stat_fifo_empty ), // : OUT STD_LOGIC;
      .data_count( stat_fifo_count ), // : OUT STD_LOGIC_VECTOR(9 DOWNTO 0);
      .wr_rst_busy( ), 
      .rd_rst_busy( )
   );   
    
    // Do a soft shutdown of the datamover on a reset request.  This should ensure the AXI bus does not get hung mid burst
    // and allow the datamover to be instantiated without using store-forward.  This will save blockrams, and is OK because we already
    // have buffering in the receive path
    logic s2mm_err;
    logic s2mm_halt;
    logic s2mm_halt_cmplt;
    logic datamover_rst;
    logic dma_rst_q;
    logic dma_rst_q2;
    logic dma_rst_red;
    
    always @ (posedge axim.clk) begin
    
        dma_rst_q <= dma_rst;
        dma_rst_q2 <= dma_rst_q;
        dma_rst_red <= dma_rst_q & ~dma_rst_q;
        
        
        s2mm_halt <= dma_rst;
        
        // Clear the datamover reset when the dma reset clears
        if (dma_rst == 1'b0) begin
            datamover_rst <= 1'b0;
        end
        else if (s2mm_halt_cmplt == 1'b1) begin
            // Wait for halt complete to trigger datamover reset
            datamover_rst <= 1'b1;
        end
    
    end   
   
   axi_datamover_256_0 axi_datamover_0_i (
      .m_axi_s2mm_aclk( axim.clk ), //
      .m_axi_s2mm_aresetn( ~datamover_rst ), // : IN STD_LOGIC;
      
      .s2mm_halt(s2mm_halt),                                    // input wire s2mm_halt
      .s2mm_halt_cmplt(s2mm_halt_cmplt),                        // output wire s2mm_halt_cmplt
      
      .s2mm_allow_addr_req(1'b1),                // input wire s2mm_allow_addr_req
      .s2mm_addr_req_posted(),              // output wire s2mm_addr_req_posted
      .s2mm_wr_xfer_cmplt(),                  // output wire s2mm_wr_xfer_cmplt
      .s2mm_ld_nxt_len(),                        // output wire s2mm_ld_nxt_len
      .s2mm_wr_len(),                                // output wire [7 : 0] s2mm_wr_len
      
      .s2mm_err(s2mm_err), // : OUT STD_LOGIC;
      
      .m_axis_s2mm_cmdsts_awclk( ctrl_if.clk ), // : IN STD_LOGIC;
      .m_axis_s2mm_cmdsts_aresetn( ~dma_rst ), // : IN STD_LOGIC;
      .s_axis_s2mm_cmd_tvalid( s_axis_s2mm_cmd_tvalid ), // : IN STD_LOGIC;
      .s_axis_s2mm_cmd_tready( s_axis_s2mm_cmd_tready ), // : OUT STD_LOGIC;
      .s_axis_s2mm_cmd_tdata( s_axis_s2mm_cmd_tdata), // : IN STD_LOGIC_VECTOR(79 DOWNTO 0);
      .m_axis_s2mm_sts_tvalid( m_axis_s2mm_sts_tvalid ), // : OUT STD_LOGIC;
      .m_axis_s2mm_sts_tready( m_axis_s2mm_sts_tready), // : IN STD_LOGIC;
      .m_axis_s2mm_sts_tdata( m_axis_s2mm_sts_tdata ), // : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
      .m_axis_s2mm_sts_tkeep( ), // : OUT STD_LOGIC_VECTOR(0 DOWNTO 0);
      .m_axis_s2mm_sts_tlast( ), // : OUT STD_LOGIC;
      
      .m_axi_s2mm_awaddr( axim.awaddr ), // : OUT STD_LOGIC_VECTOR(31 DOWNTO 0);
      .m_axi_s2mm_awlen( axim.awlen ), // : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
      .m_axi_s2mm_awsize( axim.awsize ), // : OUT STD_LOGIC_VECTOR(2 DOWNTO 0);
      .m_axi_s2mm_awburst( axim.awburst ), // : OUT STD_LOGIC_VECTOR(1 DOWNTO 0);
      .m_axi_s2mm_awprot( axim.awprot ), // : OUT STD_LOGIC_VECTOR(2 DOWNTO 0);
      .m_axi_s2mm_awcache( axim.awcache ), // : OUT STD_LOGIC_VECTOR(3 DOWNTO 0);
      .m_axi_s2mm_awuser(  ), // : OUT STD_LOGIC_VECTOR(3 DOWNTO 0);
      .m_axi_s2mm_awvalid( axim.awvalid ), // : OUT STD_LOGIC;
      .m_axi_s2mm_awready( axim.awready ), // : IN STD_LOGIC;
      .m_axi_s2mm_wdata( axim.wdata ), // : OUT STD_LOGIC_VECTOR(255 DOWNTO 0);
      .m_axi_s2mm_wstrb( axim.wstrb ), // : OUT STD_LOGIC_VECTOR(15 DOWNTO 0);
      .m_axi_s2mm_wlast( axim.wlast ), // : OUT STD_LOGIC;
      .m_axi_s2mm_wvalid( axim.wvalid ), // : OUT STD_LOGIC;
      .m_axi_s2mm_wready( axim.wready ), // : IN STD_LOGIC;
      .m_axi_s2mm_bresp( axim.bresp ), // : IN STD_LOGIC_VECTOR(1 DOWNTO 0);
      .m_axi_s2mm_bvalid( axim.bvalid ), // : IN STD_LOGIC;
      .m_axi_s2mm_bready( axim.bready ), // : OUT STD_LOGIC;
      
      .s_axis_s2mm_tdata( axis_gated.tdata ), // : IN STD_LOGIC_VECTOR(255 DOWNTO 0);
      .s_axis_s2mm_tkeep( axis_gated.tkeep ), // : IN STD_LOGIC_VECTOR(15 DOWNTO 0);
      .s_axis_s2mm_tlast( axis_gated.tlast ), // : IN STD_LOGIC;
      .s_axis_s2mm_tvalid( axis_gated.tvalid ), // : IN STD_LOGIC;
      .s_axis_s2mm_tready( axis_gated.tready ) // : OUT STD_LOGIC
   );

endmodule
