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

entity hydra_triple_cpu is
  port (
    clk_i : in std_logic;
    cpu_rst_i : in std_logic_vector(3 downto 1);
    clk_cpu_i : in std_logic_vector(3 downto 1);
    im_data_i : in std_logic_vector (31 downto 0);
    im_valid_i : in std_logic;
    im_addr_o : out std_logic_vector (31 downto 0);
    im_rd_o : out std_logic;
    dm_addr_o : out std_logic_vector (31 downto 0);
    dm_data_s_o : out std_logic_vector (31 downto 0);
    dm_data_l_i : in std_logic_vector (31 downto 0);
    dm_load_done_i : in std_logic;
    dm_store_done_i : in std_logic;
    dm_data_select_o : out std_logic_vector (3 downto 0);
    dm_store_o : out std_logic;
    dm_load_o : out std_logic;

    --  True when a cpu is in sync with at least another one.
    --  (ie same outputs on the bus)
    cpu_sync_o : out std_logic_vector(3 downto 1);

    --  If set, will give different values to each cpu.
    dm_force_divergence_i : in std_logic;

    --  Set to 1 if an output differs on the instruction bus
    err_cpu_im_o : out std_logic;

    --  Set to 1 if an output differs on the data bus
    err_cpu_dm_o : out std_logic);
end entity hydra_triple_cpu;

architecture arch of hydra_triple_cpu is
  type t_slv32_x3 is array(1 to 3) of std_logic_vector(31 downto 0);
  type t_slv4_x3 is array(1 to 3) of std_logic_vector(3 downto 0);
  subtype t_sl_x3 is std_logic_vector(3 downto 1);
  signal im_addr, dm_addr, dm_data_s, dm_data_l : t_slv32_x3;
  signal dm_data_select : t_slv4_x3;
  signal im_rd, dm_store, dm_load : t_sl_x3;

  signal ok_im : std_logic_vector(3 downto 1);

  signal ok_dm_addr, ok_dm_data, ok_dm_select, ok_dm_load, ok_dm_store, ok_dm : std_logic_vector(3 downto 1);
begin
  gen_triple: for i in 1 to 3 generate
    inst_cpu : urv_cpu
      generic map (
        g_timer_frequency => 0,
        g_with_hw_debug => 0,
        g_with_hw_mul => 0,
        g_with_hw_div => 0,
        g_with_ecc => 0
        )
      port map (
        clk_i            => clk_cpu_i(i),
        rst_i            => cpu_rst_i(i),
        irq_i            => '0',
        fault_o          => open,
        im_addr_o        => im_addr(i),
        im_rd_o          => im_rd(i),
        im_data_i        => im_data_i,
        im_valid_i       => im_valid_i,
        dm_addr_o        => dm_addr(i),
        dm_data_s_o      => dm_data_s(i),
        dm_data_l_i      => dm_data_l(i),
        dm_data_select_o => dm_data_select(i),
        dm_store_o       => dm_store(i),
        dm_load_o        => dm_load(i),
        dm_load_done_i   => dm_load_done_i,
        dm_store_done_i  => dm_store_done_i,
        dbg_force_i      => '0',
        dbg_enabled_o    => open,
        dbg_insn_i       => x"0000_0000",
        dbg_insn_set_i   => '0',
        dbg_insn_ready_o => open,
        dbg_mbx_data_i   => x"0000_0000",
        dbg_mbx_write_i  => '0',
        dbg_mbx_data_o   => open);
  end generate;

  inst_voter_im_addr: entity work.voter_vec_status
    generic map (
      g_WIDTH => 32
    )
    port map (
      a => im_addr(1),
      b => im_addr(2),
      c => im_addr(3),
      res => im_addr_o,
      err => open
    );

  inst_voter_dm_addr: entity work.voter_vec_status
    generic map (
      g_WIDTH => 32
    )
    port map (
      a => dm_addr(1),
      b => dm_addr(2),
      c => dm_addr(3),
      res => dm_addr_o,
      err => open
    );
 
  inst_voter_dm_data_s: entity work.voter_vec_status
    generic map (
      g_WIDTH => 32
    )
    port map (
      a => dm_data_s(1),
      b => dm_data_s(2),
      c => dm_data_s(3),
      res => dm_data_s_o,
      err => open
    );
 
  inst_voter_dm_data_select: entity work.voter_vec_status
    generic map (
      g_WIDTH => 4
    )
    port map (
      a => dm_data_select(1),
      b => dm_data_select(2),
      c => dm_data_select(3),
      res => dm_data_select_o,
      err => open
    );

  inst_voter_im_rd: entity work.voter_status
    port map (
      inp => im_rd,
      res => im_rd_o,
      err => open
    );

  inst_voter_dm_load: entity work.voter_status
    port map (
      inp => dm_load,
      res => dm_load_o,
      err => open
    );

  inst_voter_dm_store: entity work.voter_status
    port map (
      inp => dm_store,
      res => dm_store_o,
      err => open
    );

  dm_data_l(1) <= dm_data_l_i when dm_force_divergence_i = '0' else x"0000_0001";
  dm_data_l(2) <= dm_data_l_i when dm_force_divergence_i = '0' else x"0000_0002";
  dm_data_l(3) <= dm_data_l_i when dm_force_divergence_i = '0' else x"0000_0004";

  gen_s: for i in 0 to 2 generate
    constant n0 : natural := 1 + i;
    constant n1 : natural := 1 + (i + 1) mod 3;
    constant n2 : natural := 1 + (i + 2) mod 3;
  begin
    ok_im(n0) <= '1' when cpu_rst_i(n0) = '1'
       or (im_rd(n0) = '0' and (im_rd(n1) = '0' or im_rd(n2) = '0'))
       or (im_rd(n0) = '1' and ((im_rd(n1) = '1' and im_addr(n1) = im_addr(n0))
                                or (im_rd(n2) = '1' and im_addr(n2) = im_addr(n0)))) else '0';
    ok_dm_addr (n0) <= '1' when cpu_rst_i(n0) = '1'
        or (dm_load(n0) = '0' and (dm_load(n1) = '0' or dm_load(n2) = '0'))
        or (dm_store(n0) = '0' and (dm_store(n1) = '0' or dm_store(n2) = '0'))
        or (((dm_load(n0) = '1' and dm_load(n1) = '1') or (dm_store(n0) = '1' and dm_store(n1) = '1'))
            and dm_addr(n0) = dm_addr(n1))
        or (((dm_load(n0) = '1' and dm_load(n2) = '1') or (dm_store(n0) = '1' and dm_store(n2) = '1'))
           and dm_addr(n0) = dm_addr(n2))
      else '0';
    ok_dm_data (n0) <= '1' when cpu_rst_i(n0) = '1'
        or (dm_store(n0) = '0' and (dm_store(n1) = '0' or dm_store(n2) = '0'))
        or ((dm_store(n0) = '1' and dm_store(n1) = '1') and dm_data_s(n0) = dm_data_s(n1))
        or ((dm_store(n0) = '1' and dm_store(n2) = '1') and dm_data_s(n0) = dm_data_s(n2))
      else '0';
    ok_dm_select (n0) <= '1' when cpu_rst_i(n0) = '1'
        or (dm_store(n0) = '0' and (dm_store(n1) = '0' or dm_store(n2) = '0'))
        or ((dm_store(n0) = '1' and dm_store(n1) = '1') and dm_data_select(n0) = dm_data_select(n1))
        or ((dm_store(n0) = '1' and dm_store(n2) = '1') and dm_data_select(n0) = dm_data_select(n2))
      else '0';
    ok_dm_store (n0) <= '1' when cpu_rst_i(n0) = '1'
       or (dm_store(n0) = dm_store(n1))
       or (dm_store(n0) = dm_store(n2)) else '0';
    ok_dm_load (n0) <= '1' when cpu_rst_i(n0) = '1'
       or (dm_load(n0) = dm_load(n1))
       or (dm_load(n0) = dm_load(n2)) else '0';
    ok_dm (n0) <= ok_dm_addr(n0) and ok_dm_data(n0) and ok_dm_select(n0) and ok_dm_store(n0) and ok_dm_load(n0);

  end generate;

  cpu_sync_o <= ok_im and ok_dm;
  err_cpu_dm_o <= '1' when ok_dm /= "111" else '0';
  err_cpu_im_o <= '1' when ok_im /= "111" else '0';

end arch;
