--------------------------------------------------------------------------
-- Project: JCPU, a portable 8-bit RISC CPU written in VHDL
-- This file: Types, constants and functions for JCPU
--
-- 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;


package cpu_pkg is

	-- Revision of the CPU
	constant REVISION : integer := 2;
	
	-- Chipram depth (can be tweaked)
	constant CHIPRAM_SIZE_BITS : integer := 10;
	
	-- Chipregister file depth
	constant CHIPREG_SIZE_BITS : integer := 8;

	-- Number of registers (can be tweaked)
	constant REG_SIZE_BITS : integer := 4;
	
	-- Instruction memory
	constant IMEM_ADDR_WIDTH : integer := 12;
	
	-- Data memory
	constant DMEM_DATA_WIDTH : integer := 8;
	constant DMEM_ADDR_WIDTH : integer := 8;
	
	-- Instruction format
	constant INST_OPCODE_WIDTH : integer := 6;
	constant IMEM_DATA_WIDTH : integer := INST_OPCODE_WIDTH + DMEM_DATA_WIDTH + REG_SIZE_BITS;

	-- Microcode ROM
	constant MUCODE_ADDR_WIDTH : integer := 9;
	
	-- Stack depth
	constant STACK_SIZE_BITS : integer := CHIPRAM_SIZE_BITS-1;

	--Types
	subtype iphase_t is integer range 0 to 1;
	type mem_access_t is (cmem_access, xmem_access, cio_access, xio_access);
	type reg_src_t is (alu_src, stk_src, cmem_src, xmem_src);
	type alu_src_t is (reg_a, reg_b, const);
	type ddata_src_t is (reg_a, reg_b, const);
	type daddr_src_t is (reg_a, reg_b, const);
	subtype instr_name_t is string(1 to 12);
	type instr_name_array_t is array (0 to 63) of instr_name_t;
	subtype opcode_t is unsigned (INST_OPCODE_WIDTH-1 downto 0);
	subtype inst_data_t is unsigned (IMEM_ADDR_WIDTH-1 downto 0);
	subtype inst_addr_t is unsigned (IMEM_ADDR_WIDTH-1 downto 0);
	subtype inst_t is unsigned (IMEM_DATA_WIDTH-1 downto 0);
	subtype dmem_data_t is unsigned (DMEM_DATA_WIDTH-1 downto 0);
	subtype dmem_addr_t is unsigned (DMEM_DATA_WIDTH-1 downto 0);
	subtype reg_ptr_t is unsigned (3 downto 0);
	type dmem_array_t is array (natural range <>) of dmem_data_t;
	type instr_addr_array is array (integer range <>) of inst_addr_t;
	subtype page_sel_t is unsigned (CHIPRAM_SIZE_BITS-DMEM_DATA_WIDTH-2 downto 0);
	
	type alu_op_t is
	(
		pass_op2,
		op1_add_op2,
		op1_sub_op2,
		op1_addc_op2,
		op1_subc_op2,
		op1_and_op2,
		op1_or_op2,
		op1_xor_op2,
		swap_op,
		shl_op,
		shr_op,
		rol_op,
		ror_op,
		rolc_op,
		rorc_op
	);
	
	type alu_status_t is record
		zero		: STD_LOGIC;
		carry		: STD_LOGIC;
	end record;

	type cpu_status_t is record
		alu			: alu_status_t;
	end record;

	type ctrl_lines_t is record
		reg_we			: STD_LOGIC;
		reg_src_sel		: reg_src_t;
		alu_op1_src_sel	: alu_src_t;
		alu_op2_src_sel	: alu_src_t;
		alu_opsel		: alu_op_t;
		alu_load		: STD_LOGIC;
		ddata_src_sel	: ddata_src_t;
		daddr_src_sel	: daddr_src_t; 
		pc_load			: STD_LOGIC;
		pc_inc			: STD_LOGIC;
		stk_push		: STD_LOGIC; 
		stk_pop			: STD_LOGIC;
		stk_pushd		: STD_LOGIC; 
		stk_popd		: STD_LOGIC;
		mem_access		: mem_access_t;
		mem_read		: STD_LOGIC;
		mem_write		: STD_LOGIC;
		int_exit		: STD_LOGIC;
	end record;

	type dpath_ctrl_out_t is record
		lines			: ctrl_lines_t;
		reg_a_ptr		: reg_ptr_t;
		reg_b_ptr		: reg_ptr_t;
	end record;
	
	type int_ctrl_in_t is record
		enable			: std_logic;
		polarity		: std_logic;
		edge_sens		: std_logic;
		request			: std_logic;
	end record;
	
	type creg_ctrl_out_t is record
		page_sel		: page_sel_t;
		int_ctrl		: int_ctrl_in_t;
		stk_high		: unsigned(IMEM_ADDR_WIDTH-DMEM_ADDR_WIDTH-1 downto 0);
		cmem_high		: unsigned(IMEM_ADDR_WIDTH-DMEM_ADDR_WIDTH-1 downto 0);
	end record;
	
	type creg_ctrl_in_t is record
		alu				: alu_status_t;
	end record;

	
	type murom_t is array (0 to integer(2**MUCODE_ADDR_WIDTH)-1) of ctrl_lines_t;

	constant instr_name_array : instr_name_array_t := 
	(
		"NOP         ", -- 00
		"HALT        ", -- 01
		"MOV|R|R     ", -- 02
		"MOV|R|K     ", -- 03
		"MOVX|R|Ri   ", -- 04
		"MOVX|R|Ki   ", -- 05
		"MOVX|Ri|R   ", -- 06
		"MOVX|Ri|K   ", -- 07
		"MOVX|Ki|R   ", -- 08
		"MOVC|R|Ri   ", -- 09
		"MOVC|R|Ki   ", -- 0A
		"MOVC|Ri|R   ", -- 0B
		"MOVC|Ri|K   ", -- 0C
		"MOVC|Ki|R   ", -- 0D
		"CMP|R|R     ", -- 0E
		"CMP|R|K     ", -- 0F
		"ADD|R|R     ", -- 10
		"ADD|R|K     ", -- 11
		"ADDC|R|R    ", -- 12
		"ADDC|R|K    ", -- 13
		"SUB|R|R     ", -- 14
		"SUB|R|K     ", -- 15
		"SUBC|R|R    ", -- 16
		"SUBC|R|K    ", -- 17
		"AND|R|R     ", -- 18
		"AND|R|K     ", -- 19
		"OR|R|R      ", -- 1A
		"OR|R|K      ", -- 1B
		"XOR|R|R     ", -- 1C
		"XOR|R|K     ", -- 1D
		"SHL|R       ", -- 1E
		"SHR|R       ", -- 1F
		"ROL|R       ", -- 20
		"ROR|R       ", -- 21
		"ROLC|R      ", -- 22
		"RORC|R      ", -- 23
		"XOUT|Ki|R   ", -- 24
		"XOUT|Ri|K   ", -- 25
		"COUT|Ki|R   ", -- 26
		"COUT|Ri|K   ", -- 27
		"XIN|R|Ki    ", -- 28
		"CIN|R|Ki    ", -- 29
		"SWAP|R      ", -- 2A
		"UNDEF       ", -- 2B
		"UNDEF       ", -- 2C
		"UNDEF       ", -- 2D
		"SUB|K|R     ", -- 2E
		"SUBC|K|R    ", -- 2F
		"JMP|K       ", -- 30
		"JZ|K        ", -- 31
		"JNZ|K       ", -- 32
		"JC|K        ", -- 33
		"JNC|K       ", -- 34
		"JLT|K       ", -- 35
		"JGT|K       ", -- 36
		"JLE|K       ", -- 37
		"JGE|K       ", -- 38
		"JEQ|K       ", -- 39
		"JNE|K       ", -- 3A
		"CALL|K      ", -- 3B
		"PUSH|R      ", -- 3C
		"POP|R       ", -- 3D
		"RET         ", -- 3E
		"RETI        "  -- 3F
	);
	-- Functions
	function Instr(opcode : opcode_t) return inst_t;
	function Instr(opcode : opcode_t; data : inst_data_t) return inst_t;
	function Instr(opcode : opcode_t; data : inst_data_t; reg : integer) return inst_t;
	function Instr(opcode : opcode_t; reg_a : integer) return inst_t;
	function Instr(opcode : opcode_t; reg_a, reg_b : integer) return inst_t;
	function Instr(opcode : opcode_t; reg_a, reg_b, reg_c : integer) return inst_t;
	function ctrl_lines_default return ctrl_lines_t;
	function idecoder(instr_name : instr_name_t; iphase : iphase_t; alu_status : alu_status_t) return ctrl_lines_t;
	function idecoder(opcode : opcode_t; iphase : iphase_t; alu_status : alu_status_t) return ctrl_lines_t;
	function gen_murom return murom_t;
	function get_muromaddr (opcode : opcode_t; iphase : iphase_t; alu_status : alu_status_t) return unsigned;
	function MIN (X, Y: INTEGER) return INTEGER;
	function MAX (X, Y: INTEGER) return INTEGER;

end cpu_pkg;

package body cpu_pkg is

	function Instr(opcode : opcode_t) return inst_t is
	variable inst : inst_t := (others => '0'); 
	begin
		inst(inst_t'length-1 downto inst_t'length-opcode_t'length) := opcode;
		return inst;

	end Instr;

	function Instr(opcode : opcode_t; data : inst_data_t) return inst_t is
	variable inst : inst_t := (others => '0'); 
	begin
		inst(inst_t'length-1 downto inst_t'length-opcode_t'length) := opcode;
		inst(inst_t'length-opcode_t'length-1 downto inst_t'length-opcode_t'length-inst_addr_t'length) := data;
		return inst;

	end Instr;

	function Instr(opcode : opcode_t; data : inst_data_t; reg : integer) return inst_t is
	variable inst : inst_t := (others => '0');
	begin
		inst(inst_t'length-1 downto inst_t'length-opcode_t'length) := opcode;
		inst(inst_t'length-opcode_t'length-1 downto inst_t'length-opcode_t'length-inst_data_t'length) := data;
		inst(reg_ptr_t'length-1 downto 0) := reg_ptr_t(to_unsigned(reg, reg_ptr_t'length));

		return inst;

	end Instr;

	function Instr(opcode : opcode_t; reg_a : integer) return inst_t is
	variable inst : inst_t := (others => '0');
	begin
		inst(inst_t'length-1 downto inst_t'length-opcode_t'length) := opcode;
		inst(reg_ptr_t'length-1 downto 0) := reg_ptr_t(to_unsigned(reg_a, reg_ptr_t'length));

		return inst;

	end Instr;

	function Instr(opcode : opcode_t; reg_a, reg_b : integer) return inst_t is
	variable inst : inst_t := (others => '0');
	begin
		inst(inst_t'length-1 downto inst_t'length-opcode_t'length) := opcode;
		inst(2*(reg_ptr_t'length)-1 downto reg_ptr_t'length) := reg_ptr_t(to_unsigned(reg_b, reg_ptr_t'length));
		inst(reg_ptr_t'length-1 downto 0) := reg_ptr_t(to_unsigned(reg_a, reg_ptr_t'length));

		return inst;

	end Instr;

	function Instr(opcode : opcode_t; reg_a, reg_b, reg_c : integer) return inst_t is
	variable inst : inst_t := (others => '0');
	begin
		inst(inst_t'length-1 downto inst_t'length-opcode_t'length) := opcode;
		inst(3*(reg_ptr_t'length)-1 downto 2*reg_ptr_t'length) := reg_ptr_t(to_unsigned(reg_c, reg_ptr_t'length));
		inst(2*(reg_ptr_t'length)-1 downto reg_ptr_t'length) := reg_ptr_t(to_unsigned(reg_b, reg_ptr_t'length));
		inst(reg_ptr_t'length-1 downto 0) := reg_ptr_t(to_unsigned(reg_a, reg_ptr_t'length));

		return inst;

	end Instr;

	function ctrl_lines_default return ctrl_lines_t is
	variable result : ctrl_lines_t;
	begin
		result.pc_load := '0';
		result.pc_inc := '0';
		result.stk_push := '0';
		result.stk_pop := '0';
		result.stk_pushd := '0';
		result.stk_popd := '0';
		result.reg_we := '0';
		result.reg_src_sel := alu_src;
		result.ddata_src_sel := reg_a;
		result.daddr_src_sel := reg_b;
		result.alu_opsel := pass_op2;
		result.alu_op1_src_sel := reg_a;
		result.alu_op2_src_sel := reg_b;
		result.alu_load := '0';
		result.mem_access := cmem_access;
		result.mem_read := '0';
		result.mem_write := '0';
		result.int_exit := '0';
		return result;
	
	end ctrl_lines_default;

	function idecoder(opcode : opcode_t; iphase : iphase_t; alu_status : alu_status_t) return ctrl_lines_t is
	variable instr_name : instr_name_t;
	begin
		instr_name := instr_name_array(to_integer(opcode));
		return idecoder(instr_name, iphase, alu_status);
	end idecoder;

	function idecoder(instr_name : instr_name_t; iphase : iphase_t; alu_status : alu_status_t) return ctrl_lines_t is
	variable result : ctrl_lines_t;
	variable ze, cy : STD_LOGIC;
	
	begin
		ze := alu_status.zero;
		cy := alu_status.carry;

		result := ctrl_lines_default;
		if iphase = 0 then
			result.pc_inc := '1';
		end if;

		case instr_name is

		when "HALT        " =>
			result.pc_inc := '0';
		when "MOV|R|R     " => -- R(a) <= R(b)
			result.reg_src_sel := alu_src;
			result.alu_opsel := pass_op2;
			result.alu_op2_src_sel := reg_b;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "MOV|R|K     " => -- R(a) <= #kk
			result.reg_src_sel := alu_src;
			result.alu_opsel := pass_op2;
			result.alu_op2_src_sel := const;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "MOVC|Ri|R   " => -- [R(b)] <= R(a)
			result.mem_access := cmem_access;
			result.daddr_src_sel := reg_b;
			result.ddata_src_sel := reg_a;
			if iphase = 0 then
				result.mem_write := '1';
			end if;
		when "MOVC|Ri|K   " => -- [R(a)] <= kk
			result.mem_access := cmem_access;
			result.daddr_src_sel := reg_a;
			result.ddata_src_sel := const;
			if iphase = 0 then
				result.mem_write := '1';
			end if;
		when "COUT|Ri|K   " => -- [R(a)] <= kk
			result.mem_access := cio_access;
			result.daddr_src_sel := reg_a;
			result.ddata_src_sel := const;
			if iphase = 0 then
				result.mem_write := '1';
			end if;
		when "MOVC|R|Ri   " => -- R(a) <= [R(b)]
			result.mem_access := cmem_access;
			result.reg_src_sel := cmem_src;
			result.daddr_src_sel := reg_b;
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "MOVC|R|Ki   " => -- R(a) <= [#addr]
			result.mem_access := cmem_access;
			result.reg_src_sel := cmem_src;
			result.daddr_src_sel := const;
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "CIN|R|Ki    " => -- R(a) <= [#addr]
			result.mem_access := cio_access;
			result.reg_src_sel := cmem_src;
			result.daddr_src_sel := const;
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "MOVC|Ki|R   " => -- [#addr] <= R(a)
			result.mem_access := cmem_access;
			result.daddr_src_sel := const;
			result.ddata_src_sel := reg_a;
			if iphase = 0 then
				result.mem_write := '1';
			end if;
		when "COUT|Ki|R   " => -- [#addr] <= R(a)
			result.mem_access := cio_access;
			result.daddr_src_sel := const;
			result.ddata_src_sel := reg_a;
			if iphase = 0 then
				result.mem_write := '1';
			end if;
		when "MOVX|Ri|R   " => -- [R(b)] <= R(a)
			result.mem_access := xmem_access;
			result.daddr_src_sel := reg_b;
			result.ddata_src_sel := reg_a;
			if iphase = 0 then
				result.mem_write := '1';
			end if;
		when "MOVX|Ri|K   " => -- [R(a)] <= kk
			result.mem_access := xmem_access;
			result.daddr_src_sel := reg_a;
			result.ddata_src_sel := const;
			if iphase = 0 then
				result.mem_write := '1';
			end if;
		when "XOUT|Ri|K   " => -- [R(a)] <= kk
			result.mem_access := xio_access;
			result.daddr_src_sel := reg_a;
			result.ddata_src_sel := const;
			if iphase = 0 then
				result.mem_write := '1';
			end if;
		when "MOVX|R|Ri   " => -- R(a) <= [R(b)]
			result.mem_access := xmem_access;
			result.reg_src_sel := xmem_src;
			result.daddr_src_sel := reg_b;
			if iphase = 0 then
				result.mem_read := '1';
			elsif iphase = 1 then
				result.reg_we := '1';
			end if;
		when "MOVX|R|Ki   " => -- R(a) <= [#addr]
			result.mem_access := xmem_access;
			result.reg_src_sel := xmem_src;
			result.daddr_src_sel := const;
			if iphase = 0 then
				result.mem_read := '1';
			elsif iphase = 1 then
				result.reg_we := '1';
			end if;
		when "XIN|R|Ki    " => -- R(a) <= [#addr]
			result.mem_access := xio_access;
			result.reg_src_sel := xmem_src;
			result.daddr_src_sel := const;
			if iphase = 0 then
				result.mem_read := '1';
			elsif iphase = 1 then
				result.reg_we := '1';
			end if;
		when "MOVX|Ki|R   " => -- [#addr] <= R(a)
			result.mem_access := xmem_access;
			result.daddr_src_sel := const;
			result.ddata_src_sel := reg_a;
			if iphase = 0 then
				result.mem_write := '1';
			end if;
		when "XOUT|Ki|R   " => -- [#addr] <= R(a)
			result.mem_access := xio_access;
			result.daddr_src_sel := const;
			result.ddata_src_sel := reg_a;
			if iphase = 0 then
				result.mem_write := '1';
			end if;
		when "ADD|R|R     " => -- R(a) <= R(a) + R(b)
			result.alu_opsel := op1_add_op2;
			result.alu_op1_src_sel := reg_a;
			result.alu_op2_src_sel := reg_b;
			result.reg_src_sel := alu_src;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "ADD|R|K     " => -- R(a) <= R(a) + k
			result.alu_opsel := op1_add_op2;
			result.alu_op1_src_sel := reg_a;
			result.alu_op2_src_sel := const;
			result.reg_src_sel := alu_src;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "ADDC|R|R    " => -- R(a) <= R(a) + R(b)
			result.alu_opsel := op1_addc_op2;
			result.alu_op1_src_sel := reg_a;
			result.alu_op2_src_sel := reg_b;
			result.reg_src_sel := alu_src;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "ADDC|R|K    " => -- R(a) <= R(a) + k
			result.alu_opsel := op1_addc_op2;
			result.alu_op1_src_sel := reg_a;
			result.alu_op2_src_sel := const;
			result.reg_src_sel := alu_src;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "SUB|R|R     " => -- R(a) <= R(a) + R(b)
			result.alu_opsel := op1_sub_op2;
			result.alu_op1_src_sel := reg_a;
			result.alu_op2_src_sel := reg_b;
			result.reg_src_sel := alu_src;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "SUB|R|K     " => -- R(a) <= R(a) + k
			result.alu_opsel := op1_sub_op2;
			result.alu_op1_src_sel := reg_a;
			result.alu_op2_src_sel := const;
			result.reg_src_sel := alu_src;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "SUB|K|R     " => -- R(a) <= k - R(a)
			result.alu_opsel := op1_sub_op2;
			result.alu_op1_src_sel := const;
			result.alu_op2_src_sel := reg_a;
			result.reg_src_sel := alu_src;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "SUBC|R|R    " => -- R(a) <= R(a) + R(b)
			result.alu_opsel := op1_subc_op2;
			result.alu_op1_src_sel := reg_a;
			result.alu_op2_src_sel := reg_b;
			result.reg_src_sel := alu_src;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "SUBC|R|K    " => -- R(a) <= R(a) + k
			result.alu_opsel := op1_subc_op2;
			result.alu_op1_src_sel := reg_a;
			result.alu_op2_src_sel := const;
			result.reg_src_sel := alu_src;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "SUBC|K|R    " => -- R(a) <= K - R(a)
			result.alu_opsel := op1_subc_op2;
			result.alu_op1_src_sel := const;
			result.alu_op2_src_sel := reg_a;
			result.reg_src_sel := alu_src;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "AND|R|R     " => -- R(a) <= R(a) and R(b)
			result.alu_opsel := op1_and_op2;
			result.alu_op1_src_sel := reg_a;
			result.alu_op2_src_sel := reg_b;
			result.reg_src_sel := alu_src;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "AND|R|K     " => -- R(a) <= R(a) and k
			result.alu_opsel := op1_and_op2;
			result.alu_op1_src_sel := reg_a;
			result.alu_op2_src_sel := const;
			result.reg_src_sel := alu_src;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "OR|R|R      " => -- R(a) <= R(a) or R(b)
			result.alu_opsel := op1_or_op2;
			result.alu_op1_src_sel := reg_a;
			result.alu_op2_src_sel := reg_b;
			result.reg_src_sel := alu_src;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "OR|R|K      " => -- R(a) <= R(a) or R(b)
			result.alu_opsel := op1_or_op2;
			result.alu_op1_src_sel := reg_a;
			result.alu_op2_src_sel := const;
			result.reg_src_sel := alu_src;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "XOR|R|R     " => -- R(a) <= R(a) xor R(b)
			result.alu_opsel := op1_xor_op2;
			result.alu_op1_src_sel := reg_a;
			result.alu_op2_src_sel := reg_b;
			result.reg_src_sel := alu_src;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "XOR|R|K     " => -- R(a) <= R(a) xor R(b)
			result.alu_opsel := op1_xor_op2;
			result.alu_op1_src_sel := reg_a;
			result.alu_op2_src_sel := const;
			result.reg_src_sel := alu_src;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "SWAP|R      " => -- R(a) <= R(b)
			result.reg_src_sel := alu_src;
			result.alu_opsel := swap_op;
			result.alu_op1_src_sel := reg_a;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "SHL|R       " => -- R(a) <= R(a) xor R(b)
			result.reg_src_sel := alu_src;
			result.alu_opsel := shl_op;
			result.alu_op1_src_sel := reg_a;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "SHR|R       " => -- R(a) <= R(a) xor R(b)
			result.reg_src_sel := alu_src;
			result.alu_opsel := shr_op;
			result.alu_op1_src_sel := reg_a;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "ROL|R       " => -- R(a) <= R(a) xor R(b)
			result.reg_src_sel := alu_src;
			result.alu_opsel := rol_op;
			result.alu_op1_src_sel := reg_a;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "ROR|R       " => -- R(a) <= R(a) xor R(b)
			result.reg_src_sel := alu_src;
			result.alu_opsel := ror_op;
			result.alu_op1_src_sel := reg_a;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "ROLC|R      " => -- R(a) <= R(a) xor R(b)
			result.reg_src_sel := alu_src;
			result.alu_opsel := rolc_op;
			result.alu_op1_src_sel := reg_a;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "RORC|R      " => -- R(a) <= R(a) xor R(b)
			result.reg_src_sel := alu_src;
			result.alu_opsel := rorc_op;
			result.alu_op1_src_sel := reg_a;
			result.alu_load := '1';
			if iphase = 1 then
				result.reg_we := '1';
			end if;
		when "CMP|R|R     " => -- 
			result.alu_opsel := op1_sub_op2;
			result.alu_op1_src_sel := reg_a;
			result.alu_op2_src_sel := reg_b;
			result.alu_load := '1';
		when "CMP|R|K     " => -- 
			result.alu_opsel := op1_sub_op2;
			result.alu_op1_src_sel := reg_a;
			result.alu_op2_src_sel := const;
			result.alu_load := '1';
		when "JMP|K       " => -- PC <= #addr
			if iphase = 0 then
				result.pc_load := '1';
			end if;
		when "JZ|K        " => -- PC <= #addr, zero
			if iphase = 0 then
				if ze = '1' then
					result.pc_load := '1';
				end if;
			end if;
		when "JNZ|K       " => -- PC <= #addr, !zero
			if iphase = 0 then
				if ze = '0' then
					result.pc_load := '1';
				end if;
			end if;
		when "JC|K        " => -- PC <= #addr, carry
			if iphase = 0 then
				if cy = '1' then
					result.pc_load := '1';
				end if;
			end if;
		when "JNC|K       " => -- PC <= #addr, !carry
			if iphase = 0 then
				if cy = '0' then
					result.pc_load := '1';
				end if;
			end if;
		when "JLT|K       " => -- PC <= #addr, !carry
			if iphase = 0 then
				if cy = '1' then
					result.pc_load := '1';
				end if;
			end if;
		when "JGT|K       " => -- 
			if iphase = 0 then
				if (cy or ze) = '0' then
					result.pc_load := '1';
				end if;
			end if;
		when "JLE|K       " => -- 
			if iphase = 0 then
				if (cy xor ze) = '1' then
					result.pc_load := '1';
				end if;
			end if;
		when "JGE|K       " => -- 
			if iphase = 0 then
				if (not cy or ze) = '1' then
					result.pc_load := '1';
				end if;
			end if;
		when "JEQ|K       " => -- 
			if iphase = 0 then
				if ze = '1' then
					result.pc_load := '1';
				end if;
			end if;
		when "JNE|K       " => -- 
			if iphase = 0 then
				if ze = '0' then
					result.pc_load := '1';
				end if;
			end if;
		when "PUSH|R      " => -- push
			if iphase = 0 then
				result.stk_pushd := '1';
			end if;
		when "POP|R       " => -- pop
			result.reg_src_sel := stk_src;
			if iphase = 0 then
				result.stk_popd := '1';
			elsif iphase = 1 then
				result.reg_we := '1';
			end if;
		when "CALL|K      " => -- call
			if iphase = 0 then
				result.pc_load := '1';
				result.stk_push := '1';
			end if;
		when "RET         " => -- return
			if iphase = 0 then
				result.pc_load := '1';
				result.stk_pop := '1';
			end if;
		when "RETI        " => -- return
			result.pc_inc := '0';
			result.int_exit := '1';
			
		when others => null; -- nop
		end case;

		return result;

	end idecoder;

	function gen_murom return murom_t is
	variable result : murom_t; 
	variable opcode : opcode_t;
	variable iphase : iphase_t;
	variable addr : unsigned(MUCODE_ADDR_WIDTH-1 downto 0);
	variable alu_status : alu_status_t;
	variable pos : integer := 0;
	
	begin
		for i in 0 to 2**addr'length-1 loop
			addr := to_unsigned(i, addr'length);
			opcode := addr(opcode'length-1 downto 0);
			pos := opcode'length;
			iphase := 0;
			if addr(pos) = '1' then
				iphase := 1;
			end if;
			pos := pos + 1;
			alu_status.zero := addr(pos);
			pos := pos + 1;
			alu_status.carry := addr(pos);
			
			result(i) := idecoder(opcode, iphase, alu_status);
		end loop;

		return result;
	end gen_murom;

	function get_muromaddr (opcode : opcode_t; iphase : iphase_t; alu_status : alu_status_t) return unsigned is
	variable addr : unsigned(MUCODE_ADDR_WIDTH-1 downto 0);
	begin
		addr := alu_status.carry & alu_status.zero & to_unsigned(iphase, 1) & opcode;
		return addr;
		
	end get_muromaddr;

	-------------------------------------------------------------
	function MIN (X, Y: INTEGER) return INTEGER is
	variable res : integer := X;
	begin
	if Y < X then
		res := Y;
	end if;

	return res;

	end MIN;

	-------------------------------------------------------------
	function MAX (X, Y: INTEGER) return INTEGER is
	variable res : integer := X;
	begin
	if Y > X then
		res := Y;
	end if;

	return res;
	
	end MAX;


end cpu_pkg;

