`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////
//
// Author         : Torry Akins
// Creation Date  : 06/04/2024
// File Name      : dma_engine_256.sv
// Tool Version   : 2021.2
// Description    : DMA Engine (256 bits wide)
//
// Copyright (c) 2024 Wide Swath Research, LLC.
// This design is confidential and is the proprietary property
// of Wide Swath Research, LLC.
//
// Design licensed for use by Aloft Sensing, Inc.
//
//////////////////////////////////////////////////////////////////////////////

module dma_engine_256 (
      axi4l_intf.slave axi,
      axi4s_intf.slave axis,
      axi4_intf.master axim
   );
   
   // incoming pipeline of write address
   reg awaddr_valid;
   reg [axi.AXI_ADDR_WIDTH-1:0] awaddr;

   // incoming pipeline of write data
   reg wdata_valid;
   reg [((axi.AXI_DATA_WIDTH/8)-1):0] wstrb;
   reg [axi.AXI_DATA_WIDTH-1:0] wdata;
   
   // incoming pipeline of read address
   reg araddr_valid;
   reg [axi.AXI_ADDR_WIDTH:0] araddr;
   
   // outgoing pipeline of read data
   reg rvalid;
   reg [axi.AXI_DATA_WIDTH-1:0] rdata;
      
   integer byte_index;      
      
   reg [axi.AXI_DATA_WIDTH-1:0] ctrl_reg[3:0];
   wire [axi.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;


   // ready while data hasn't been latched and still waiting
   // for acknowledgement to our response
   assign axi.awready = !awaddr_valid && !axi.bvalid && axi.resetn;
   assign axi.wready = !wdata_valid && !axi.bvalid && axi.resetn;
   
	always @( posedge axi.clk )
   begin
      if ( axi.resetn == 1'b0 ) begin
         awaddr_valid <= 1'b0; 
         awaddr <= 'b0; 
         wdata_valid <= 1'b0;
         wstrb <= 'b0;
         wdata <= 'b0;    
         axi.bvalid <= 1'b0;
         axi.bresp  <= 2'b0;                       
	   end
	   else begin   
         // latch awaddr and valid signal
         if (axi.awready && axi.awvalid) begin 
            awaddr_valid <= 1'b1;   
            awaddr <= axi.awaddr; 
         end
         
         // latch wirte data and valid signal
         if (axi.wready && axi.wvalid) begin 
            wdata_valid <= 1'b1;   
            wstrb <= axi.wstrb;
            wdata <= axi.wdata;    
         end    
         
         // clear valids when both high;
         // data needs to be consumed on this same condition
         if (awaddr_valid && wdata_valid && !axi.bvalid) begin
             awaddr_valid <= 1'b0; 
             wdata_valid <= 1'b0;
             
             axi.bvalid <= 1'b1;
             axi.bresp  <= 2'b0; // 'OKAY' response          
         end  
         
         // clear bvalid when it has been acknowledged
         if (axi.bvalid && axi.bready) begin
            axi.bvalid <= 1'b0;
         end
      end
	end
	
	// ready while data hasn't been latched and still waiting
   // for acknowledgement to our response
	assign axi.arready = !araddr_valid && !axi.rvalid && axi.resetn;
	   
	always @( posedge axi.clk )
   begin
      if ( axi.resetn == 1'b0 ) begin
         araddr_valid <= 1'b0; 
         araddr <= 'b0; 
         axi.rvalid <= 1'b0;
         axi.rresp  <= 2'b0;                       
         axi.rdata <= 'b0;    
	   end
	   else begin   
         // latch araddr and valid signal	   
         if (axi.arready && axi.arvalid) begin 
            araddr_valid <= 1'b1;   
            araddr <= axi.araddr; 
         end
                  
         // if address is valid and rdata has been latched;
         // set axi bus rdata and valid signal
         if (araddr_valid && rvalid && !axi.rvalid) begin
             araddr_valid <= 1'b0; 
             
             axi.rvalid <= 1'b1;
             axi.rresp  <= 2'b0; // 'OKAY' response     
             axi.rdata <= rdata;     
         end  
         
         // clear rvalid when it has been acknowledged
         if (axi.rvalid && axi.rready) begin
            axi.rvalid <= 1'b0;
         end
      end
	end
	
	always @( posedge axi.clk )
   begin
      if ( axi.resetn == 1'b0 ) 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;

         rvalid <= 1'b0;                      
         rdata <= 'b0; 
	   end
	   else begin   
	   
	      ctrl_fifo_wren <= 'b0;
	      // everything is valid;  latch data to the correct location
         if (awaddr_valid && wdata_valid) begin
            for ( byte_index = 0; byte_index <= (axi.AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) begin
               if ( wstrb[byte_index] == 1 ) begin            
                  if ( awaddr[7:0] == 'h00 ) 
                     ctrl_reg[0][(byte_index*8) +: 8] <= wdata[(byte_index*8) +: 8];
      
                  if ( awaddr[7:0] == 'h04 ) 
                     ctrl_reg[1][(byte_index*8) +: 8] <= wdata[(byte_index*8) +: 8];
      
                  if ( awaddr[7:0] == 'h08 ) 
                     ctrl_reg[2][(byte_index*8) +: 8] <= wdata[(byte_index*8) +: 8];
      
                  if ( awaddr[7:0] == 'h0C ) 
                     ctrl_reg[3][(byte_index*8) +: 8] <= wdata[(byte_index*8) +: 8];
     
                  if ( awaddr[7:0] == 'h20 ) 
                     ctrl_fifo_din[(byte_index*8) +: 8] <= wdata[(byte_index*8) +: 8];
                     
                  if ( awaddr[7:0] == 'h24 ) 
                     ctrl_fifo_din[(byte_index*8)+32 +: 8] <= wdata[(byte_index*8) +: 8];
                                          
                  if ( awaddr[7:0] == 'h28 ) begin
                     ctrl_fifo_din[(byte_index*8)+64 +: 8] <= wdata[(byte_index*8) +: 8]; 
                     if (byte_index == 0) ctrl_fifo_wren <= 1'b1;
                  end                    
               end  
            end          
         end  
         
         stat_fifo_rden <= 1'b0;
         
         // by default rvalid is low; unless read address is valid;
         // latch appropriate data and set valid signal
         rvalid <= 1'b0;
         if (araddr_valid  && !rvalid) begin
            if ( araddr[7:0] == 'h0 )
               rdata <= ctrl_reg[0];
            if ( araddr[7:0] == 'h4 ) 
               rdata <= ctrl_reg[1];   
            if ( araddr[7:0] == 'h8 ) 
               rdata <= ctrl_reg[2]; 
            if ( araddr[7:0] == 'hC ) 
               rdata <= ctrl_reg[3];    
            if ( araddr[7:0] == 'h10 ) 
               rdata <= status_reg[0];
            if ( araddr[7:0] == 'h14 ) 
               rdata <= status_reg[1];   
            if ( araddr[7:0] == 'h18 ) 
               rdata <= status_reg[2]; 
            if ( araddr[7:0] == 'h1C ) 
               rdata <= status_reg[3];    
               
            if ( araddr[7:0] == 'h20 ) 
               rdata <= stat_fifo_dout[31:0];                                              
   
            if ( araddr[7:0] == 'h24 ) begin 
               rdata <= stat_fifo_dout[63:32];
            end
               
            if ( araddr[7:0] == 'h28 ) begin 
               rdata <= {24'b0, stat_fifo_dout[71:64]};
               stat_fifo_rden <= 1'b1;   
            end

            if ( araddr[7:0] == 'h2C) begin
               rdata <= status_reg[4];
            end
                           
            rvalid <= 1'b1;
         end               
      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];
  
   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 axi.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 
                        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.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 

   dma_ctrl_status_fifo_0 ctrl_fifo_i (
      .clk( axi.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;
      .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( axi.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;
//            s2mm_halt <= 1'b0;
        end
//        else if (dma_rst_red == 1'b1) begin
//            // Trigger soft shutdown when the dma reset signal goes high
//            s2mm_halt <= 1;
//        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( ~dma_rst ), // : IN STD_LOGIC;
      .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( axi.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
