--------------------------------------------------------------------------------
-- CERN BE-CO-HT
-- Mock Turtle
-- https://gitlab.cern.ch/coht/mockturtle
--------------------------------------------------------------------------------
--
-- unit name:   hydra_dram
--
-- description: a 32b RAM for rad-tol systems (with ECC and scrubing)
--   2 ports: 1 ro, 1 wo.
--
--------------------------------------------------------------------------------
-- 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.secded_32b_pkg.all;

entity hydra_dram is
  generic (
    g_RAM_LOG_SIZE : natural := 12;  --  In bytes
    g_SIM_SEU_PERIOD : natural := 0);
  port (
    --  Note: only writes are allowed during reset.
    clk_i            : in  std_logic;
    rst_n_i          : in  std_logic;

    --  Enable scrubber
    scrub_en_i       : in  std_logic;

    --  Read port
    --  An access is performed iff EN_I is active.
    --  If ERR_O is set, RDONE_O must be ignored, the error will be fixed.
    --  RDONE_O is a pulse.
    addr_i      : in  std_logic_vector(g_RAM_LOG_SIZE - 1 downto 2);
    en_i        : in  std_logic;
    we_i        : in  std_logic;
    sel_i       : in  std_logic_vector(3 downto 0);
    data_i      : in  std_logic_vector(31 downto 0);
    data_o      : out std_logic_vector(31 downto 0);
    eccm_i      : in  std_logic_vector(6 downto 0);
    done_o      : out std_logic;
    err_o       : out std_logic;

    --  For statistics
    ecc_one_o    : out std_logic;
    ecc_fatal_o  : out std_logic;
    scrub_se_o   : out std_logic;
    scrub_de_o   : out std_logic;
    scrub_cycle_o : out std_logic;

    --  Scrubber
    scrubber_period_i : in  std_logic_vector(15 downto 0)
  );
end hydra_dram;

architecture arch of hydra_dram is
  constant RAM_WSIZE : natural := 2 ** (g_RAM_LOG_SIZE - 2);
  type t_ram39_type is array(natural range <>) of std_logic_vector(38 downto 0);

  signal addr : std_logic_vector(g_RAM_LOG_SIZE - 1 downto 2);
  signal rdata_ecc, wdata_ecc : std_logic_vector(38 downto 0);
  signal wen, ren, ren_d : std_logic;

  signal recc, rsyndrome : std_logic_vector(6 downto 0);
  signal rerr, rerr_one : std_logic;
  signal r_done, n_r_done, r_done_d : std_logic;
  signal p_done, n_p_done : std_logic;
  signal w_done, n_w_done : std_logic;

  signal n_ecc_one, n_ecc_fatal : std_logic;
  signal last_addr, n_last_addr : std_logic_vector(g_RAM_LOG_SIZE - 1 downto 2);
  signal last_we, n_last_we : std_logic;
  signal last_sel, n_last_sel : std_logic_vector(3 downto 0);

  --  The scrubber counter has an extra bit to signal a new cycle.
  signal scrub_addr_ext : std_logic_vector(g_RAM_LOG_SIZE - 1 + 1 downto 2);
  alias scrub_cycle : std_logic is scrub_addr_ext (g_RAM_LOG_SIZE);
  alias scrub_addr : std_logic_vector(g_RAM_LOG_SIZE - 1 downto 2) is scrub_addr_ext (g_RAM_LOG_SIZE - 1 downto 2);
  signal scrub_cycle_d1, scrub_cycle_d2 : std_logic;

  signal scrub_counter : unsigned(15 downto 0);
  signal scrub_rd, scrub_done, scrub_done_d, n_scrub_done : std_logic;
  signal n_scrub_de, n_scrub_se : std_logic;

  type state_t is (S_READ, S_REWRITE);
  signal state, n_state : state_t;
begin
  --  The raw ram.
  p_ram: process (clk_i)
  is
    variable ram : t_ram39_type(0 to RAM_WSIZE - 1) := (others => (others => '1'));
    variable d : std_logic_vector(38 downto 0);
    variable err : std_logic_vector(38 downto 0) := (0 => '1', others => '0');
    variable sim_cnt : natural;
  begin
    if rising_edge(clk_i) then
      if wen = '1' then
        ram (to_integer(unsigned(addr))) := wdata_ecc;
      end if;
      if ren = '1' then
        d := ram (to_integer(unsigned(addr)));

        --  Simulate errors.
        if g_SIM_SEU_PERIOD /= 0 then
          if sim_cnt < g_SIM_SEU_PERIOD then
            sim_cnt := sim_cnt + 1;
          else
            sim_cnt := 0;
            d := d xor err;
            err := err(err'left - 1 downto 0) & err(err'left);
          end if;
        end if;

        rdata_ecc <= d;
      else
        rdata_ecc <= (others => 'X');
      end if;
      ren_d <= ren;
    end if;
  end process;

  data_o <= rdata_ecc (31 downto 0);

  recc <= f_calc_ecc(rdata_ecc (31 downto 0));
  rsyndrome <= recc xor rdata_ecc(38 downto 32);
  rerr <= f_ecc_errors(rsyndrome) and ren_d;
  rerr_one <= f_ecc_one_error(rsyndrome) and ren_d;

  --  Only single errors are reported, double errors are fatal.
  err_o <= rerr_one;
  done_o <= r_done or w_done;

  --  We need to delay scrub_cycle_o so that it happens after a possible
  --  error on the last address.
  scrub_cycle_o <= scrub_cycle_d2;

  p_scrub: process (clk_i)
  begin
    if rising_edge(clk_i) then
      if rst_n_i = '0' or scrub_cycle_d2 = '1' then
        scrub_counter <= unsigned(scrubber_period_i);
        scrub_addr_ext <= (others => '0');
        scrub_rd <= '0';
        scrub_cycle_d2 <= '0';
        scrub_cycle_d1 <= '0';
      else
        scrub_cycle_d1 <= scrub_cycle;
        scrub_cycle_d2 <= scrub_cycle_d1;
        if scrub_done = '1' and rerr_one = '0' then
          scrub_counter <= unsigned(scrubber_period_i);
          scrub_addr_ext <= std_logic_vector(unsigned(scrub_addr_ext) + 1);
          scrub_rd <= '0';
        elsif scrub_en_i = '1' then
          if scrub_counter = (scrub_counter'range => '0') then
            scrub_rd <= '1';
          else
            scrub_counter <= scrub_counter - 1;
          end if;
        end if;
      end if;
    end if;
  end process;

  p_ctrl: process (state,
                   p_done, r_done, scrub_done, rerr,
                   rerr_one, rdata_ecc, rsyndrome, last_addr, last_we, last_sel, eccm_i,
                   en_i, addr_i, we_i, data_i, sel_i, r_done_d,
                   scrub_rd, scrub_addr, scrub_done_d, scrub_en_i)
  is
    variable d : std_logic_vector(31 downto 0);
  begin
    wen <= '0';
    addr <= (others => 'X');
    wdata_ecc <= (others => 'X');
    n_w_done <= '0';
    ren <= '0';
    n_r_done <= '0';
    n_p_done <= '0';
    n_scrub_done <= '0';
    n_last_we <= '0';
    n_last_sel <= "0000";

    n_ecc_one <= '0';
    n_ecc_fatal <= '0';
    n_scrub_se <= '0';
    n_scrub_de <= '0';
    n_state <= state;
    n_last_addr <= (others => 'X');

    case state is
      when S_READ =>
        if (p_done = '1' or r_done = '1' or scrub_done = '1') and rerr = '1' then
          --  There was an error on the last access.
          --  Write to fix it.
          addr <= last_addr;
          n_last_addr <= last_addr;
          n_last_we <= last_we;
          n_last_sel <= last_sel;
          if rerr_one = '1' then
            --  Correctable: correct it.
            n_scrub_se <= scrub_done;
            n_ecc_one <= p_done or r_done;
            wdata_ecc <= f_fix_error(rsyndrome, rdata_ecc(38 downto 32), rdata_ecc(31 downto 0));
            wen <= '1';
            n_state <= S_REWRITE;
          else
            --  Uncorrectable.  Do not fix it.
            n_scrub_de <= scrub_done;
            n_ecc_fatal <= p_done or r_done;
          end if;
        elsif last_we = '1' then
          assert last_sel /= "1111";
          --  The partial write;
          d := rdata_ecc(31 downto 0);
          for i in 3 downto 0 loop
            if last_sel (i) = '1' then
              d(8 * i + 7 downto 8 * i) := data_i(8 *i + 7 downto 8 * i);
            end if;
          end loop;
          wen <= '1';
          wdata_ecc <= (f_calc_ecc (d) & d);
          addr <= last_addr;
          n_w_done <= '1';
        elsif scrub_rd = '1' and scrub_done = '0' then
          -- scrubber
          addr <= scrub_addr;
          ren <= '1';
          n_scrub_done <= '1';
          n_last_addr <= scrub_addr;
        elsif en_i = '1' then
          addr <= addr_i;
          n_last_addr <= addr_i;
          n_state <= S_READ;
          if we_i = '1' then
            --  Write
            if sel_i = "1111" then
              --  Full write
              wen <= '1';
              wdata_ecc <= (f_calc_ecc (data_i) xor eccm_i) & data_i;
              n_w_done <= '1';
            else
              --  Partial write: first read the word
              ren <= '1';
              n_last_we <= we_i;
              n_last_sel <= sel_i;
              n_p_done <= '1';
            end if;
          else
            --  Read.
            ren <= '1';
            n_r_done <= '1';
          end if;
        elsif scrub_en_i = '1' and scrub_cycle = '0' then
          -- scrub if idle
          addr <= scrub_addr;
          ren <= '1';
          n_scrub_done <= '1';
          n_last_addr <= scrub_addr;
        end if;
      when S_REWRITE =>
        --  Reread
        addr <= last_addr;
        ren <= '1';
        n_r_done <= r_done_d;
        n_scrub_done <= scrub_done_d;
        n_last_addr <= last_addr;
        n_last_we <= last_we;
        n_last_sel <= last_sel;
        n_state <= S_READ;
    end case;
  end process;

  p_ctrl_reg: process (clk_i)
  begin
    if rising_edge(clk_i) then
      if rst_n_i = '0' then
        r_done <= '0';
        r_done_d <= '0';
        scrub_done <= '0';
        scrub_done_d <= '0';
        p_done <= '0';
        state <= S_READ;
        last_addr <= (others => 'X');
        last_we <= '0';
        last_sel <= (others => 'X');

        ecc_one_o <= '0';
        ecc_fatal_o <= '0';
        scrub_se_o <= '0';
        scrub_de_o <= '0';
      else
        r_done_d <= r_done;
        r_done <= n_r_done;
        p_done <= n_p_done;
        scrub_done_d <= scrub_done;
        scrub_done <= n_scrub_done;

        state <= n_state;
        last_addr <= n_last_addr;
        last_we <= n_last_we;
        last_sel <= n_last_sel;

        ecc_one_o <= n_ecc_one;
        ecc_fatal_o <= n_ecc_fatal;
        scrub_se_o <= n_scrub_se;
        scrub_de_o <= n_scrub_de;
      end if;

      w_done <= n_w_done;
    end if;
  end process;
end arch;