--------------------------------------------------------------------------
-- Project: JCPU, a portable 8-bit RISC CPU written in VHDL
-- This file: The arithmetic logic unit
--
-- 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 alu is
	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 alu;

architecture Behavioral of alu is

	signal sum_cy, sh_cy, sum_cy_in, sh_cy_in, left_shift, add_subn : STD_LOGIC;
	signal result, sum_res, lf_res, sh_res, res_reg : unsigned (data_width-1 downto 0);
	signal status, stat_reg : alu_status_t;
	
	--------------------------------------------------------------------------
	function test_zero(arg : unsigned) return STD_LOGIC is
	variable res : STD_LOGIC;
	begin
			res := '0';
			for i in arg'range loop
				res := res or To_X01(arg(i));
			end loop;

			return not res;
	end test_zero;

--------------------------------------------------------------------------
begin

--------------------------------------------------------------------------
proc_logic_func: 
	process (op1_in, op2_in, opsel)
	begin
		
		case opsel is
		when op1_and_op2 =>
			lf_res <= op1_in and op2_in;
		when op1_or_op2 =>
			lf_res <= op1_in or op2_in;
		when op1_xor_op2 =>
			lf_res <= op1_in xor op2_in;
		when others =>
			lf_res <= (others => '-');
		end case;

	end process;	

--------------------------------------------------------------------------
proc_shift_func: 
	process (op1_in, left_shift, sh_cy_in)
	begin
		if left_shift = '1' then
			sh_res <= op1_in(op1_in'left-1 downto op1_in'right) & sh_cy_in;
			sh_cy <= op1_in(op1_in'left);
		else	
			sh_res <= sh_cy_in & op1_in(op1_in'left downto op1_in'right+1);
			sh_cy <= op1_in(op1_in'right);
		end if;	
		
	end process;	

--------------------------------------------------------------------------
proc_alu_out:
	process(opsel, op1_in, op2_in, res_reg, stat_reg, lf_res, sum_res, sh_res, sum_cy, sh_cy, cy_in)

	begin
		
		case opsel is
		
		when op1_add_op2 | op1_sub_op2 | op1_addc_op2 | op1_subc_op2 =>
			status.zero <= test_zero(res_reg);
			status.carry <= sum_cy;
			result <= sum_res;
		when op1_and_op2 | op1_or_op2 | op1_xor_op2 =>
			status.zero <= test_zero(res_reg);
			status.carry <= stat_reg.carry;
			result <= lf_res;
		when shl_op | shr_op | rol_op | ror_op | rolc_op | rorc_op =>
			status.zero <= test_zero(res_reg);
			status.carry <= sh_cy;
			result <= sh_res;
		when swap_op =>
			result <= op1_in(op1_in'left-op1_in'length/2 downto op1_in'right) & op1_in(op1_in'left downto op1_in'right+op1_in'length/2);
			status <= stat_reg;
		when others =>
			result <= op2_in;
			status.zero <= stat_reg.zero;
			status.carry <= stat_reg.carry or cy_in;
		end case;
		
	end process;

--------------------------------------------------------------------------
proc_alu_in:
	process(opsel, op1_in, stat_reg)

	begin
		
		add_subn <= '-';
		sum_cy_in <= '-';
		sh_cy_in <= '-';
		left_shift <= '-';

		case opsel is
		
		when op1_add_op2 =>
			sum_cy_in <= '0';
			add_subn <= '1';

		when op1_sub_op2 =>
			sum_cy_in <= '0';
			add_subn <= '0';
	
		when op1_addc_op2 => 
			sum_cy_in <= stat_reg.carry;
			add_subn <= '1';

		when op1_subc_op2 => 
			sum_cy_in <= stat_reg.carry;
			add_subn <= '0';

		when shl_op =>
			sh_cy_in <= '0';
			left_shift <= '1';
			
		when shr_op =>
			sh_cy_in <= '0';
			left_shift <= '0';

		when rol_op =>
			sh_cy_in <= op1_in(op1_in'left);
			left_shift <= '1';

		when ror_op =>
			sh_cy_in <= op1_in(op1_in'right);
			left_shift <= '0';

		when rolc_op =>
			sh_cy_in <= stat_reg.carry;
			left_shift <= '1';

		when rorc_op =>
			sh_cy_in <= stat_reg.carry;
			left_shift <= '0';

		when others => null;

		end case;
		
	end process;

--------------------------------------------------------------------------
proc_res_reg:
	process(clk, rst, op_en, stat_load, res_reg, stat_reg)
	begin
		if rst = '1' then
			res_reg <= (others => '0');
			stat_reg <= (others => '0');
		elsif rising_edge(clk) then
			if op_en = '1' then
				res_reg <= result;
				stat_reg <= status;
			elsif stat_load = '1' then
				stat_reg  <= stat_in;
			end if;	
		end if;
		res_out <= res_reg;
		stat_out <= stat_reg;

	end process;

--------------------------------------------------------------------------
alu_addsub:
	process(op1_in, op2_in, sum_cy_in, add_subn)
	variable sum  : unsigned(data_width+1 downto 0);
	variable op1, op2 : unsigned(data_width+1 downto 0);
	begin
		if add_subn = '1' then
			op1 := '0' & op1_in & '1';
			op2 := '0' & op2_in & sum_cy_in;
			sum := op1 + op2; 
		else
			op1 := '0' & op1_in & not sum_cy_in;
			op2 := '0' & op2_in & '1';
			sum := op1 - op2; 
		end if;

		-- Form sum + carry
		sum_res <= sum(data_width downto 1);
		sum_cy <= sum(sum'left);
		
	end process;	

--------------------------------------------------------------------------
end Behavioral;

