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

--altera mega functions library mf.
--unsigned integer natural;
library altera_mf;
use altera_mf.all;

--readdatavalid is for pipelined read transfer with variable latench.
entity smack_ctrl_modify is 
	generic(
		kernel_hash: natural := 0;
        fifo_depth: natural := 256; -- <- NEW CODE
        ttl: natural := 16384; -- <- NEW CODE
        -- Number of metastability register stages to use in clock domain-crossing. Must be >= 2
		meta_stages: natural := 2
	);
	port(
		clk: in std_logic;
		rstn: in std_logic;
		bus_address: in std_logic_vector(2 downto 0);
		bus_read: in std_logic;
		bus_readdata: out std_logic_vector(31 downto 0);
		bus_waitrequest: out std_logic;
		bus_write: in std_logic;
		bus_writedata: in std_logic_vector(31 downto 0);
		smack_clk: in std_logic;
		smack_sclk: in std_logic;
		smack_en: out std_logic;
		smack_scanin: out std_logic;
		smack_scanout: in std_logic
	);
end entity smack_ctrl_modify;

--In which we need to define component, signals, process and concurrent statements.
architecture rtl of smack_ctrl_modify is
--enable signal:
--signals connect the separate concurrent statements of an architecture to each other
	signal en: std_logic;
	signal en_req: std_logic_vector(meta_stages + 1 downto 0);
	signal en_ack: std_logic_vector(meta_stages - 1 downto 0);

	signal state: std_logic := '0';  -- <- NEW CODE
	
	signal buscnt_running: std_logic;
	
	signal kernelen: std_logic;
	signal kernelen_req: std_logic_vector(meta_stages + 1 downto 0);
	signal kernelen_ack: std_logic_vector(meta_stages - 1 downto 0);
	signal kernelcnt_running: std_logic;
	signal smack_kernelen: std_logic;
	signal kernel_cnt: std_logic_vector(31 downto 0);
	signal kernel_state: std_logic := '0';

	signal shadowen: std_logic;
	signal shadowen_req: std_logic_vector(meta_stages + 1 downto 0);
	signal shadowen_ack: std_logic_vector(meta_stages - 1 downto 0);
	signal shadowcnt_running: std_logic;
	signal smack_shadowen: std_logic;
	signal shadow_cnt: std_logic_vector(31 downto 0);
	signal shadow_state: std_logic := '0';

	attribute altera_attribute: string;
	attribute altera_attribute of en_req: signal is "-name AUTO_SHIFT_REGISTER_RECOGNITION OFF; -name SYNCHRONIZER_IDENTIFICATION FORCED";
	attribute altera_attribute of en_ack: signal is "-name AUTO_SHIFT_REGISTER_RECOGNITION OFF; -name SYNCHRONIZER_IDENTIFICATION FORCED";
	
	signal fifo_data: std_logic_vector(0 downto 0);
	signal fifo_pop: std_logic;
	signal fifo_push: std_logic;
	signal fifo_dataout: std_logic_vector(31 downto 0);
	signal fifo_empty: std_logic;
	signal fifo_rdfull: std_logic; -- <- NEW CODE
	signal fifo_wrfull: std_logic; -- <- NEW CODE
	signal fifo_rdinhibit: std_logic; -- <- NEW CODE

--Generics can specify the bit-widths of components—such as adders, or can provide internal timing values.
	component dcfifo_mixed_widths
		generic(
			intended_device_family: string;
			lpm_numwords: natural;
			lpm_showahead: string;
			lpm_width: natural;
			lpm_widthu: natural;
			lpm_widthu_r: natural;
			lpm_width_r: natural;
			rdsync_delaypipe: natural;
			wrsync_delaypipe: natural
		);
		port(
			data: in std_logic_vector(0 downto 0);
			rdclk: in std_logic;
			rdreq: in std_logic;
			wrclk: in std_logic;
			wrreq: in std_logic;
			q: out std_logic_vector(31 downto 0);
			rdempty: out std_logic;
			rdfull: out std_logic; -- <- NEW CODE
			wrfull: out std_logic -- <- NEW CODE
		);
	end component dcfifo_mixed_widths;
	
begin
	
		bus_comb_proc: process(rstn, bus_read, bus_address, fifo_empty, fifo_dataout, fifo_rdinhibit, buscnt_running, kernelcnt_running, shadowcnt_running)
		begin
			bus_readdata <= x"deadbeef";
			bus_waitrequest <= '0';
			fifo_pop <= '0';
			if rstn = '1' then
				if bus_read = '1' then
					  case bus_address is 
						when "000" =>
						-- Read address 0: return SMACK ID stamp, which is hex(md5('SMACK'))[-8:] = 0xb477e410
							   bus_readdata <= x"b477e410";
						when "001" =>
						-- Read address 1: return the value dangling out of the FIFO, if it's valid
							if fifo_rdinhibit = '0' then -- <- NEW CODE
								if fifo_empty = '0' then -- <- NEW CODE
									bus_readdata <= fifo_dataout;
									fifo_pop <= '1';
								end if;
							else
								bus_waitrequest <= '1';
							end if;
						when "010" =>
						-- Read address 2: return fifo_depth as passed in via Qsys using generic fifo_depth
							bus_readdata <= std_logic_vector(to_unsigned(fifo_depth, 32));
						   
						when "100" => 
							if buscnt_running = '1' then
								bus_waitrequest <= '1';
							else 
								bus_readdata <= x"deafcafe";
							end if; 
						
						when "101" =>
							if kernelcnt_running = '0' then
								bus_readdata <= kernel_cnt;
							else
								bus_waitrequest <= '1';
							end if;
	
						when "110" =>
							if shadowcnt_running = '0' then
								bus_readdata <= shadow_cnt;
							else
								bus_waitrequest <= '1';
							end if;
						--get hash data from each controller kernel. This is currently the only difference between this and smack_ctrl	
						when "111"=> 
							bus_readdata <= std_logic_vector(to_unsigned(kernel_hash, 32));			
						when others =>
							   null;
					   end case;
				end if;
			else
				bus_waitrequest <= '1';
			end if;
		end process bus_comb_proc;
	
		bus_seq_proc: process
			variable cnt: integer range 0 to 2**16 - 1;
			variable buscnt: integer range 0 to 100000000;
			variable kernelcnt: integer range 0 to 2**16 - 1;
			variable shadowcnt: integer range 0 to 2**16 - 1;
		begin
			wait until clk'event and clk = '1';
			en <= '0';
			kernelen <= '0';
			shadowen <= '0';
			if rstn = '1' then
				
				if bus_write = '1' then
					case bus_address is
						when "011" =>
							cnt := ttl;
							fifo_rdinhibit <= '1';
	
						when "100" =>
							buscnt := 100000000;
							buscnt_running <= '1';
							
						when "101" =>
							kernelcnt := 200;
							kernelcnt_running <= '1';
	
						when "110" =>
							shadowcnt := 200;
							shadowcnt_running <= '1';
	
						when others =>
							null;
					end case;
				end if;
	
				if fifo_rdfull = '1' then
					fifo_rdinhibit <= '0';
				end if;
	
				if buscnt > 0 then
					buscnt := buscnt - 1;
				else
					buscnt_running <= '0';
				end if;
	
				
				if kernelcnt > 0 then
					if kernelcnt > 100 then
						kernelen <= '1';
					end if;
					kernelcnt := kernelcnt - 1;
				else
					kernelcnt_running <= '0';
				end if;
	
				if shadowcnt > 0 then
					if shadowcnt > 100 then
						shadowen <= '1';
					end if;
					shadowcnt := shadowcnt - 1;
				else
					shadowcnt_running <= '0';
				end if;
	
				if cnt > 0 then
					cnt := cnt - 1;
					en <= '1';
				end if;
			
			else
				cnt := 0;
				kernelcnt := 0;
				shadowcnt := 0;
				fifo_rdinhibit <= '0';
				buscnt_running <= '0';
				kernelcnt_running <= '0';
				shadowcnt_running <= '0';
			end if;
	
		end process bus_seq_proc;
		
		-- en handshaking into smack_sclk domain
		en_bus_proc: process
		begin
			wait until clk'event and clk = '1';
			if en_req(en_req'high) = en_ack(0) then
				en_req(en_req'high) <= en;
			end if;
			en_ack <= en_req(1) & en_ack(en_ack'high downto 1);
		end process en_bus_proc;
		
		en_smack_proc: process
		begin
			wait until smack_sclk'event and smack_sclk = '1';
			en_req(en_req'high - 1 downto 0) <= en_req(en_req'high downto 1);
		end process en_smack_proc;
		smack_en <= en_req(0);
	
		-- kernelen handshaking into smack_clk domain
		kernelen_bus_proc: process
		begin
			wait until clk'event and clk = '1';
			if kernelen_req(kernelen_req'high) = kernelen_ack(0) then
				kernelen_req(kernelen_req'high) <= kernelen;
			end if;
			kernelen_ack <= kernelen_req(1) & kernelen_ack(kernelen_ack'high downto 1);
		end process kernelen_bus_proc;
		
		kernelen_smack_proc: process
		begin
			wait until smack_clk'event and smack_clk = '1';
			kernelen_req(kernelen_req'high - 1 downto 0) <= kernelen_req(kernelen_req'high downto 1);
		end process kernelen_smack_proc;
		smack_kernelen <= kernelen_req(0);
	
		shadowen_bus_proc: process
		begin
			wait until clk'event and clk = '1';
			if shadowen_req(shadowen_req'high) = shadowen_ack(0) then
				shadowen_req(shadowen_req'high) <= shadowen;
			end if;
			shadowen_ack <= shadowen_req(1) & shadowen_ack(shadowen_ack'high downto 1);
		end process shadowen_bus_proc;
		
		shadowen_smack_proc: process
		begin
			wait until smack_sclk'event and smack_sclk = '1';
			shadowen_req(shadowen_req'high - 1 downto 0) <= shadowen_req(shadowen_req'high downto 1);
		end process shadowen_smack_proc;
		smack_shadowen <= shadowen_req(0);
	--This is component instantiation, a new level of hierarchy. 
		smack_fifo: dcfifo_mixed_widths
			generic map(
				intended_device_family => "Cyclone V",
				lpm_numwords => 2**integer(ceil(log2(real(fifo_depth)))), -- <- NEW CODE
				lpm_showahead => "ON",
				lpm_width => 1,
				lpm_widthu => integer(ceil(log2(real(fifo_depth)))), -- <- NEW CODE
				lpm_widthu_r => integer(ceil(log2(real(fifo_depth)))) - 5, -- <- NEW CODE
				lpm_width_r => 32,
				rdsync_delaypipe => meta_stages + 2,
				wrsync_delaypipe => meta_stages + 2
			)
			port map(
				data => fifo_data,
				rdclk => clk,
				rdreq => fifo_pop,
				wrclk => smack_sclk,
				wrreq => fifo_push,
				q => fifo_dataout,
				rdempty => fifo_empty,
				rdfull => fifo_rdfull, -- <- NEW CODE
				wrfull => fifo_wrfull -- <- NEW CODE
			);
		fifo_data(0) <= smack_scanout;
		
		-- NEW CODE BLOCK
		fifo_comb_proc: process(state, fifo_wrfull)
		begin
			case state is
				when '0' =>
					fifo_push <= '0';
				when '1' => 
					if fifo_wrfull = '1' then
						fifo_push <= '0';
					else
						fifo_push <= '1';
					end if;
				when others =>
					fifo_push <= '0'; 
			end case;
		end process fifo_comb_proc;
		
		kernel_seq_proc: process
			variable smack_kernelcnt: integer range 0 to 2**16 - 1;
		begin
			wait until smack_clk'event and smack_clk = '1';
			case kernel_state is
				when '0' =>
				   if smack_kernelen = '1' then
						smack_kernelcnt := 0;
						kernel_state <= '1';
					end if;
				when '1' =>
					kernel_cnt <= x"deafcafe";
					if smack_kernelen = '0' then
						kernel_cnt <= std_logic_vector(to_unsigned(smack_kernelcnt + 1, 32));
						kernel_state <= '0';
					end if;
					smack_kernelcnt := smack_kernelcnt + 1;
				when others =>
					null;
			end case;
	
		   end process kernel_seq_proc;
	 
		fifo_seq_proc: process
			variable smack_shadowcnt: integer range 0 to 2**16 - 1;
		begin
			wait until smack_sclk'event and smack_sclk = '1';
			case state is
				when '0' =>
					if en_req(1) = '0' and en_req(0) = '1' then
						state <= '1';
					end if;
				when '1' => 
					if fifo_wrfull = '1' then
						state <= '0';
					end if;
				when others =>
					null;
			end case;
	
			case shadow_state is
				when '0' =>
					if smack_shadowen = '1' then
						smack_shadowcnt := 0;
						shadow_state <= '1';
					end if;
				when '1' =>
					shadow_cnt <= x"deafcafe";
					if smack_shadowen = '0' then
						shadow_cnt <= std_logic_vector(to_unsigned(smack_shadowcnt + 1, 32));
						shadow_state <= '0';
					end if;
					smack_shadowcnt := smack_shadowcnt + 1;
				when others =>
					null;
			end case;
	
		   end process fifo_seq_proc;
		-- NEW CODE BLOCK END
	
		-- Hold scan chain input low to reset activity counters as their data is scanned out
		smack_scanin <= '0'; 
		
	end architecture rtl;