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

library ddr3controller;
use ddr3controller.all;

entity top is

  port (
    ref_clk : in  std_logic;
    rst_hw  : in  std_logic;
    leds_hw : out std_logic_vector(7 downto 0);

    -- User input
    inject_error_write : in std_logic;

    -- DDR3 Memory
    mem_a       : out   std_logic_vector(14 downto 0);
    mem_ba      : out   std_logic_vector(2 downto 0);
    mem_ck      : inout std_logic;
    mem_ck_n    : inout std_logic;
    mem_cke     : out   std_logic;
    mem_cs_n    : out   std_logic;
    mem_dm      : out   std_logic_vector(1 downto 0);
    mem_ras_n   : out   std_logic;
    mem_cas_n   : out   std_logic;
    mem_we_n    : out   std_logic;
    mem_reset_n : out   std_logic;
    mem_dq      : inout std_logic_vector(15 downto 0);
    mem_dqs     : inout std_logic_vector(1 downto 0);
    mem_dqs_n   : inout std_logic_vector(1 downto 0);
    mem_odt     : out   std_logic
    );

end entity top;

architecture RTL of top is

  signal clk : std_logic;

  type pattern_state_t is (PATTERN_WAIT_INIT, PATTERN_STARTUP_DELAY, PATTERN_WRITE_MEMORY, PATTERN_READ_MEMORY, PATTERN_STOP);
  signal pattern_state    : pattern_state_t;
  signal current_mem_addr : unsigned(25 downto 0);

  signal read_data_acceptor_reset : std_logic;

  signal reset_async  : std_logic;
  signal reset_synced : std_logic;

  -- DDR3 status signals
  signal local_init_done   : std_logic;
  signal local_cal_success : std_logic;
  signal local_cal_fail    : std_logic;
  signal pll_locked        : std_logic;

  -- DDR3 Avalon signals
  signal avl_ready       : std_logic;
  signal avl_burstbegin  : std_logic;
  signal avl_addr        : std_logic_vector(25 downto 0);
  signal avl_rdata_valid : std_logic;
  signal avl_rdata       : std_logic_vector(63 downto 0);
  signal avl_wdata       : std_logic_vector(63 downto 0);
  signal avl_be          : std_logic_vector(7 downto 0);
  signal avl_read_req    : std_logic;
  signal avl_write_req   : std_logic;
  signal avl_size        : std_logic_vector(2 downto 0);

  signal leds : std_logic_vector(leds_hw'range);

  signal error_detected : std_logic;

  signal inject_error_synced : std_logic;
  signal current_round       : integer range 0 to 3;


  function get_pattern_of_addr(address : in unsigned(25 downto 0);
                               round   : in integer range 0 to 3) return std_logic_vector is
    variable low_nibble   : unsigned(3 downto 0);
    variable test_pattern : std_logic_vector(63 downto 0);
  begin
    low_nibble := address(low_nibble'range);
    case to_integer(low_nibble) + round is
      when 0      => test_pattern := x"AAAAAAAAAAAAAAAA";
      when 1      => test_pattern := x"CCCCCCCCCCCCCCCC";
      when 2      => test_pattern := x"5555555555555555";
      when 3      => test_pattern := x"A5A5A5A5A5A5A5A5";
      when 4      => test_pattern := x"1111111111111111";
      when 5      => test_pattern := x"3333333333333333";
      when 6      => test_pattern := x"AAAAAAAA55555555";
      when 7      => test_pattern := x"55555555AAAAAAAA";
      when 8      => test_pattern := x"55AA55AA55AA55AA";
      when 9      => test_pattern := x"AA55AA55AA55AA55";
      when 10     => test_pattern := x"1234567891234567";
      when 11     => test_pattern := x"ABCDEF123456789F";
      when 12     => test_pattern := x"0000000000000000";
      when 13     => test_pattern := x"FFFFFFFFFFFFFFFF";
      when 14     => test_pattern := x"F0F0F0F0F0F0F0F0";
      when 15     => test_pattern := x"0F0F0F0F0F0F0F0F";
      when others => test_pattern := (others => '0');
    end case;

    return test_pattern;
  end function get_pattern_of_addr;

begin  -- architecture RTL

  reset_async <= not rst_hw;

  reset_synchronizer : process(ref_clk, reset_async) is
  begin
    if reset_async = '1' then
      reset_synced <= '1';
    elsif rising_edge(ref_clk) then
      reset_synced <= reset_async;
    end if;
  end process reset_synchronizer;

  leds(4 downto 0) <= std_logic_vector(current_mem_addr(25 downto 21));
  leds(5)          <= '1' when pattern_state = PATTERN_WRITE_MEMORY else '0';
  leds(6)          <= '1' when pattern_state = PATTERN_READ_MEMORY  else '0';
  leds(7)          <= error_detected;

  leds_hw <= not leds;

  inject_sync : process(clk, reset_synced) is
    variable shift_reg : std_logic_vector(2 downto 0) := (others => '0');
  begin
    if reset_synced = '1' then
      shift_reg           := (others => '0');
      inject_error_synced <= '0';
    elsif rising_edge(clk) then
      shift_reg           := shift_reg(1 downto 0) & inject_error_write;
      inject_error_synced <= shift_reg(2);
    end if;
  end process inject_sync;

  ddr3controller_1 : entity ddr3controller.ddr3controller
    port map (
      pll_ref_clk        => ref_clk,
      global_reset_n     => not reset_synced,
      soft_reset_n       => '1',
      afi_clk            => clk,
      afi_half_clk       => open,
      afi_reset_n        => open,
      afi_reset_export_n => open,
      mem_a              => mem_a,
      mem_ba             => mem_ba,
      mem_ck(0)          => mem_ck,
      mem_ck_n(0)        => mem_ck_n,
      mem_cke(0)         => mem_cke,
      mem_cs_n(0)        => mem_cs_n,
      mem_dm             => mem_dm,
      mem_ras_n(0)       => mem_ras_n,
      mem_cas_n(0)       => mem_cas_n,
      mem_we_n(0)        => mem_we_n,
      mem_reset_n        => mem_reset_n,
      mem_dq             => mem_dq,
      mem_dqs            => mem_dqs,
      mem_dqs_n          => mem_dqs_n,
      mem_odt(0)         => mem_odt,
      avl_ready          => avl_ready,
      avl_burstbegin     => avl_burstbegin,
      avl_addr           => avl_addr,
      avl_rdata_valid    => avl_rdata_valid,
      avl_rdata          => avl_rdata,
      avl_wdata          => avl_wdata,
      avl_be             => avl_be,
      avl_read_req       => avl_read_req,
      avl_write_req      => avl_write_req,
      avl_size           => avl_size,
      local_init_done    => local_init_done,
      local_cal_success  => local_cal_success,
      local_cal_fail     => local_cal_fail,
      pll_mem_clk        => open,
      pll_write_clk      => open,
      pll_locked         => pll_locked,
      pll_capture0_clk   => open,
      pll_capture1_clk   => open);



  pattern_writer : process(clk, reset_synced) is
    variable write_data : std_logic_vector(63 downto 0) := (others => '0');
    variable delay_cnt  : integer range 0 to 1000       := 0;
  begin
    if reset_synced = '1' then
      pattern_state    <= PATTERN_WAIT_INIT;
      avl_size         <= (others => '0');
      avl_burstbegin   <= '0';
      avl_be           <= (others => '1');
      avl_write_req    <= '0';
      avl_read_req     <= '0';
      current_mem_addr <= (others => '0');
      avl_addr         <= (others => '0');
      current_round    <= 0;
    elsif rising_edge(clk) then
      read_data_acceptor_reset <= '0';
      avl_burstbegin           <= '0';
      avl_be                   <= (others => '1');
      case pattern_state is
        when PATTERN_WAIT_INIT =>
          if local_init_done = '1' and local_cal_success = '1' then
            pattern_state    <= PATTERN_STARTUP_DELAY;
            current_mem_addr <= (others => '0');
            delay_cnt        := 0;
          end if;
        when PATTERN_STARTUP_DELAY =>
          if delay_cnt < 1000 then
            delay_cnt := delay_cnt + 1;
          else
            pattern_state <= PATTERN_WRITE_MEMORY;
          end if;
        when PATTERN_WRITE_MEMORY =>
          avl_read_req <= '0';
          write_data   := get_pattern_of_addr(current_mem_addr, current_round);
          if avl_ready = '1' then
            avl_addr      <= std_logic_vector(current_mem_addr);
            avl_write_req <= '1';
            avl_size      <= "001";
            if inject_error_synced = '1' then
              write_data(18) := not write_data(18);
            end if;
            avl_wdata <= write_data;
            if current_mem_addr = 67108863 then
              pattern_state            <= PATTERN_READ_MEMORY;
              read_data_acceptor_reset <= '1';
              current_mem_addr         <= to_unsigned(0, current_mem_addr'length);
            else
              current_mem_addr <= current_mem_addr + 1;
            end if;
          else
          -- Do nuthing
          end if;
        when PATTERN_READ_MEMORY =>
          if avl_ready = '1' then
            avl_addr       <= std_logic_vector(current_mem_addr);
            avl_size       <= "100";
            avl_burstbegin <= '1';
            avl_read_req   <= '1';
            avl_write_req  <= '0';
            if current_mem_addr < 67108860 then
              current_mem_addr <= current_mem_addr + 4;
            else
              pattern_state <= PATTERN_STOP;
            end if;
          end if;
        when PATTERN_STOP =>
          if avl_ready = '1' then
            avl_read_req  <= '0';
            avl_write_req <= '0';
            pattern_state <= PATTERN_WAIT_INIT;

            if current_round < 3 then
              current_round <= current_round + 1;
            else
              current_round <= 0;
            end if;
          end if;
        when others => null;
      end case;
    end if;
  end process pattern_writer;

  data_acceptor : process(clk, reset_synced) is
    variable accepted_round   : integer range 0 to 3;
    variable read_memory_addr : unsigned(current_mem_addr'range) := (others => '0');
  begin
    if reset_synced = '1' then
      error_detected   <= '0';
      accepted_round   := 0;
      read_memory_addr := to_unsigned(0, read_memory_addr'length);
    elsif rising_edge(clk) then
      if read_data_acceptor_reset = '1' then
        accepted_round   := current_round;
        read_memory_addr := to_unsigned(0, read_memory_addr'length);
      else
        if avl_rdata_valid = '1' then
          if get_pattern_of_addr(read_memory_addr, accepted_round) /= avl_rdata then
            error_detected <= '1';
          end if;
          read_memory_addr := read_memory_addr + 1;
        end if;
      end if;
    end if;
  end process data_acceptor;


end architecture RTL;
