-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
-- Copyright 2025 VLSI Solution Oy
-- SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1
--
-- Licensed under the Solderpad Hardware License v 2.1 (the "License");
-- you may not use this file except in compliance with the License, or,
-- at your option, the Apache License version 2.0.
-- You may obtain a copy of the License at
--
--    https://solderpad.org/licenses/SHL-2.1/
--
-- Unless required by applicable law or agreed to in writing, any work
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--                               RISCV core
--                                 V1.1
--
--
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

-- - - - - - - -
--  libraries
-- - - - - - - -
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;


entity dma is
  generic ( AXIDATA_W   : natural range 32 to 64 := 32 );
  port ( clk_i          : in std_logic;
         xrst_i         : in std_logic;
         -- to/from perips
         dma_rd_i       : in  std_logic;
         dma_wr_i       : in  std_logic;
         dma_addr_i     : in  std_logic_vector(4 downto 0);
         dma_wr_data_i  : in  std_logic_vector(31 downto 0);
         dma_rd_data_o  : out std_logic_vector(31 downto 0);
         dma_intr_o     : out std_logic;
         -- to/from axiwrapper
         resp_rdata_i   : in  std_logic_vector(AXIDATA_W-1 downto 0);
         dma1_rd_o      : out std_logic;
         dma1_wr_o      : out std_logic;
         dma1_len_o     : out std_logic_vector(7 downto 0);
         dma1_addr_o    : out std_logic_vector(31 downto 0);         -- [1:0] NC
         dma1_wdata_o   : out std_logic_vector(AXIDATA_W-1 downto 0);
         dma1_accept_i  : in  std_logic;
         dma1_ack_i     : in  std_logic;
         dma1_error_i   : in  std_logic
         );
end dma;


architecture rtl of dma is

  -- DMA Internal Registers
  signal dma_rd_en_q     : std_logic;
  signal dma_length_q    : unsigned(7 downto 0);
  signal dma1_len1_q     : std_logic_vector(7 downto 0);
  signal dma1_len2_q     : std_logic_vector(7 downto 0);
  signal dma_addr1_q     : unsigned(31 downto 0);
  signal dma_addr2_q     : unsigned(31 downto 0);
  signal dma1_rd_o_q     : std_logic;
  signal dma1_wr_o_q     : std_logic;
  signal dma1_addr1_q    : std_logic_vector(31 downto 0);            -- [1:0] NC
  signal dma1_addr2_q    : std_logic_vector(31 downto 0);
  signal dma1_addr_q     : std_logic_vector(31 downto 0);
  signal dma1_wdata_o_q  : std_logic_vector(AXIDATA_W-1 downto 0);
  signal dma_wr_done_q   : std_logic;
  signal rd_intr_q       : std_logic;
  signal wr_intr_q       : std_logic;
  signal din1_q          : std_logic_vector(AXIDATA_W-1 downto 0);

  -- Register Map
  -- set LEN in words 0:N
  constant DSP_LEN_c     : std_logic_vector(4 downto 0) := "00000";  -- 0x00
  constant DSP_1ADDR_c   : std_logic_vector(4 downto 0) := "00001";  -- 0x01
  constant DSP_2ADDR_c   : std_logic_vector(4 downto 0) := "00010";  -- 0x02
  constant DSP_CTRL_c    : std_logic_vector(4 downto 0) := "00011";  -- 0x03

  constant DMA_ENA_bc    : integer := 0;

  -- DMA State Machine
  type dma_state_t is (IDLE, RD_TRANSFER, WAIT_ACK, WR_START,
                       WR_TRANSFER, WR_WAIT_ACK);
  signal state_q         : dma_state_t;

begin

  p_write : process(clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        dma_length_q    <= (others => '0');
        dma_addr1_q     <= (others => '0');
        dma_addr2_q     <= (others => '0');
        dma_rd_en_q     <= '0';
      else
        if (state_q = IDLE and dma1_accept_i = '1' ) then
          dma_rd_en_q   <= '0';
        end if;
        if ( dma_wr_done_q = '1' ) then
          dma_length_q  <= dma_length_q-1;
          dma_addr1_q   <= dma_addr1_q + 1;
          dma_addr2_q   <= dma_addr2_q + 1;
          if (dma_length_q /= 0 ) then
            dma_rd_en_q <= '1';
          else
            dma_length_q <= (others => '0');
            dma_addr1_q  <= (others => '0');
            dma_addr2_q  <= (others => '0');
            dma_rd_en_q  <= '0';
          end if;
        end if;
        if dma_wr_i = '1' then
          case dma_addr_i is
            when DSP_LEN_c   =>
              dma_length_q <= unsigned(dma_wr_data_i(7 downto 0)); -- 0x00
            when DSP_1ADDR_c =>
              dma_addr1_q  <= unsigned(dma_wr_data_i);             -- 0x01
            when DSP_2ADDR_c =>
              dma_addr2_q  <= unsigned(dma_wr_data_i);             -- 0x02
            when DSP_CTRL_c =>
              dma_rd_en_q  <= dma_wr_data_i(DMA_ENA_bc) ;          -- 0x03, bc 1
            when others      =>
              null;
          end case;
        end if;
      end if;
    end if;
  end process p_write;


  p_read :  process(clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        -- Reset all signals
        dma_rd_data_o <= (others => '0');
      else
        if dma_rd_i = '1' then
          case dma_addr_i is
            when DSP_LEN_c =>
              -- Read Transfer Length
              dma_rd_data_o(7 downto 0) <= std_logic_vector(dma_length_q);
            when DSP_1ADDR_c =>
              -- Read Transfer Address
              dma_rd_data_o             <= std_logic_vector(dma_addr1_q);
            when DSP_2ADDR_c =>
              -- Read Transfer Address
              dma_rd_data_o             <= std_logic_vector(dma_addr2_q);
            when DSP_CTRL_c =>
              dma_rd_data_o(DMA_ENA_bc) <= dma_rd_en_q;
            when others  =>
              dma_rd_data_o             <= (others => '0');
          end case;
        end if;

      end if;
    end if;
  end process p_read;


  dma_intr_o <= rd_intr_q and wr_intr_q;


  p_rd : process(clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        dma1_rd_o_q    <= '0';
        dma1_len1_q    <= (others => '0');
        dma1_addr1_q   <= (others => '0');
        din1_q         <= (others => '0');
        state_q        <= IDLE;
        --
        dma1_wr_o_q    <= '0';
        dma1_len2_q    <= (others => '0');
        dma1_addr2_q   <= (others => '0');
        dma1_wdata_o_q <= (others => '0');
        --
        wr_intr_q      <= '0';
        rd_intr_q      <= '0';
      else
        case state_q is
          when IDLE =>
            wr_intr_q        <= '0';
            rd_intr_q        <= '0';
            --
            dma_wr_done_q    <= '0';
            if ( dma_rd_en_q = '1' ) then
              dma1_rd_o_q    <= '1';
              dma1_len1_q    <= X"00";     -- dma len is N:0 in axiwrapper
              dma1_addr1_q   <= std_logic_vector(dma_addr1_q);
              if dma1_accept_i = '1' then
                dma1_rd_o_q  <= '0';
                state_q      <= RD_TRANSFER;
              end if;
            end if;

          when RD_TRANSFER =>
            -- ack signal start with accept signal,
            -- actual read happens in WAIT_ACK State
            if dma1_ack_i = '1' then
              din1_q         <= resp_rdata_i;
              state_q        <= WAIT_ACK;
            end if;

          when WAIT_ACK =>
            if dma1_ack_i = '0' then
              dma1_rd_o_q    <= '0';
              dma1_len1_q    <= X"00";
              dma1_addr1_q   <= (others => '0');
              rd_intr_q      <= '1';
              state_q        <= WR_START;
            end if;

          when WR_START =>
            dma1_wr_o_q      <= '1';
            dma1_len2_q      <= X"00";
            -- addr update before WR state in axiwrapper
            dma1_addr2_q  <= std_logic_vector(dma_addr2_q);
            dma1_wdata_o_q   <= din1_q;   -- first word in
            if dma1_accept_i = '1' then
              dma1_wr_o_q    <= '0';
              -- different RD/WR states (RD_TRANSFER WR_TRANSFER) because
              -- accept/ack signal different for RD/WR in axi_wrapper
              state_q        <= WR_TRANSFER;
            end if;

          when WR_TRANSFER =>
            -- ack 1 after WR is done
            if dma1_ack_i = '1' then
              state_q        <= WR_WAIT_ACK;
            end if;

          when WR_WAIT_ACK =>
            if dma1_ack_i = '0' then
              dma1_wr_o_q    <= '0';
              dma1_len2_q    <= X"00";
              dma1_addr2_q   <= (others => '0');
              state_q <= IDLE;
              --
              dma_wr_done_q  <= '1';
              wr_intr_q      <= '1';
            end if;

        end case;
      end if;
    end if;
  end process p_rd;


  dma1_addr_q   <= dma1_addr1_q or dma1_addr2_q;
  dma1_rd_o     <= dma1_rd_o_q;
  dma1_wr_o     <= dma1_wr_o_q;
  dma1_len_o    <= dma1_len1_q or dma1_len2_q;
  dma1_addr_o   <= dma1_addr_q(28 downto 0) & "000" when (AXIDATA_W = 64) else
                   dma1_addr_q(29 downto 0) & "00";
  dma1_wdata_o  <= dma1_wdata_o_q;

end rtl;
