--------------------------------------------------------------------------------
-- CERN BE-CO-HT
--------------------------------------------------------------------------------
--
-- description: Dual port ram with scrubber.
--
--------------------------------------------------------------------------------
-- Copyright CERN 2014-2018
--------------------------------------------------------------------------------
-- Copyright and related rights are licensed under the Solderpad Hardware
-- License, Version 2.0 (the "License"); you may not use this file except
-- in compliance with the License. You may obtain a copy of the License at
-- http://solderpad.org/licenses/SHL-2.0.
-- Unless required by applicable law or agreed to in writing, software,
-- hardware and materials distributed under this 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.
--------------------------------------------------------------------------------

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

use work.wishbone_pkg.all;
use work.secded_32b_pkg.all;

entity hydra_dpram is
  generic(
    g_RAM_LOG_SIZE : natural := 12);  --  In bytes
  port(
    clk_i   : in  std_logic;
    --  Reset the whole logic.
    rst_n_i     : in  std_logic;
    --  Reset only the cpu + clear ram if requested.
    --  To be used in case of uncorrectable error.
    --  NOT to be used for recovery.
    cpu_rst_n_i : in  std_logic;

    --  WB ports
    porta_o     : out t_wishbone_slave_out;
    porta_i     : in  t_wishbone_slave_in;
    porta_ecc_i : in  std_logic_vector(6 downto 0); --  Corrupt ecc

    portb_o     : out t_wishbone_slave_out;
    portb_i     : in  t_wishbone_slave_in;

    --  Supervisor registers visible to the outside
    nbr_ecc_corr_o : out std_logic_vector(31 downto 0);
    nbr_scrub_corr_o : out std_logic_vector(31 downto 0);
    nbr_scrub_uncorr_last_o : out std_logic_vector(15 downto 0);
    nbr_scrub_cycle_o : out std_logic_vector(15 downto 0);
    scrub_en_i : std_logic;
    scrub_period_i : std_logic_vector(15 downto 0)
  );
end hydra_dpram;

architecture arch of hydra_dpram is
  signal addr : std_logic_vector (g_RAM_LOG_SIZE - 1 downto 2);
  signal en, we : std_logic;
  signal dati, dato, dato_r : std_logic_vector (31 downto 0);
  signal sel : std_logic_vector(3 downto 0);
  signal eccm : std_logic_vector (6 downto 0);
  signal done, err : std_logic;
  signal ecc_one, ecc_fatal, scrub_se, scrub_de, scrub_cycle : std_logic;
  signal scrub_act : std_logic;

  signal nbr_ecc_corr, nbr_scrub_corr : unsigned (31 downto 0);
  signal nbr_scrub_uncorr_last, nbr_scrub_uncorr_curr, nbr_scrub_cycle : unsigned (15 downto 0);

  type state_t is (S_IDLE, S_CLEAR, S_CLEAR_WAIT,
                   S_ACK_A, S_WAIT_A,
                   S_ACK_B, S_WAIT_B);
  signal state : state_t;
begin
  scrub_act <= scrub_en_i and cpu_rst_n_i;

  porta_o.err <= '0';
  porta_o.rty <= '0';
  porta_o.stall <= '0'; --  TODO
  
  portb_o.err <= '0';
  portb_o.rty <= '0';
  portb_o.stall <= '0'; --  TODO
  
  --  Mux between porta and portb, priority to porta.
  --  Fully synchronous, so it's slow!
  process (clk_i)
  begin
    if rising_edge(clk_i) then
      porta_o.ack <= '0';
      portb_o.ack <= '0';
      porta_o.dat <= (others => 'X');
      portb_o.dat <= (others => 'X');
      en <= '0';

      if rst_n_i = '0' then
        addr <= (others => '0');
        state <= S_CLEAR;
      else
        case state is
          when S_CLEAR =>
            we <= '1';
            sel <= "1111";
            dati <= (others => '0');
            eccm <= (others => '0');
            en <= '1';
            state <= S_CLEAR_WAIT;
          when S_CLEAR_WAIT =>
            if done = '1' then
              if addr = (addr'range => '1') then
                state <= S_IDLE;
              else
                addr <= std_logic_vector(unsigned(addr) + 1);  
                en <= '1';
              end if;
          end if;
        when S_IDLE =>
          if cpu_rst_n_i = '0' then
            state <= S_CLEAR;
          elsif (porta_i.cyc and porta_i.stb) = '1' then
            en <= '1';
            sel <= porta_i.sel;
            we <= porta_i.we;
            dati <= porta_i.dat;
            addr <= porta_i.adr(addr'range);
            eccm <= porta_ecc_i;
            state <= S_ACK_A;
          elsif (portb_i.cyc and portb_i.stb) = '1' then
            en <= '1';
            sel <= portb_i.sel;
            we <= portb_i.we;
            dati <= portb_i.dat;
            addr <= portb_i.adr(addr'range);
            eccm <= (others => '0');
            state <= S_ACK_B;
          end if;
        when S_ACK_A =>
          if (done and not err) = '1' then
            porta_o.ack <= '1';
            porta_o.dat <= dato;
            state <= S_WAIT_A;
          end if;
        when S_WAIT_A =>
          if (porta_i.cyc and porta_i.stb) = '0' then
            state <= S_IDLE;
          end if;
        when S_ACK_B =>
          if (done and not err) = '1' then
            portb_o.ack <= '1';
            portb_o.dat <= dato;
            state <= S_WAIT_B;
          end if;
        when S_WAIT_B =>
          if (portb_i.cyc and portb_i.stb) = '0' then
            state <= S_IDLE;
          end if;
        end case;
      end if;
    end if;
  end process;

  inst_ram: entity work.hydra_dram
    generic map (
      g_ram_log_size => g_ram_log_size,
      g_sim_seu_period => 0
      )
    port map (
      clk_i => clk_i,
      rst_n_i => rst_n_i,
      scrub_en_i => scrub_act,
      addr_i => addr,
      en_i => en,
      we_i => we,
      sel_i => sel,
      data_i => dati,
      data_o => dato,
      eccm_i => eccm,
      done_o => done,
      err_o => err,
      ecc_one_o => ecc_one,
      ecc_fatal_o => ecc_fatal,
      scrub_se_o => scrub_se,
      scrub_de_o => scrub_de,
      scrub_cycle_o => scrub_cycle,
      scrubber_period_i => scrub_period_i
      );

  p_ecc_stats: process (clk_i)
  begin
    if rising_edge(clk_i) then
      if rst_n_i = '0' then
        nbr_ecc_corr <= (others => '0');
        nbr_scrub_corr <= (others => '0');
        nbr_scrub_uncorr_last <= (others => '0');
        nbr_scrub_cycle <= (others => '0');
      else
        if ecc_one = '1' then
          nbr_ecc_corr <= nbr_ecc_corr + 1;
        end if;
        if scrub_se = '1' then
          nbr_scrub_corr <= nbr_scrub_corr + 1;
        end if;
        if scrub_de = '1' and nbr_scrub_uncorr_curr(15) /= '1' then
          nbr_scrub_uncorr_curr <= nbr_scrub_uncorr_curr + 1;
        end if;
        if scrub_cycle = '1' then
          nbr_scrub_uncorr_last <= nbr_scrub_uncorr_curr;
          nbr_scrub_uncorr_curr <= (others => '0');
          nbr_scrub_cycle <= nbr_scrub_cycle + 1;
        end if;
      end if;
    end if;
  end process;

  nbr_ecc_corr_o <= std_logic_vector(nbr_ecc_corr);
  nbr_scrub_corr_o <= std_logic_vector(nbr_scrub_corr);
  nbr_scrub_uncorr_last_o <= std_logic_vector(nbr_scrub_uncorr_last);
  nbr_scrub_cycle_o <= std_logic_vector(nbr_scrub_cycle);
end arch;
