library ieee;
use ieee.std_logic_1164.all;

use work.core_pkg.all;
use work.mem_pkg.all;
use work.op_pkg.all;

entity pipeline is
	port (
		clk    : in  std_logic;
		res_n  : in  std_logic;

		-- instruction interface
		mem_i_out    : out mem_out_type;
		mem_i_in     : in  mem_in_type;

		-- data interface
		mem_d_out    : out mem_out_type;
		mem_d_in     : in  mem_in_type
	);
end entity;

architecture impl of pipeline is
	type pipeline_ctrl_t is record
		stall       : std_logic;
		flush       : std_logic;
		stall_fetch : std_logic;
		stall_dec   : std_logic;
		stall_exec  : std_logic;
		stall_mem   : std_logic;
		stall_wb    : std_logic;

		flush_fetch : std_logic;
		flush_dec   : std_logic;
		flush_exec  : std_logic;
		flush_mem   : std_logic;
		flush_wb    : std_logic;
	end record;
	signal ctrl : pipeline_ctrl_t;

	type fetch_t is record
		-- to control
		mem_busy : std_logic;

		pcsrc  : std_logic;
		pc_in  : pc_type;
		pc_out : pc_type;
		instr  : instr_type;
	end record;
	signal fetch : fetch_t;

	type decode_t is record
		-- from fetch
		pc_in : pc_type;
		instr : instr_type;

		-- from writeback
		reg_write : reg_write_type;

		-- towards next stages
		pc_out  : pc_type;
		exec_op : exec_op_type;
		mem_op  : mem_op_type;
		wb_op   : wb_op_type;

		-- exceptions
		exc_dec : std_logic;
	end record;
	signal decode : decode_t;

	type exec_t is record
		-- from DEC
		op    : exec_op_type;
		pc_in : pc_type;

		-- to MEM
		pc_old_out : pc_type;
		pc_new_out : pc_type;
		aluresult  : data_type;
		wrdata     : data_type;
		zero       : std_logic;

		memop_in  : mem_op_type;
		memop_out : mem_op_type;
		wbop_in   : wb_op_type;
		wbop_out  : wb_op_type;

		-- FWD
		exec_op       : exec_op_type;
		reg_write_mem : reg_write_type;
		reg_write_wr  : reg_write_type;
	end record;
	signal exec : exec_t;

	type mem_t is record
		-- to Ctrl
		mem_busy : std_logic;

		-- from EXEC
		mem_op       : mem_op_type;
		wbop_in      : wb_op_type;
		pc_new_in    : pc_type;
		pc_old_in    : pc_type;
		aluresult_in : data_type;
		wrdata       : data_type;
		zero         : std_logic;

		-- to EXEC (forwarding)
		reg_write : reg_write_type;

		-- to FETCH
		pc_new_out : pc_type;
		pcsrc      : std_logic;

		-- to WB
		wbop_out      : wb_op_type;
		pc_old_out    : pc_type;
		aluresult_out : data_type;
		memresult     : data_type;

		-- exceptions
		exc_load  : std_logic;
		exc_store : std_logic;
	end record;
	signal mem : mem_t;

	type wb_t is record
		-- from MEM
		op        : wb_op_type;
		aluresult : data_type;
		memresult : data_type;
		pc_old_in : pc_type;

		-- to FWD and DEC
		reg_write : reg_write_type;
	end record;
	signal wb : wb_t;
	
	signal cntrl_res_n, cntrl_res_n_next : std_logic := '0';

begin

	-- synchronous
	res_cntrl : process(clk, res_n)
	begin
		if res_n = '0' then
			cntrl_res_n_next <= '0';
		else 
			cntrl_res_n_next <= '1';
		end if;

		if rising_edge(clk) then
			cntrl_res_n <= cntrl_res_n_next;
		end if;
	end process;

	-- concurrent
	ctrl.stall <= fetch.mem_busy or mem.mem_busy or not cntrl_res_n;
	ctrl.flush <= '0';

	-- structural
	-- STAGE 1
	fetch_inst : entity work.fetch
	port map (
		clk   => clk,
		res_n => res_n,

		stall => ctrl.stall or ctrl.stall_fetch,
		flush => ctrl.flush or ctrl.flush_fetch,

		-- to control
		mem_busy => fetch.mem_busy,

		pcsrc  => fetch.pcsrc,
		pc_in  => fetch.pc_in,
		pc_out => fetch.pc_out,
		instr  => fetch.instr,

		-- memory controller interface
		mem_out => mem_i_out,
		mem_in  => mem_i_in
	);
	
	fetch.pcsrc <= mem.pcsrc;
	fetch.pc_in <= mem.pc_new_out;

	-- STAGE 2
	decode_inst : entity work.decode
	port map (
		clk   => clk,
		res_n => res_n,

		stall => ctrl.stall or ctrl.stall_dec,
		flush => ctrl.flush or ctrl.flush_dec,

		-- from fetch
		pc_in => decode.pc_in,
		instr => decode.instr,

		-- from writeback
		reg_write => decode.reg_write,

		-- towards next stages
		pc_out  => decode.pc_out,
		exec_op => decode.exec_op,
		mem_op  => decode.mem_op,
		wb_op   => decode.wb_op,

		-- exceptions
		exc_dec => decode.exc_dec
	);
	decode.pc_in     <= fetch.pc_out;
	decode.instr     <= fetch.instr;
	decode.reg_write <= wb.reg_write;

	-- STAGE 3
	execute_inst : entity work.exec
	port map (
		clk   => clk,
		res_n => res_n,

		stall => ctrl.stall or ctrl.stall_exec,
		flush => ctrl.flush or ctrl.flush_exec,

		-- from DEC
		op    => exec.op,
		pc_in => exec.pc_in,

		-- to MEM
		pc_old_out => exec.pc_old_out,
		pc_new_out => exec.pc_new_out,
		aluresult  => exec.aluresult,
		wrdata     => exec.wrdata,
		zero       => exec.zero,

		memop_in  => exec.memop_in,
		memop_out => exec.memop_out,
		wbop_in   => exec.wbop_in,
		wbop_out  => exec.wbop_out,

		-- FWD
		exec_op       => exec.exec_op,
		reg_write_mem => exec.reg_write_mem,
		reg_write_wr  => exec.reg_write_wr
	);
	exec.pc_in    <= decode.pc_out;
	exec.op       <= decode.exec_op;
	exec.memop_in <= decode.mem_op;
	exec.wbop_in  <= decode.wb_op;
	exec.reg_write_mem <= mem.reg_write;
	exec.reg_write_wr  <= wb.reg_write;

	-- STAGE 4
	memory_inst : entity work.mem
	port map (
		clk   => clk,
		res_n => res_n,

		stall => ctrl.stall or ctrl.stall_mem,
		flush => ctrl.flush or ctrl.flush_mem,

		-- to Ctrl
		mem_busy => mem.mem_busy,

		-- from EXEC
		mem_op       => mem.mem_op,
		wbop_in      => mem.wbop_in,
		pc_new_in    => mem.pc_new_in,
		pc_old_in    => mem.pc_old_in,
		aluresult_in => mem.aluresult_in,
		wrdata       => mem.wrdata,
		zero         => mem.zero,

		-- to EXEC (forwarding)
		reg_write => mem.reg_write,

		-- to FETCH
		pc_new_out => mem.pc_new_out,
		pcsrc      => mem.pcsrc,

		-- to WB
		wbop_out      => mem.wbop_out,
		pc_old_out    => mem.pc_old_out,
		aluresult_out => mem.aluresult_out,
		memresult     => mem.memresult,

		-- memory controller interface
		mem_out => mem_d_out,
		mem_in  => mem_d_in,

		-- exceptions
		exc_load  => mem.exc_load,
		exc_store => mem.exc_store
	);
	-- to MEM
	mem.pc_old_in    <= exec.pc_old_out;
	mem.pc_new_in    <= exec.pc_new_out;
	mem.aluresult_in <= exec.aluresult;
	mem.wrdata       <= exec.wrdata;
	mem.zero         <= exec.zero;
	mem.mem_op       <= exec.memop_out;
	mem.wbop_in     <= exec.wbop_out;

	-- STAGE 5
	writeback_inst : entity work.wb
	port map (
		clk   => clk,
		res_n => res_n,

		stall => ctrl.stall or ctrl.stall_wb,
		flush => ctrl.flush or ctrl.flush_wb,

		-- from MEM
		op        => wb.op,
		aluresult => wb.aluresult,
		memresult => wb.memresult,
		pc_old_in => wb.pc_old_in,

		-- to FWD and DEC
		reg_write => wb.reg_write
	);
	wb.op        <= mem.wbop_out;
	wb.aluresult <= mem.aluresult_out;
	wb.memresult <= mem.memresult;
	wb.pc_old_in <= mem.pc_old_out;
	
	-- CTRL
	ctrl_inst : entity work.ctrl
	port map (
		clk => clk,
		res_n => res_n,
		stall => ctrl.stall,
		
		stall_fetch  => ctrl.stall_fetch,
		stall_dec    => ctrl.stall_dec,
		stall_exec   => ctrl.stall_exec,
		stall_mem    => ctrl.stall_mem,
		stall_wb     => ctrl.stall_wb,
		
		flush_fetch  => ctrl.flush_fetch,
		flush_dec    => ctrl.flush_dec,
		flush_exec   => ctrl.flush_exec,
		flush_mem    => ctrl.flush_mem,
		flush_wb     => ctrl.flush_wb,
		
		wb_op_exec   => exec.wbop_out,
		exec_op_dec  => decode.exec_op,

		pcsrc_in     => mem.pcsrc,
		pcsrc_out    => open
	);
	
end architecture;
