-------------------------------------------------------------------------------
-- Title      : DMA MBLT Booster
-- Project    : 16z002-01
-------------------------------------------------------------------------------
-- File       : dma_mblt_boost.vhd
-- Author     : Grzegorz Daniluk <grzegorz.daniluk@cern.ch>
-- Company    : CERN (BE-CO-HT)
-------------------------------------------------------------------------------
-- Description:
-- Module boosts MBLT and BLT readout perfomance by streaming data words to PCIe
-- as soon as they are read from VME interface.
-------------------------------------------------------------------------------
--
-- Copyright (c) 2018 CERN
--
-- This source file is free software; you can redistribute it
-- and/or modify it under the terms of the GNU Lesser General
-- Public License as published by the Free Software Foundation;
-- either version 2.1 of the License, or (at your option) any
-- later version.
--
-- This source is distributed in the hope that it will be
-- useful, but WITHOUT ANY WARRANTY; without even the implied
-- warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
-- PURPOSE.  See the GNU Lesser General Public License for more
-- details.
--
-- You should have received a copy of the GNU Lesser General
-- Public License along with this source; if not, download it
-- from http://www.gnu.org/licenses/lgpl-2.1.html
--
-------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity dma_mblt_boost is
  port (
    rst_i  : in std_logic;
    clk_i  : in std_logic;
    en_i   : in std_logic;  
    active_o : out std_logic;

    dma_dest_adr_i     : in std_logic_vector(31 downto 2);
    dma_reached_size_i : in std_logic;
    dma_boundary_i     : in std_logic;

    -- DMA FIFO interface
    fifo_rd_o         : out std_logic;
    fifo_dat_i        : in std_logic_vector(31 downto 0);
    fifo_almost_empty : in std_logic;
    fifo_almost_full  : in std_logic;
    fifo_empty        : in std_logic;
    fifo_full         : in std_logic;

    -- dedicated PCI wb-master for streaming data
    pci_cyc_o      : out std_logic;
    pci_stb_o      : out std_logic;
    pci_we_o       : out std_logic;
    pci_sel_o      : out std_logic_vector(3 downto 0);
    pci_tga_o      : out std_logic_vector(8 downto 0);
    pci_cti_o      : out std_logic_vector(2 downto 0);
    pci_adr_o      : out std_logic_vector(31 downto 0);
    pci_dat_o      : out std_logic_vector(31 downto 0);
    pci_dat_i      : in  std_logic_vector(31 downto 0);
    pci_ack_i      : in  std_logic;
    pci_err_i      : in  std_logic
  );
end dma_mblt_boost;

architecture behav of dma_mblt_boost is

  type t_boost_state is (IDLE, WAIT_DAT, SEND_DAT, WAIT_DMA_MSTR, DMA_PAUSE);
  signal state : t_boost_state;

  signal dma_adr : unsigned(31 downto 0);
  signal fifo_rd : std_logic;
  signal pci_stb    : std_logic;
  signal stb_sel    : std_logic;
  signal dma_boundary : std_logic;
  signal boundary_served : std_logic;
  signal stuck_sig : std_logic;
  signal cnt : unsigned (7 downto 0);

  attribute noprune : boolean;
  attribute noprune of stuck_sig : signal is true;
begin

  pci_we_o  <= '1';
  pci_sel_o <= (others=>'1');
  pci_tga_o <= (others=>'0');
  pci_adr_o <= std_logic_vector(dma_adr);
  pci_dat_o <= fifo_dat_i;
  pci_cti_o <= "010";

  fifo_rd_o <= fifo_rd;
  pci_stb_o <= pci_stb when (stb_sel = '0') else
               not pci_ack_i;

  process(clk_i)
  begin
    if rising_edge(clk_i) then
      if rst_i = '1' then
        fifo_rd   <= '0';
        pci_cyc_o <= '0';
        pci_stb   <= '0';
        active_o  <= '0';
        dma_adr   <= (others=>'0');
        stb_sel   <= '0';
        state     <= IDLE;
        --dbg 
        stuck_sig <= '0';
        cnt <= (others=>'0');
      else
        case state is
          -----------------------------------
          when IDLE =>
            fifo_rd   <= '0';
            pci_cyc_o <= '0';
            pci_stb   <= '0';
            dma_adr   <= (others=>'0');
            active_o  <= '0';
            stb_sel   <= '0';
            stuck_sig <= '0';
            cnt <= (others=>'0');

            if (en_i = '1') then
              active_o <= '1';
              dma_adr  <= unsigned(dma_dest_adr_i & "00");
              state    <= WAIT_DAT;
            end if;
          -----------------------------------
          when WAIT_DAT =>
            fifo_rd   <= '0';
            pci_cyc_o <= '1';
            pci_stb   <= '0';
            stb_sel   <= '0';

            if cnt = x"2f" then
              stuck_sig <= '1';
            else
              stuck_sig <= '0';
              cnt <= cnt + 1;
            end if;

            if (fifo_empty = '0') then
              -- there is some data in FIFO, let's send it to PCIe
              fifo_rd   <= '1';
              cnt <= (others=>'0');
              state <= SEND_DAT;
            elsif (fifo_empty = '1' and dma_boundary = '1' and en_i = '1') then
              fifo_rd   <= '0';
              cnt <= (others=>'0');
              state <= DMA_PAUSE;
            elsif (fifo_empty = '1' and en_i = '0') then
              -- the transfer is done and there are no more words in FIFO
              -- there is nothing to wait for
              fifo_rd   <= '0';
              state  <= IDLE;
            end if;
          -----------------------------------
          when SEND_DAT =>
            fifo_rd   <= '0';
            pci_cyc_o <= '1';
            stb_sel <= '0';
            if (fifo_rd = '1') then
              pci_stb   <= '1';
            else
              pci_stb   <= '0';
            end if;

            if cnt = x"0f" then
              stuck_sig <= '1';
            else
              stuck_sig <= '0';
              cnt <= cnt + 1;
            end if;

            ----
            --if (pci_ack_i = '1' and fifo_rd = '0' and dma_reached_size_i = '1' and fifo_empty = '1' and en_i ='1') then
            --  state  <= WAIT_DMA_MSTR;
            --elsif (pci_ack_i = '1' and fifo_rd = '0' and dma_reached_size_i = '1' and fifo_empty = '1' ) then
            --  state  <= IDLE;
            --elsif (pci_ack_i = '1' and fifo_rd = '0') then
            if (pci_ack_i = '1' and fifo_rd = '0') then
              dma_adr <= dma_adr + 4;
              cnt <= (others=>'0');
              state   <= WAIT_DAT;
            end if;
          -----------------------------------
          when WAIT_DMA_MSTR =>
            fifo_rd   <= '0';
            pci_cyc_o <= '0';
            pci_stb   <= '0';
            stb_sel   <= '0';
            active_o  <= '0';
            stuck_sig <= '0';
            cnt <= (others=>'0');

            if en_i = '0' then
              state <= IDLE;
            end if;
          -----------------------------------
          when DMA_PAUSE =>
            fifo_rd   <= '0';
            pci_cyc_o <= '0';
            pci_stb   <= '0';
            active_o  <= '1';
            stb_sel   <= '0';
            stuck_sig <= '0';
            cnt <= (others=>'0');

            if (en_i = '1') then
              state    <= WAIT_DAT;
            end if;
          -----------------------------------
        end case;
      end if;
    end if;
  end process;

  process(clk_i)
  begin
    if rising_edge(clk_i) then
      if rst_i = '1' or state = IDLE then
        dma_boundary <= '0';
        boundary_served <= '0';
      elsif state = DMA_PAUSE then
        dma_boundary <= '0';
        boundary_served <= '1';
      elsif dma_boundary_i = '1' and boundary_served = '0' then
        -- new dma_boundary, let's use it to send data accumulated in PCIe buffer
        dma_boundary <= '1';
      elsif dma_boundary = '0' and dma_boundary_i = '0' then
        -- if the boundary is served, and it's no longer signaled, clean served
        -- flag to be ready for a new boundary in the future
        boundary_served <= '0';
      end if;
    end if;
  end process;

end behav;
