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

library ip;
use ip.wishbone_package.all;

entity uart_wb is
	generic(
		portcount : integer := 1
	);
	port(
		clk     : in  std_logic;
		rst     : in  std_logic;
		-- Wishbone
		slave_i : in  wishbone_slave_in;
		slave_o : out wishbone_slave_out;
		irq_o   : out std_logic_vector(portcount - 1 downto 0);
		-- UART
		rx      : in  std_logic_vector(portcount - 1 downto 0);
		tx      : out std_logic_vector(portcount - 1 downto 0)
	);
end entity uart_wb;

architecture RTL of uart_wb is
	constant addressQuantum   : integer := 4;
	constant registersPerCore : integer := 5;

	type uart_data is array (portcount - 1 downto 0) of std_logic_vector(7 downto 0);
	type uart_ckDiv is array (portcount - 1 downto 0) of std_logic_vector(15 downto 0);
	type uart_register is array (portcount - 1 downto 0) of std_logic_vector(31 downto 0);

	signal rx_data       : uart_data;
	signal rx_byte_ready : std_logic_vector(portcount - 1 downto 0);
	signal rx_error      : std_logic_vector(portcount - 1 downto 0);
	signal ckDiv         : uart_ckDiv;
	signal parityEnable  : std_logic_vector(portcount - 1 downto 0);
	signal parityOdd     : std_logic_vector(portcount - 1 downto 0);
	signal twoStopBits   : std_logic_vector(portcount - 1 downto 0);
	signal tx_data       : uart_data;
	signal tx_strobe     : std_logic_vector(portcount - 1 downto 0);
	signal tx_busy       : std_logic_vector(portcount - 1 downto 0);

	signal CR       : uart_register;
	signal SR       : uart_register;
	signal IMR      : uart_register;
	signal MASKEDSR : uart_register;

	signal data_in_buffered : uart_data;

begin
	generate_label : for i in 0 to portcount - 1 generate
		uart_rx_inst : entity work.uart_rx
			port map(
				clk          => clk,
				rst          => rst,
				data         => rx_data(i),
				byte_ready   => rx_byte_ready(i),
				error        => rx_error(i),
				ckDiv        => ckDiv(i),
				parityEnable => parityEnable(i),
				parityOdd    => parityOdd(i),
				twoStopBits  => twoStopBits(i),
				rx           => rx(i)
			);
		uart_tx_inst : entity work.uart_tx
			port map(
				clk          => clk,
				rst          => rst,
				data         => tx_data(i),
				byte_ready   => tx_strobe(i),
				busy         => tx_busy(i),
				ckDiv        => ckDiv(i),
				parityEnable => parityEnable(i),
				parityOdd    => parityOdd(i),
				twoStopBits  => twoStopBits(i),
				tx           => tx(i)
			);
	end generate generate_label;

	wb : process(rst, clk) is
	begin
		if rst = '1' then
			slave_o.DAT <= (others => '0');

			for i in 0 to portcount - 1 loop
				SR(i)        <= (others => '0');
				CR(i)        <= (others => '0');
				IMR(i)       <= (others => '0');
				tx_strobe(i) <= '0';
				tx_data(i)   <= (others => '0');
			end loop;
		elsif rising_edge(clk) then
			for i in 0 to portcount - 1 loop
				slave_o.ACK <= '0';
				tx_strobe(i) <= '0';

				--SR update
				if (rx_error(i) = '1') then
					SR(i)(17) <= '1';   -- Set RXEI
				end if;
				if (rx_byte_ready(i) = '1') then
					SR(i)(18)           <= '1'; -- Set RXI
					SR(i)(16)           <= '1'; -- Set RXNE (no FIFO yet)
					data_in_buffered(i) <= rx_data(i);
				end if;

				SR(i)(0) <= tx_busy(i); -- TXBF; no FIFO
				SR(i)(1) <= tx_busy(i); -- TXActive

				-- WB
				if slave_i.CYC = '1' and slave_i.STB = '1' then
					if unsigned(slave_i.ADR) = to_unsigned((registersPerCore * i), slave_i.ADR'length) then -- CRx
						slave_o.DAT <= CR(i);
						if slave_i.we = '1' then
							CR(i) <= slave_i.DAT;
						end if;
					elsif unsigned(slave_i.ADR) = to_unsigned((registersPerCore * i) + 1 * addressQuantum, slave_i.ADR'length) then --SRx
						slave_o.DAT <= SR(i);
						if slave_i.we = '1' then
							SR(i) <= slave_i.DAT and (x"00" & "00000110" & x"0000"); -- mask RO bits
						end if;
					elsif unsigned(slave_i.ADR) = to_unsigned((registersPerCore * i) + 2 * addressQuantum, slave_i.ADR'length) then --IMRx
						slave_o.DAT <= IMR(i);
						if slave_i.we = '1' then
							IMR(i) <= slave_i.DAT;
						end if;
					elsif unsigned(slave_i.ADR) = to_unsigned((registersPerCore * i) + 3 * addressQuantum, slave_i.ADR'length) then --ODRx
						slave_o.DAT <= x"000000" & tx_data(i); --(others => '0');
						if slave_i.we = '1' then
							tx_data(i)   <= slave_i.DAT(7 downto 0);
							tx_strobe(i) <= '1';
						end if;
					elsif unsigned(slave_i.ADR) = to_unsigned((registersPerCore * i) + 4 * addressQuantum, slave_i.ADR'length) then --IDRx
						slave_o.DAT <= x"000000" & data_in_buffered(i);
						SR(i)(18)   <= '0'; -- RXI    Clear interrupt
						SR(i)(16)   <= '0'; -- RXNE   No FIFO -> buffer is immediately empty again
					end if;
					slave_o.ACK <= '1';
				end if;
			end loop;
		end if;
	end process wb;

	slave_o.RTY   <= '0';
	slave_o.STALL <= '0';
	slave_o.ERR   <= '0';

	applyCR : for i in 0 to portcount - 1 generate
		ckDiv(i)        <= CR(i)(31 downto 16);
		parityEnable(i) <= CR(i)(1);
		parityOdd(i)    <= CR(i)(0);
		twoStopBits(i)  <= CR(i)(2);
	end generate applyCR;

	masking : for i in 0 to portcount - 1 generate
		MASKEDSR(i) <= SR(i) and IMR(i);
		irq_o(i)    <= OR_REDUCE(MASKEDSR(i));
	end generate masking;

end architecture RTL;
