--------------------------------------------------------------------------
-- Project: JCPU, a portable 8-bit RISC CPU written in VHDL
-- This file: JCPU top file 
--
-- Copyright (C) 2007 J. Ahrensfeld
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
--
-- For questions and ideas, please contact the author at jens@jayfield.org
--
--------------------------------------------------------------------------

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.numeric_std.ALL;

use work.cpu_pkg.all;

entity cpu is
	Generic (
		use_instr_register	: boolean := false;
		use_ctrl_rom		: boolean := true

	);
	Port (
		rst			: in  STD_LOGIC;
		clk			: in  STD_LOGIC;
		ce			: in  STD_LOGIC;
		int_in		: in  STD_LOGIC;
		int_ack		: out STD_LOGIC;
		xmem_we		: out STD_LOGIC;
		xmem_re		: out STD_LOGIC;
		instr_din	: in  unsigned (IMEM_DATA_WIDTH-1 downto 0);
		instr_addr	: out unsigned (IMEM_ADDR_WIDTH-1 downto 0);
		xmem_din	: in  unsigned (DMEM_DATA_WIDTH-1 downto 0);
		xmem_dout	: out unsigned (DMEM_DATA_WIDTH-1 downto 0);
		xmem_addr	: out unsigned (DMEM_ADDR_WIDTH-1 downto 0);
		io_sel		: out std_logic
	);
end cpu;

architecture rtl of cpu is

	COMPONENT stack_ctrl
	GENERIC (
		addr_width	: integer
	);
	Port (
		rst			: in  STD_LOGIC;
		clk			: in  STD_LOGIC;
		push		: in  STD_LOGIC;
		pop			: in  STD_LOGIC;
		din			: in  inst_addr_t;
		dout		: out inst_addr_t;
		mem_we		: out  STD_LOGIC;
		ptr_out		: out unsigned (addr_width-1 downto 0)
	);
	END COMPONENT;

	COMPONENT pc
	Port (
		rst			: in  STD_LOGIC;
		clk			: in  STD_LOGIC;
		inc			: in  STD_LOGIC;
		load		: in  STD_LOGIC;
		pc_in		: in  inst_addr_t;
		pc_out		: out inst_addr_t;
		pc_next		: out inst_addr_t
	);
	END COMPONENT;

	COMPONENT dpath_ctrl
	GENERIC (
		use_rom		: boolean
	);
	Port (
		inst_in		: in  inst_t;
		iphase_in	: in  iphase_t;
		status_in	: in  cpu_status_t;
		ctrl_out	: out dpath_ctrl_out_t;
		idout		: out inst_addr_t;
		ddout		: out dmem_data_t
	);
	END COMPONENT;

	COMPONENT int_ctrl
	Port (
		rst				: in  STD_LOGIC;
		clk				: in  STD_LOGIC;
		ctrl_in			: in  int_ctrl_in_t;
		int_in			: in  STD_LOGIC;
		int_exit		: in  STD_LOGIC;
		iphase_in		: in  iphase_t;
		int_ack_out		: out STD_LOGIC;
		stat_save_out	: out STD_LOGIC; 
		stat_rest_out	: out STD_LOGIC; 
		pc_addr_out		: out inst_addr_t;
		pc_load_out		: out STD_LOGIC;
		stk_push_out	: out STD_LOGIC;
		stk_pop_out		: out STD_LOGIC
	);
	END COMPONENT;

	COMPONENT reg_dual
	GENERIC (
		addr_width	: integer;
		data_width	: integer
	);
	Port (
		clk			: in  STD_LOGIC;
		we_a		: in  STD_LOGIC;
		ptr_a		: in  reg_ptr_t;
		ptr_b		: in  reg_ptr_t;
		din_a		: in  unsigned (data_width-1 downto 0);
		dout_a		: out unsigned (data_width-1 downto 0);
		dout_b		: out unsigned (data_width-1 downto 0)
	);
	END COMPONENT;

	COMPONENT chipram
	GENERIC (
		addr_width : integer;
		data_width : integer
	);
	Port (
		clka		: in  STD_LOGIC;
		clkb		: in  STD_LOGIC;
		en_a		: in  STD_LOGIC;
		en_b		: in  STD_LOGIC;
		we_a		: in  STD_LOGIC;
		we_b		: in  STD_LOGIC;
		addr_a		: in  unsigned (addr_width-1 downto 0);
		addr_b		: in  unsigned (addr_width-1 downto 0);
		din_a		: in  unsigned (data_width-1 downto 0);
		din_b		: in  unsigned (data_width-1 downto 0);
		dout_a		: out unsigned (data_width-1 downto 0);
		dout_b		: out unsigned (data_width-1 downto 0)
	);
	END COMPONENT;

	COMPONENT chipreg
	Port (
		rst			: in  STD_LOGIC; 
		clk			: in  STD_LOGIC;
		we			: in  STD_LOGIC;
		addr		: in  dmem_addr_t;
		din			: in  dmem_data_t;
		dout		: out dmem_data_t;
		ctrl_in		: in  creg_ctrl_in_t;
		ctrl_out	: out creg_ctrl_out_t
	);
	END COMPONENT;

	COMPONENT alu
	GENERIC (
		data_width : integer := 8
	);
	Port (
		rst			: in  STD_LOGIC;
		clk			: in  STD_LOGIC;
		op_en		: in  STD_LOGIC;
		cy_in		: in  STD_LOGIC;
		op1_in		: in  unsigned (data_width-1 downto 0);
		op2_in		: in  unsigned (data_width-1 downto 0);
		opsel		: in  alu_op_t;
		res_out		: out unsigned (data_width-1 downto 0);
		stat_load	: in  STD_LOGIC;
		stat_in		: in  alu_status_t;
		stat_out	: out alu_status_t
	);
	END COMPONENT;

	signal cpu_status, status_reg : cpu_status_t; -- := cpu_status_t(others => '0');
	signal ctrl : dpath_ctrl_out_t;
	signal stk_out : inst_addr_t;
	signal stk_in : inst_addr_t;
	signal stk_reg : inst_addr_t;
	signal pc_in, pc_out, pc_next, idata, int_pc_addr : inst_addr_t;
	signal iphase : iphase_t;
	signal const_data, reg_a_dout, reg_b_dout : dmem_data_t;
	signal reg_din : dmem_data_t;
	signal alu_op1, alu_op2, alu_result : dmem_data_t;
	signal alu_status : alu_status_t;
	signal pc_inc, pc_load, cpu_active, stk_we, cmem_we, creg_we : STD_LOGIC;
	signal ctrl_inst : inst_t;
	signal ctrl_iphase : iphase_t;
	signal was_pcld, was_pop2pc, int_pc_load, int_stk_push, int_stk_pop, stk_push, stk_pop : STD_LOGIC;
	signal status_save, status_restore : STD_LOGIC;
	signal creg_dout : dmem_data_t;
	signal cmem_din, cmem_dout : inst_addr_t;
	signal stk_addr, cmem_addr : unsigned(CHIPRAM_SIZE_BITS-1 downto 0);
	signal stk_ptr : unsigned(STACK_SIZE_BITS-1 downto 0);
	signal creg_ctrl_out : creg_ctrl_out_t;
	signal creg_ctrl_in : creg_ctrl_in_t;
	signal xio_sel, cio_sel : std_logic; 
	signal mem_data : dmem_data_t;
	signal mem_addr : dmem_data_t;
	
begin
	io_sel <= xio_sel;
	instr_addr <= pc_out;
	pc_inc <= cpu_active and ctrl.lines.pc_inc;
	stk_addr <= '0' & stk_ptr;
	cpu_status.alu <= alu_status;
	creg_ctrl_in.alu <= alu_status;

	xmem_dout <= mem_data;
	xmem_addr <= mem_addr;
	cmem_din <= creg_ctrl_out.cmem_high & mem_data;
	cmem_addr <= '1' & creg_ctrl_out.bank_sel & mem_addr(JMIN(cmem_addr'length-creg_ctrl_out.bank_sel'length-1, mem_addr'length)-1 downto 0);

--------------------------------------------------------------------
mem_ctrl_lines:
	process(ctrl.lines)
	begin
		xio_sel <= '0';
		xmem_re <= '0';
		xmem_we <= '0';
		cio_sel <= '0';
		cmem_we <= '0';
		creg_we <= '0';
		
		case ctrl.lines.mem_access is
			when xmem_access =>
				xmem_re <= ctrl.lines.mem_read;
				xmem_we <= ctrl.lines.mem_write;
			when xio_access =>
				xio_sel <= '1';
				xmem_re <= ctrl.lines.mem_read;
				xmem_we <= ctrl.lines.mem_write;
			when cmem_access =>
				cmem_we <= ctrl.lines.mem_write;
			when cio_access =>
				cio_sel <= '1';
				creg_we <= ctrl.lines.mem_write;
			when others => null;
		end case;
	end process;

iphase_counter:
	process(rst, clk, ce)
	variable pos : integer; 
	begin
		if rst = '1' then
			iphase <= iphase_t'low;
			cpu_active <= '0';
		elsif rising_edge(clk) then
			cpu_active <= '0';
			if ce = '1' then
				cpu_active <= '1';
				if iphase = iphase_t'high then
					iphase <= iphase_t'low;
				else
					iphase <= iphase + 1;
				end if;
			end if;	
		end if;
	end process;

gen_instr_reg:
if use_instr_register = true generate

instr_reg:
	process (rst, clk, ce)
	begin
		if rst = '1' then
			ctrl_inst <= (others => '0');
			ctrl_iphase <= 0;
		elsif rising_edge(clk) and ce = '1' then
			ctrl_inst <= instr_din;
			ctrl_iphase <= iphase;
		end if;
	end process;

end generate;
	
gen_instr_direct:
if use_instr_register = false generate

	ctrl_inst <= instr_din;
	ctrl_iphase <= iphase;

end generate;

status_register:
	process(clk, status_save)
	begin
		if rising_edge(clk) and status_save = '1' then
			status_reg.alu <= alu_status;
		end if;
	end process;

pc_pushpop:
	process(ctrl.lines, int_pc_load, int_stk_push, int_stk_pop, was_pop2pc)
	begin
		pc_load <= ctrl.lines.pc_load or int_pc_load;
		stk_push <= ctrl.lines.stk_push or (int_stk_push and not was_pop2pc);
		stk_pop <= (ctrl.lines.stk_pop and not ctrl.lines.pc_load) or int_stk_pop or ((ctrl.lines.stk_pop and ctrl.lines.pc_load) and not int_pc_load);
	end process;
	
lines_delay:
	process(clk)
	begin
		if rising_edge(clk) then
			was_pcld <= ctrl.lines.pc_load;
			was_pop2pc <= ctrl.lines.stk_pop and ctrl.lines.pc_load;
		end if;
	end process;

stk_mux:
	process(ctrl.lines, was_pcld, int_stk_push, pc_next, idata, reg_a_dout, creg_ctrl_out)
	begin
		if was_pcld = '1' and int_stk_push = '1' then 
			stk_in <= idata;
		elsif ctrl.lines.stk_push = '1' and ctrl.lines.pc_load = '0' then
			stk_in <= creg_ctrl_out.stk_high & reg_a_dout;
		else	
			stk_in <= pc_next;
		end if;	
		
	end process;

pc_mux:
	process(ctrl.lines, int_pc_load, int_stk_pop, idata, int_pc_addr, stk_out)
	begin
		
		pc_in <= stk_out;
		if int_pc_load = '1' then 
			if int_stk_pop = '0' then
				pc_in <= int_pc_addr;
			end if;	
		elsif ctrl.lines.pc_load = '1' then
			if ctrl.lines.stk_pop = '0' then
				pc_in <= idata;
			end if;	
		end if;	

	end process;

mem_data_mux:
	process(ctrl.lines.ddata_src_sel, reg_a_dout, reg_b_dout, const_data, creg_ctrl_out)
	begin
		case ctrl.lines.ddata_src_sel is
		when reg_a => 
			mem_data <= reg_a_dout;
		when reg_b =>
			mem_data <= reg_b_dout;
		when const =>
			mem_data <= const_data;
		when others =>
			mem_data <= const_data;
		end case;

	end process;

mem_addr_mux:
	process(ctrl.lines.daddr_src_sel, reg_a_dout, reg_b_dout, const_data, creg_ctrl_out.bank_sel)
	begin
		case ctrl.lines.daddr_src_sel is
		when reg_a => 
			mem_addr <= reg_a_dout;
		when reg_b =>
			mem_addr <= reg_b_dout;
		when const =>
			mem_addr <= const_data;
		when others =>
			mem_addr <= const_data;
		end case;

	end process;

reg_in_mux:
	process(ctrl.lines.reg_src_sel, cio_sel, xmem_din, alu_result, stk_out, cmem_dout, creg_dout)
	variable data : dmem_data_t;
	begin
		case ctrl.lines.reg_src_sel is
		when xmem_src =>
			data := xmem_din;
		when cmem_src =>
			if cio_sel = '0' then
				data := cmem_dout(dmem_data_t'range);
			else
				data := creg_dout(dmem_data_t'range);
			end if;
		when stk_src =>
			data := stk_out(dmem_data_t'range);
		when others =>
			data := alu_result;
		end case;

		reg_din <= data;

	end process;

alu_op1_mux:
	process(ctrl.lines.alu_op1_src_sel, reg_a_dout, reg_b_dout, const_data)
	variable data : dmem_data_t;
	begin
		case ctrl.lines.alu_op1_src_sel is
		when reg_a =>
			data := reg_a_dout;
		when reg_b =>
			data := reg_b_dout;
		when others =>
			data := const_data;
		end case;

		alu_op1 <= data;

	end process;

alu_op2_mux:
	process(ctrl.lines.alu_op2_src_sel, reg_a_dout, reg_b_dout, const_data)
	variable data : dmem_data_t;
	begin
		case ctrl.lines.alu_op2_src_sel is
		when reg_a =>
			data := reg_a_dout;
		when reg_b =>
			data := reg_b_dout;
		when others =>
			data := const_data;
		end case;

		alu_op2 <= data;

	end process;

--------------------------------------------------------------------
	inst_stack_ctrl: stack_ctrl
	GENERIC MAP (
		addr_width	=> STACK_SIZE_BITS
	)
	PORT MAP(
		rst			=> rst,
		clk			=> clk,
		push		=> stk_push,
		pop			=> stk_pop,
		dout		=> stk_reg,
		din			=> stk_in,
		ptr_out		=> stk_ptr,
		mem_we		=> stk_we
	);

	inst_pc: pc
	PORT MAP(
		rst			=> rst,
		clk			=> clk,
		inc			=> pc_inc,
		load		=> pc_load,
		pc_in		=> pc_in,
		pc_out		=> pc_out,
		pc_next		=> pc_next
	);

	inst_dpath_ctrl: dpath_ctrl
	GENERIC MAP (
		use_rom		=> use_ctrl_rom
	)
	PORT MAP(
		inst_in		=> ctrl_inst,
		iphase_in   => ctrl_iphase,
		status_in	=> cpu_status,
		ctrl_out	=> ctrl,
		ddout		=> const_data,
		idout		=> idata
	);
	
	inst_int_ctrl: int_ctrl
	PORT MAP(
		rst				=> rst,
		clk				=> clk,
		ctrl_in			=> creg_ctrl_out.int_ctrl,
		int_in			=> int_in,
		int_ack_out		=> int_ack,
		int_exit		=> ctrl.lines.int_exit,
		iphase_in		=> ctrl_iphase,
		stat_save_out	=> status_save,
		stat_rest_out	=> status_restore,
		pc_addr_out		=> int_pc_addr,
		pc_load_out		=> int_pc_load,
		stk_push_out	=> int_stk_push,
		stk_pop_out		=> int_stk_pop
		
	);
	
	inst_reg_ab: reg_dual
	GENERIC MAP (
		addr_width	=> REG_SIZE_BITS,
		data_width	=> DMEM_DATA_WIDTH
	)
	PORT MAP(
		clk			=> clk,
		we_a		=> ctrl.lines.reg_we,
		ptr_a		=> ctrl.reg_a_ptr,
		ptr_b		=> ctrl.reg_b_ptr,
		din_a		=> reg_din,
		dout_a		=> reg_a_dout,
		dout_b		=> reg_b_dout
	);

	inst_chipram: chipram
	GENERIC MAP (
		addr_width	=> CHIPRAM_SIZE_BITS,
		data_width	=> IMEM_ADDR_WIDTH
	)
	PORT MAP(
		clka		=> clk,
		clkb		=> clk,
		en_a		=> cpu_active,
		en_b		=> cpu_active,
		we_a		=> stk_we,
		we_b		=> cmem_we,
		addr_a		=> stk_addr,
		addr_b		=> cmem_addr,
		din_a		=> stk_reg,
		din_b		=> cmem_din,
		dout_a		=> stk_out,
		dout_b		=> cmem_dout
	);

	inst_chipreg: chipreg
	PORT MAP(
		rst			=> rst,
		clk			=> clk,
		we			=> creg_we,
		addr		=> mem_addr,
		din			=> mem_data,
		dout		=> creg_dout,
		ctrl_in		=> creg_ctrl_in,
		ctrl_out	=> creg_ctrl_out
	);

	inst_alu: alu
	GENERIC MAP (
		data_width	=> DMEM_DATA_WIDTH
	)
	PORT MAP(
		rst			=> rst,
		clk			=> clk,
		op_en		=> ctrl.lines.alu_load,
		cy_in		=> ctrl.lines.alu_cy_in,
		op1_in		=> alu_op1,
		op2_in		=> alu_op2,
		opsel		=> ctrl.lines.alu_opsel,
		res_out		=> alu_result,
		stat_load	=> status_restore,
		stat_in		=> status_reg.alu,
		stat_out	=> alu_status
	);

end rtl;
