--test bench written by Alban Bourge @ TIMA
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.textio.all;
library work;
use work.pkg_tb.all;

entity fsm is
	port(
				clock      : in std_logic;
				reset      : in std_logic;
				--prog interface
				instr_next : in instruction;
				step       : out std_logic;
				--uut interface
				cp_ok      : in std_logic;
				stdin_rdy  : in std_logic;
				stdin_ack  : in std_logic;
				reset_fsm  : out std_logic;
				start      : out std_logic;
				cp_en      : out std_logic;
				cp_rest    : out std_logic;
				--ram interface
				ram_1      : out ram_instruction;
				ram_2      : out ram_instruction;
				--assert_uut interface
				context_uut : out context_t;
				en_feed    : out std_logic;
				en_check   : out std_logic;
				vecs_found : in std_logic;
				vec_read   : in std_logic;
				--tb interface
				stopped    : out std_logic
			);
end fsm;

architecture rtl of fsm is

	-- read output
	signal step_sig  : std_logic;
	-- FSM signals
	signal instr_c   : instruction := instr_rst;
	signal instr_n   : instruction := instr_rst;
	-- TIMER signal
	signal times_en  : std_logic := '0';
	signal times_z   : std_logic := '0';
	signal times     : unsigned(ARG_WIDTH - 1 downto 0);
	signal times_max : unsigned(ARG_WIDTH - 1 downto 0);
	signal times_ok  : std_logic := '0';
	-- COUNTER signal
	signal count_en  : std_logic := '0';
	signal count_z   : std_logic := '0';
	signal count     : unsigned(ARG_WIDTH - 1 downto 0);
	signal count_max : unsigned(ARG_WIDTH - 1 downto 0);
	signal count_ok  : std_logic := '0';
	-- runtime counter
	signal runtime_en : std_logic := '0';
	signal runtime    : integer range 0 to 99999999; --100 million cycles

begin

	-- FSM
	state_reg : process (clock, reset) is
	begin
		if (reset = '1') then
			instr_c <= instr_rst;
		elsif rising_edge(clock) then
			instr_c <= instr_n;
		end if;
	end process state_reg;

	comb_logic: process(instr_next, instr_c, stdin_rdy, count_ok, times_ok, cp_ok, stdin_ack, vecs_found, vec_read)
	begin
		--default definition for fsm control signals
		instr_n    <= instr_rst;
		step_sig   <= '0';
		--top
		reset_fsm  <= '0';
		start      <= '0';
		cp_en      <= '0';
		cp_rest    <= '0';
		--counter & timer
		times_en   <= '0';
		times_max  <= (others => '0');
		count_en   <= '0';
		count_max  <= (others => '0');
		--runtime counter
		runtime_en <= '0';
		--ram
		ram_1      <= ram_instr_z;
		ram_2      <= ram_instr_z;
		--assert_uut
		en_feed    <= '0';
		en_check   <= '0';
		--tb interface
		stopped    <= '0';

		case instr_c.state is
			when Rst =>
				--signals
				reset_fsm    <= '1';
				ram_1.addr_z <= '1';
				ram_2.addr_z <= '1';
				step_sig     <= '1'; --demand for next instruction
				--transition
				instr_n <= instr_next;

			when Sig_start =>
				--signals
				start    <= '1';
				step_sig <= '1'; --demand for next instruction
				--transition
				instr_n <= instr_next;
				--if (instr_next.state = Ack_data) then
					--en_feed <= '1';
				--end if;

			when Ack_data =>
				times_max <= instr_c.arg - 1;
				--signals
				en_feed <= '1';
				--transition
				if (stdin_rdy = '1' and stdin_ack = '1') then
					times_en <= '1';
				end if;
				if (times_ok = '1') then
					en_feed <= '0';
					step_sig <= '1';
					instr_n <= instr_next;
				else
					instr_n <= instr_c;
				end if;

			when Running =>
				--signals
				count_max <= instr_c.arg;
				count_en  <= '1';
				--en_check  <= '1';
				--runtime counter
				if(vecs_found = '0') then
					runtime_en <= '1';
				end if;
				--transition
				if (count_ok = '1') then
					step_sig <= '1';
					instr_n  <= instr_next;
				else
					instr_n <= instr_c;
				end if;

			when Waitfor =>
				--signals
				count_max <= instr_c.arg;
				en_check  <= '1';
				if(vec_read = '1') then
					count_en  <= '1';
				end if;
				--runtime counter
				if(vecs_found = '0') then
					runtime_en <= '1';
				end if;
				--transition
				if (count_ok = '1') then
					step_sig <= '1';
					instr_n  <= instr_next;
				else
					instr_n <= instr_c;
				end if;

			when Cp_search =>
				--signals
				cp_en <= '1';
				--transition
				if (cp_ok = '1') then
					case instr_c.context_uut is
						when "01" =>
							ram_1.we <= '1';
							ram_1.addr_up  <= '1';
							ram_1.sel  <= '1';
						when "10" =>
							ram_2.we <= '1';
							ram_2.addr_up  <= '1';
							ram_2.sel  <= '1';
						when others =>
					end case;
					instr_n  <= (state => Cp_save, context_uut => instr_c.context_uut, arg => (others => '0')); --hard coded
				else
					instr_n <= instr_c;
				end if;

			when Cp_save =>
				--signals
				cp_en    <= '1';
				case instr_c.context_uut is
					when "01" =>
						ram_1.we <= '1';
						ram_1.addr_up  <= '1';
						ram_1.sel  <= '1';
					when "10" =>
						ram_2.we <= '1';
						ram_2.addr_up  <= '1';
						ram_2.sel  <= '1';
					when others =>
				end case;
				--transition
				if (cp_ok = '0') then
					case instr_c.context_uut is
						when "01" =>
							ram_1.we <= '0';
							ram_1.addr_up  <= '0';
						when "10" =>
							ram_2.we <= '0';
							ram_2.addr_up  <= '0';
						when others =>
					end case;
					step_sig <= '1';
					instr_n  <= instr_next;
				else
					instr_n  <= instr_c;
				end if;

			when Idle => 
				--signals
				count_max <= instr_c.arg;
				count_en <= '1';
				--transition
				if (count_ok = '1') then
					step_sig <= '1';
					instr_n <= instr_next;
				else
					instr_n <= instr_c;
				end if;

			when Rst_uut =>
				--signals
				reset_fsm <= '1';
				ram_1.addr_z <= '1';
				ram_2.addr_z <= '1';
				--transition
				step_sig <= '1';
				instr_n <= instr_next;

			when Rest_ini0 =>
				--signals
				start   <= '1';
				cp_en   <= '1';
				cp_rest <= '1';
				--this is for restoration : reading the first word of the right memory
				case instr_c.context_uut is
					when "01" =>
						ram_1.sel  <= '1';
					when "10" =>
						ram_2.sel  <= '1';
					when others =>
				end case;
				--transition
				instr_n <= (state => Rest_ini1, context_uut => instr_c.context_uut, arg => (others => '0')); --hard coded

			when Rest_ini1 =>
				--signals
				cp_en   <= '1';
				cp_rest <= '1';
				case instr_c.context_uut is
					when "01" =>
						ram_1.addr_up  <= '1';
						ram_1.sel  <= '1';
					when "10" =>
						ram_2.addr_up  <= '1';
						ram_2.sel  <= '1';
					when others =>
				end case;
				--transition
				instr_n <= (state => Rest, context_uut => instr_c.context_uut, arg => (others => '0')); --hard coded

			when Rest =>
				--signals
				cp_en   <= '1';
				cp_rest <= '1';
				case instr_c.context_uut is
					when "01" =>
						ram_1.addr_up  <= '1';
						ram_1.sel  <= '1';
					when "10" =>
						ram_2.addr_up  <= '1';
						ram_2.sel  <= '1';
					when others =>
				end case;
				--transition
				if (cp_ok = '0') then
					step_sig <= '1';
					instr_n  <= instr_next;
				else
					instr_n  <= instr_c;
				end if;

			when Stop =>
				--signals
				stopped   <= '1';
				reset_fsm <= '1';
				report "RUNTIME:" & integer'image(runtime);
				assert (vecs_found = '0')
				report "END_OF_SIM ---> Stop state reached, some output vectors were read." severity note;
				--transition
				instr_n <= (state => Stop, context_uut => "00", arg => (others => '0')); --hard coded

			when others =>
		end case;
	end process comb_logic;

	--*ER reset combo logic
	--if a step_sig signal is sent, it means a instr_next will be consumed
	reseter : process(step_sig)
	begin
		if (step_sig = '0') then
			times_z    <= '0';
			count_z    <= '0';
		else
			times_z    <= '1';
			count_z    <= '1';
		end if;
	end process reseter;

	--TIMER
	timer : process(clock, reset)
	begin
		if (reset =  '1') then
			times    <= (others => '0');
			times_ok <= '0';
		elsif rising_edge(clock) then
			if (times_z = '1') then
				times    <= (others => '0');
				times_ok <= '0';
			else
				if (times_en = '1') then
					times <= times + 1;
					if (times = times_max) then
						times_ok <= '1';
					else
						times_ok <= '0';
					end if;
				end if;
			end if;
		end if;
	end process timer;

	--COUNTER
	counter : process(clock, reset)
	begin
		if (reset =  '1') then
			count <= (others => '0');
			count_ok <= '0';
		elsif rising_edge(clock) then
			--count_ok driving if
			if (count_z = '1') then
				count_ok <= '0';
				count    <= (others => '0');
			else
				if (count = count_max) then
					count_ok <= '1';
				else
					count_ok <= '0';
					if (count_en = '1') then
						count <= count + 1;
					end if;
				end if;
			end if;
		end if;
		end process counter;

	--Runtime counter
	runtime_counter : process(clock, reset)
	begin
		if (reset =  '1') then
			runtime <= 0;
		elsif rising_edge(clock) then
			if (runtime_en = '1') then
				runtime <= runtime + 1;
				if ((runtime mod 1000) = 0) then
					report "Running since:" & integer'image(runtime) severity note;
				end if;
			end if;
		end if;
	end process runtime_counter;

	-- process only used for reporting current instruction
	reporter : process(instr_c)
	begin
		--report "Instruction: " & state_t'image(instr_c.state) severity note;
		report "Instruction: " & state_t'image(instr_c.state) & " (context " & integer'image(to_integer(unsigned(instr_c.context_uut))) & ")" severity note;
	end process reporter;

	--Combinational
	step <= step_sig;
	context_uut <= instr_c.context_uut;
end rtl;