--------------------------------------------------------------------------
-- Project: JIPS, a portable 32-bit RISC CPU written in VHDL
-- This file: The arithmetic logic unit
--
-- Copyright (C) 2008 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.mips_types.all;

entity alu is
	Generic 
	(
		data_width : integer := 8
	);
	Port 
	(
		op1_in		: in  unsigned (data_width-1 downto 0);
		op2_in		: in  unsigned (data_width-1 downto 0);
		op2_shifted	: in  unsigned (data_width-1 downto 0);
		ctrl		: in  alu_ctrl_t;
		result		: out unsigned (data_width-1 downto 0);
		flags		: out alu_flags_t
	);
end alu;

architecture Behavioral of alu is

	signal sum_res			: unsigned (data_width-1 downto 0);
	signal and_res			: unsigned (data_width-1 downto 0);
	signal xor_res			: unsigned (data_width-1 downto 0);
	signal nor_res			: unsigned (data_width-1 downto 0);
	signal or_res			: unsigned (data_width-1 downto 0);
	signal eq, sa, sb, sr, c	: STD_LOGIC;
	signal lts, ltu				: STD_LOGIC;

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

	eq			<= '1' when op1_in = op2_in else '0';
	sa			<= op1_in(op1_in'left);
	sb			<= op2_in(op2_in'left);
	sr			<= sum_res(sum_res'left);
	
	flags.uvf	<= (not ctrl.add) and ((sa and (not sb) and (not sr)) or ((not sa) and sb and sr));
	flags.ovf	<= ctrl.add and ((sa and sb and (not sr)) or ((not sa) and (not sb) and sr));
	lts			<= not eq and ((not c and sa) or (not sb and sa) or (not sb and sr));
	
	ltu			<= (not eq) and (not c);
	flags.lts	<= lts;
	flags.ltu	<= ltu;
	flags.c		<= c;
	
--------------------------------------------------------------------------
proc_op_and:
	process(op1_in, op2_in)
	variable x1, x2	: unsigned (data_width-1 downto 0);
	begin
		x1 := op1_in;
		x2 := op2_in;

		and_res <= (x1 and x2);
		
	end process;

--------------------------------------------------------------------------
proc_proc_xor:
	process(op1_in, op2_in)
	variable x1, x2	: unsigned (data_width-1 downto 0);
	begin
		x1 := op1_in;
		x2 := op2_in;
		
		xor_res <= (x1 xor x2);
		
	end process;

--------------------------------------------------------------------------
proc_proc_nor:
	process(op1_in, op2_in)
	variable x1, x2	: unsigned (data_width-1 downto 0);
	begin
		x1 := op1_in;
		x2 := op2_in;
		
		nor_res <= (x1 nor x2);
		
	end process;

--------------------------------------------------------------------------
proc_proc_or:
	process(op1_in, op2_in)
	variable x1, x2	: unsigned (data_width-1 downto 0);
	begin
		x1 := op1_in;
		x2 := op2_in;
		
		or_res <= (x1 or x2);
		
	end process;

--------------------------------------------------------------------------
proc_alu_out:
	process(ctrl, sum_res, and_res, xor_res, nor_res, or_res, op2_shifted, lts, ltu)
	begin
		
		result		<= sum_res;
		
		case ctrl.outsel is
		
		when alu_adder =>
			result 		<= sum_res;

		when alu_and =>
			result 		<= and_res;
			
		when alu_xor =>
			result 		<= xor_res;

		when alu_nor =>
			result 		<= nor_res;

		when alu_or =>
			result 		<= or_res;

		when alu_shift2 =>
			result 		<= op2_shifted;

		when alu_ltu =>
			result		<= (data_width-1 downto 1 => '0') & ltu;

		when alu_lts =>
			result		<= (data_width-1 downto 1 => '0') & lts;

		when others => null;

		end case;
		
	end process;

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

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

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