--------------------------------------------------------------------------------
-- CERN BE-CO-HT
-- General Cores Library
-- https://www.ohwr.org/projects/general-cores
--------------------------------------------------------------------------------
--
-- unit name:   generic_dpram_sameclock
--
-- description: True dual-port synchronous RAM for Xilinx FPGAs with:
-- - configurable address and data bus width
-- - byte-addressing mode (data bus width restricted to multiple of 8 bits)
--
-- todo:
-- - loading initial contents from file
-- - add support for read-first/write-first address conflict resulution (only
--   supported by Xilinx in VHDL templates)
--
--------------------------------------------------------------------------------
-- Copyright CERN 2011-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 std.textio.all;

library work;
use work.genram_pkg.all;
use work.memory_loader_pkg.all;

entity generic_dpram_sameclock is

  generic (
    g_data_width : natural := 32;
    g_size       : natural := 16384;

    g_with_byte_enable         : boolean := false;
    g_addr_conflict_resolution : string  := "read_first";
    g_init_file                : string  := "";
    g_fail_if_file_not_found   : boolean := true
    );

  port (
    rst_n_i : in std_logic := '1';      -- synchronous reset, active LO

    -- Port A
    clk_i : in std_logic;

    bwea_i : in  std_logic_vector((g_data_width+7)/8-1 downto 0);
    wea_i  : in  std_logic;
    aa_i   : in  std_logic_vector(f_log2_size(g_size)-1 downto 0);
    da_i   : in  std_logic_vector(g_data_width-1 downto 0);
    qa_o   : out std_logic_vector(g_data_width-1 downto 0);

    -- Port B
    bweb_i : in  std_logic_vector((g_data_width+7)/8-1 downto 0);
    web_i  : in  std_logic;
    ab_i   : in  std_logic_vector(f_log2_size(g_size)-1 downto 0);
    db_i   : in  std_logic_vector(g_data_width-1 downto 0);
    qb_o   : out std_logic_vector(g_data_width-1 downto 0)
    );

end generic_dpram_sameclock;



architecture syn of generic_dpram_sameclock is

  constant c_num_bytes : integer := (g_data_width + 7)/8;


  type t_ram_type is array(0 to g_size-1) of std_logic_vector(g_data_width-1 downto 0);

  impure function f_file_to_ramtype return t_ram_type is
    variable tmp    : t_ram_type;
    variable n, pos : integer;
    variable mem32  : t_ram32_type(0 to g_size-1);
    variable mem16  : t_ram16_type(0 to g_size-1);
    variable mem8   : t_ram8_type(0 to g_size-1);
    variable arr    : t_meminit_array(0 to g_size-1, g_data_width-1 downto 0);
  begin
    -- If no file was given, there is nothing to convert, just return
    if (g_init_file = "" or g_init_file = "none") then
      tmp := (others=>(others=>'0'));
      return tmp;
    end if;

    arr := f_load_mem_from_file(g_init_file, g_size, g_data_width, g_fail_if_file_not_found);
    pos := 0;
    while(pos < g_size)loop
      n := 0;
      -- avoid ISE loop iteration limit
      while (pos < g_size and n < 4096) loop
        for i in 0 to g_data_width-1 loop
          tmp(pos)(i) := arr(pos, i);
        end loop;  -- i
        n   := n+1;
        pos := pos + 1;
      end loop;
    end loop;
    return tmp;
  end f_file_to_ramtype;

  function f_is_synthesis return boolean is
  begin
    -- synthesis translate_off
    return false;
    -- synthesis translate_on
    return true;
  end f_is_synthesis; 


  shared variable ram : t_ram_type := f_file_to_ramtype;

  signal s_we_a     : std_logic_vector(c_num_bytes-1 downto 0);
  signal s_we_b     : std_logic_vector(c_num_bytes-1 downto 0);

  signal wea_rep, web_rep : std_logic_vector(c_num_bytes-1 downto 0);

begin

  wea_rep <= (others => wea_i);
  web_rep <= (others => web_i);

  s_we_a <= bwea_i and wea_rep;
  s_we_b <= bweb_i and web_rep;

  gen_with_byte_enable_readfirst : if(g_with_byte_enable = true and (g_addr_conflict_resolution = "read_first" or
                                                                     g_addr_conflict_resolution = "dont_care")) generate

    Port_A: process (clk_i)
    begin
      if rising_edge(clk_i) then
        qa_o <= ram(f_check_bounds(to_integer(unsigned(aa_i)), 0, g_size-1));
        for i in 0 to c_num_bytes-1 loop
          if s_we_a(i) = '1' then
            ram(f_check_bounds(to_integer(unsigned(aa_i)), 0, g_size-1))((i+1)*8-1 downto i*8) := da_i((i+1)*8-1 downto i*8);
          end if;
        end loop;
      end if;
    end process;

    Port_B: process (clk_i)
    begin
      if rising_edge(clk_i) then
        qb_o <= ram(f_check_bounds(to_integer(unsigned(ab_i)), 0, g_size-1));
        for i in 0 to c_num_bytes-1 loop
          if(s_we_b(i) = '1') then
            ram(f_check_bounds(to_integer(unsigned(ab_i)), 0, g_size-1))((i+1)*8-1 downto i*8) := db_i((i+1)*8-1 downto i*8);
          end if;
        end loop;
      end if;
    end process;

  end generate gen_with_byte_enable_readfirst;



  gen_without_byte_enable_readfirst : if(g_with_byte_enable = false and (g_addr_conflict_resolution = "read_first" or
                                                                         g_addr_conflict_resolution = "dont_care")) generate

    Port_A: process(clk_i)
    begin
      if rising_edge(clk_i) then
        if f_is_synthesis then
          qa_o <= ram(to_integer(unsigned(aa_i)));
        else
          qa_o <= ram(to_integer(unsigned(aa_i)) mod g_size);
        end if;
        if(wea_i = '1') then
          ram(to_integer(unsigned(aa_i))) := da_i;
        end if;
      end if;
    end process;

    Port_B: process(clk_i)
    begin
      if rising_edge(clk_i) then
        if f_is_synthesis then
          qb_o <= ram(to_integer(unsigned(ab_i)));
        else
          qb_o <= ram(to_integer(unsigned(ab_i)) mod g_size);
        end if;
        if(web_i = '1') then
          ram(to_integer(unsigned(ab_i))) := db_i;
        end if;
      end if;
    end process;
  end generate gen_without_byte_enable_readfirst;

  gen_without_byte_enable_writefirst : if(g_with_byte_enable = false and g_addr_conflict_resolution = "write_first") generate

    Port_A: process(clk_i)
    begin
      if rising_edge(clk_i) then
        if(wea_i = '1') then
          ram(to_integer(unsigned(aa_i))) := da_i;
          qa_o                            <= da_i;
        else
          if f_is_synthesis then
            qa_o <= ram(to_integer(unsigned(aa_i)));
          else
            qa_o <= ram(to_integer(unsigned(aa_i)) mod g_size);
          end if;
        end if;
      end if;
    end process;

    Port_B: process(clk_i)
    begin
      if rising_edge(clk_i) then
        if(web_i = '1') then
          ram(to_integer(unsigned(ab_i))) := db_i;
          qb_o                            <= db_i;
        else
          if f_is_synthesis then
            qb_o <= ram(to_integer(unsigned(ab_i)));
          else
            qb_o <= ram(to_integer(unsigned(ab_i)) mod g_size);
          end if;
        end if;
      end if;
    end process;

  end generate gen_without_byte_enable_writefirst;


  gen_without_byte_enable_nochange : if(g_with_byte_enable = false and g_addr_conflict_resolution = "no_change") generate

    Port_A: process(clk_i)
    begin
      if rising_edge(clk_i) then
        if(wea_i = '1') then
          ram(to_integer(unsigned(aa_i))) := da_i;
        else
          if f_is_synthesis then
            qa_o <= ram(to_integer(unsigned(aa_i)));
          else
            qa_o <= ram(to_integer(unsigned(aa_i)) mod g_size);
          end if;
        end if;
      end if;
    end process;

    Port_B: process(clk_i)
    begin
      if rising_edge(clk_i) then
        if(web_i = '1') then
          ram(to_integer(unsigned(ab_i))) := db_i;
        else
          if f_is_synthesis then
            qb_o <= ram(to_integer(unsigned(ab_i)));
          else
            qb_o <= ram(to_integer(unsigned(ab_i)) mod g_size);
          end if;
        end if;
      end if;
    end process;

    
  end generate gen_without_byte_enable_nochange;

   gen_with_byte_enable_writefirst : if(g_with_byte_enable = true and (g_addr_conflict_resolution = "write_first")) generate

    Port_A: process (clk_i)
    begin
      if rising_edge(clk_i) then
        for i in 0 to c_num_bytes-1 loop
          if s_we_a(i) = '1' then
            ram(f_check_bounds(to_integer(unsigned(aa_i)), 0, g_size-1))((i+1)*8-1 downto i*8) := da_i((i+1)*8-1 downto i*8);
            qa_o((i+1)*8-1 downto i*8)                                                         <= da_i((i+1)*8-1 downto i*8);
          else
            qa_o((i+1)*8-1 downto i*8) <= ram(f_check_bounds(to_integer(unsigned(aa_i)), 0, g_size-1))((i+1)*8-1 downto i*8);
          end if;
        end loop;
      end if;
    end process;

    Port_B: process (clk_i)
    begin
      if rising_edge(clk_i) then
        for i in 0 to c_num_bytes-1 loop
          if s_we_b(i) = '1' then
            ram(f_check_bounds(to_integer(unsigned(ab_i)), 0, g_size-1))((i+1)*8-1 downto i*8) := db_i((i+1)*8-1 downto i*8);
            qb_o((i+1)*8-1 downto i*8)                                                         <= db_i((i+1)*8-1 downto i*8);
          else
            qb_o((i+1)*8-1 downto i*8) <= ram(f_check_bounds(to_integer(unsigned(ab_i)), 0, g_size-1))((i+1)*8-1 downto i*8);
          end if;
        end loop;
      end if;
    end process;

  end generate gen_with_byte_enable_writefirst;

end syn;
