-- -------------------------------------------------------------------------- --
--              TRASHERNET SoC - A Trashy Ethernet SoC for FPGAs              --
-- -------------------------------------------------------------------------- --
-- TODO
-- -------------------------------------------------------------------------- --
-- Author       : Markus Koch <markus@notsyncing.net>
-- Contributors : None
-- License      : Mozilla Public License (MPL) Version 2
-- -------------------------------------------------------------------------- --

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

entity wishbone_arbiter is
	generic(
		ASYNC         : boolean := true; -- Generate the switch as a fully asynchronous circuit when false, register outputs when true.
		NO_DEAD_SLAVE : boolean := true -- Connect to slave 0,0 on no address match (saves resources)
	);
	port(
		clk       : in  std_logic;      -- System clock, must be shared with all slaves and masters.
		rst       : in  std_logic;      -- Asynchronous reset

		-- Master connections (crossbar is a slave)
		masters_o : in  wishbone_slave_in_vector; -- Connections to masters (crossbar is slave), inputs to crossbar
		masters_i : out wishbone_slave_out_vector; -- Connections to masters (crossbar is slave), outputs from crossbar

		-- Slave connections (crossbar is a master)
		slaves_o  : in  wishbone_master_in_vector; -- Connections to slaves (crossbar is master), inputs to crossbar
		slaves_i  : out wishbone_master_out_vector; -- Connections to slaves (crossbar is master), outputs from crossbar

		-- Address configuration of the slaves connected
		address   : in  wishbone_address_vector; -- Base address for each slave connection. Must be the same size as `master_i` and `master_o`
		mask      : in  wishbone_address_vector -- Bit mask for slave base addresses. Must be the same size as `master_i` and `master_o`
	);
end wishbone_arbiter;

architecture rtl of wishbone_arbiter is
	subtype slave_index is integer range 0 to slaves_i'length - 1;
	subtype master_index is integer range 0 to masters_i'length - 1;

	signal slave_sel  : slave_index;
	signal master_sel : master_index;
	signal matched    : std_logic;

	constant SLEEPY_MASTER : wishbone_master_out := (cyc => '0', stb => '0', adr => (others => '-'), sel => (others => '-'), we => '-', dat => (others => '-'));
	constant SLEEPY_SLAVE  : wishbone_slave_out  := (ack => '0', err => '0', rty => '0', stall => '1', dat => (others => '-'));

	procedure generate_logic(
		signal mo   : in wishbone_slave_in_vector;
		signal so   : in wishbone_master_in_vector;
		signal ssel : out slave_index;
		signal msel : out master_index;
		signal mtch : out std_logic
	) is
	begin
		ssel <= 0;
		msel <= 0;
		mtch <= '0';
		master_loop : for master_id in mo'range loop
			if (mo(master_id).cyc and mo(master_id).stb) then
				slave_loop : for slave_id in so'range loop
					if (mo(master_id).adr and mask(slave_id)) = address(slave_id) then
						ssel <= slave_id;
						msel <= master_id;
						mtch <= '1';
						exit master_loop;
					end if;
				end loop;
			end if;
		end loop;
	end procedure generate_logic;

	procedure connect_slaves(
		signal   mo   : in wishbone_slave_in_vector;
		signal   mi   : out wishbone_slave_out_vector;
		signal   so   : in wishbone_master_in_vector;
		signal   si   : out wishbone_master_out_vector;
		constant ssel : in slave_index;
		constant msel : in master_index;
		signal   mtch : in std_logic
	) is
	begin
		for slave_id in si'range loop
			si(slave_id)     <= SLEEPY_MASTER;
			-- Always connect data path from selected master to *all* slave inputs
			si(slave_id).dat <= mo(msel).dat;
			si(slave_id).we  <= mo(msel).we;
			si(slave_id).sel <= mo(msel).sel;
			si(slave_id).adr <= mo(msel).adr and (not mask(ssel));
			if (mtch = '1' or NO_DEAD_SLAVE) and (slave_id = ssel) then -- If we are the currently active slave, then also connect these (else SLEEPY default)
				si(slave_id).cyc <= mo(msel).cyc;
				si(slave_id).stb <= mo(msel).stb;
			end if;
		end loop;
		for master_id in mi'range loop
			mi(master_id)     <= SLEEPY_SLAVE;
			-- Always connect data path from selected slave to *all* master inputs
			mi(master_id).dat <= so(ssel).dat;
			if (mtch = '1' or NO_DEAD_SLAVE) and (master_id = msel) then -- If we are the currently active master, also connect these (else SLEEPY default)
				mi(master_id).ack   <= so(ssel).ack;
				mi(master_id).err   <= so(ssel).err;
				mi(master_id).rty   <= so(ssel).rty;
				mi(master_id).stall <= so(ssel).stall;
			end if;
		end loop;
	end procedure connect_slaves;

begin
	assert ASYNC = true report "Arbiter does not support ASYNC mode" severity failure;

	sync : if ASYNC generate
		name : process(masters_o, slaves_o, address, mask) is
		begin
			generate_logic(masters_o, slaves_o, slave_sel, master_sel, matched);
		end process name;

	else generate
		sync_proc : process(rst, clk) is
		begin
			if rst then
				slave_sel  <= 0;
				master_sel <= 0;
			elsif rising_edge(clk) then
				generate_logic(masters_o, slaves_o, slave_sel, master_sel, matched);
			end if;
		end process sync_proc;
	end generate sync;

	name : process(masters_o, slaves_o, slave_sel, master_sel, matched, mask) is
	begin
		connect_slaves(masters_o, masters_i, slaves_o, slaves_i, slave_sel, master_sel, matched);
	end process name;

end rtl;