---------------------------------------------------------------------------
-- (c) 2013 mark watson
-- I am happy for anyone to use this for non-commercial use.
-- If my vhdl files are used commercially or otherwise sold,
-- please contact me for explicit permission at scrameta (gmail).
-- This applies for source and binary form and derived works.
---------------------------------------------------------------------------
LIBRARY ieee;
USE ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.ALL;
use ieee.numeric_std.ALL;
use IEEE.STD_LOGIC_MISC.all;

library work;
use work.zpupkg.all;

ENTITY zpu_glue IS
PORT 
(
	CLK : in std_logic;
	RESET : in std_logic;
	PAUSE : in std_logic;

	ZPU_DI : in std_logic_vector(31 downto 0); -- response from general memory - for areas that only support 8/16 bit set top bits to 0
	ZPU_ROM_DI : in std_logic_vector(31 downto 0); -- response from own program memory
	ZPU_RAM_DI : in std_logic_vector(31 downto 0); -- response from own stack	
	ZPU_CONFIG_DI : in std_logic_vector(31 downto 0); -- response from config registers
	
	ZPU_DO : out std_logic_vector(31 downto 0);
	
	ZPU_ADDR_ROM_RAM : out std_logic_vector(15 downto 0); -- direct from zpu, for short paths
	ZPU_ADDR_FETCH : out std_logic_vector(23 downto 0); -- clk->q, for longer paths
	
	-- request
	MEMORY_FETCH : out std_logic;
	ZPU_READ_ENABLE : out std_logic;
	ZPU_32BIT_WRITE_ENABLE : out std_logic; -- common case
	ZPU_16BIT_WRITE_ENABLE : out std_logic; -- for sram (never happens yet!)
	ZPU_8BIT_WRITE_ENABLE : out std_logic; -- for hardware regs
	
	-- config
	ZPU_CONFIG_WRITE : out std_logic;
	ZPU_CONFIG_READ : out std_logic;
	
	-- stack request
	ZPU_STACK_WRITE : out std_logic_vector(3 downto 0);

	-- write to ROM!!
	ZPU_ROM_WREN : out std_logic;

	-- response
	MEMORY_READY : in std_logic
);
END zpu_glue;

architecture sticky of zpu_glue is
component ZPUMediumCore is
   generic(
      WORD_SIZE    : integer:=32;  -- 16/32 (2**wordPower)
      ADDR_W       : integer:=24;  -- Total address space width (incl. I/O)
      MEM_W        : integer:=16;  -- Memory (prog+data+stack) width - stack at end of memory - so end of sdram. 32K ROM, 32K RAM (MAX)
      D_CARE_VAL   : std_logic:='X'; -- Value used to fill the unsused bits
      MULT_PIPE    : boolean:=false; -- Pipeline multiplication
      BINOP_PIPE   : integer range 0 to 2:=0; -- Pipeline binary operations (-, =, < and <=)
      ENA_LEVEL0   : boolean:=true;  -- eq, loadb, neqbranch and pushspadd
      ENA_LEVEL1   : boolean:=true;  -- lessthan, ulessthan, mult, storeb, callpcrel and sub
      ENA_LEVEL2   : boolean:=true; -- lessthanorequal, ulessthanorequal, call and poppcrel
      ENA_LSHR     : boolean:=true;  -- lshiftright
      ENA_IDLE     : boolean:=false; -- Enable the enable_i input
      FAST_FETCH   : boolean:=true); -- Merge the st_fetch with the st_execute states
   port(
      clk_i        : in  std_logic; -- CPU Clock
      reset_i      : in  std_logic; -- Sync Reset
      enable_i     : in  std_logic; -- Hold the CPU (after reset)
      break_o      : out std_logic; -- Break instruction executed
      dbg_o        : out zpu_dbgo_t; -- Debug outputs (i.e. trace log)
      -- Memory interface
      mem_busy_i   : in  std_logic; -- Memory is busy
      data_i       : in  unsigned(WORD_SIZE-1 downto 0); -- Data from mem
      data_o       : out unsigned(WORD_SIZE-1 downto 0); -- Data to mem
      addr_o       : out unsigned(ADDR_W-1 downto 0); -- Memory address
      write_en_o   : out std_logic;  -- Memory write enable (32-bit)
      read_en_o    : out std_logic; -- Memory read enable (32-bit)
		byte_read_o : out std_logic;
		byte_write_o : out std_logic;
		short_write_o: out std_logic); -- never happens
	end component;	
			
	signal zpu_addr_unsigned : unsigned(23 downto 0);
	signal zpu_do_unsigned : unsigned(31 downto 0);
	signal ZPU_DI_unsigned : unsigned(31 downto 0);
	signal zpu_break : std_logic;
	signal zpu_debug : zpu_dbgo_t;
	
	signal zpu_mem_busy : std_logic;
	
	signal zpu_memory_fetch_pending_next : std_logic;
	signal zpu_memory_fetch_pending_reg : std_logic;
	
	signal ZPU_32bit_READ_ENABLE_temp : std_logic;
	signal ZPU_8bit_READ_ENABLE_temp : std_logic;	
	signal ZPU_READ_temp : std_logic;
	signal ZPU_32BIT_WRITE_ENABLE_temp : std_logic;
	signal ZPU_16BIT_WRITE_ENABLE_temp : std_logic;
	signal ZPU_8BIT_WRITE_ENABLE_temp : std_logic;	
	signal ZPU_WRITE_temp : std_logic;
	
	signal ZPU_32BIT_WRITE_ENABLE_next : std_logic;
	signal ZPU_16BIT_WRITE_ENABLE_next : std_logic;
	signal ZPU_8BIT_WRITE_ENABLE_next : std_logic;	
	signal ZPU_READ_next : std_logic;
	signal ZPU_32BIT_WRITE_ENABLE_reg : std_logic;
	signal ZPU_16BIT_WRITE_ENABLE_reg : std_logic;
	signal ZPU_8BIT_WRITE_ENABLE_reg : std_logic;	
	signal ZPU_READ_reg : std_logic;
	
	signal block_mem : std_logic;
	signal config_mem : std_logic;
	signal special_mem : std_logic;
	
	signal result_next : std_logic_vector(4 downto 0);
	signal result_reg : std_logic_vector(4 downto 0);
	constant result_external   : std_logic_vector(4 downto 0) := "00000";
	constant result_ram        : std_logic_vector(4 downto 0) := "00001";
	constant result_ram_8bit_0 : std_logic_vector(4 downto 0) := "00010";
	constant result_ram_8bit_1 : std_logic_vector(4 downto 0) := "00011";
	constant result_ram_8bit_2 : std_logic_vector(4 downto 0) := "00100";
	constant result_ram_8bit_3 : std_logic_vector(4 downto 0) := "00101";
	constant result_rom        : std_logic_vector(4 downto 0) := "00110";
	constant result_rom_8bit_0 : std_logic_vector(4 downto 0) := "00111";
	constant result_rom_8bit_1 : std_logic_vector(4 downto 0) := "01000";
	constant result_rom_8bit_2 : std_logic_vector(4 downto 0) := "01001";
	constant result_rom_8bit_3 : std_logic_vector(4 downto 0) := "01010";	
	constant result_config     : std_logic_vector(4 downto 0) := "01011";	
	constant result_external_special : std_logic_vector(4 downto 0) := "01100";
	
	signal request_type : std_logic_vector(4 downto 0);
	
	signal zpu_di_use : std_logic_vector(31 downto 0);
	
	signal memory_access : std_logic;

	-- 1 cycle delay on memory read - needed to allow running at higher clock
	signal zpu_di_next : std_logic_vector(31 downto 0);
	signal zpu_di_reg : std_logic_vector(31 downto 0);
	
	signal memory_ready_next : std_logic;
	signal memory_ready_reg : std_logic;
	
	signal zpu_enable : std_logic;
	
	signal zpu_addr_next : std_logic_vector(23 downto 0);
	signal zpu_addr_reg : std_logic_vector(23 downto 0);

	signal ZPU_DO_next : std_logic_vector(31 downto 0);
	signal ZPU_DO_reg : std_logic_vector(31 downto 0);
	
begin
		-- register
	process(clk,reset)
	begin
		if (reset='1') then
			zpu_memory_fetch_pending_reg <= '0';
			result_reg <= result_rom;
			zpu_di_reg <= (others=>'0');
			zpu_do_reg <= (others=>'0');
			memory_ready_reg <= '0';
			zpu_addr_reg <= (others=>'0');
			ZPU_32BIT_WRITE_ENABLE_reg <= '0';
			ZPU_16BIT_WRITE_ENABLE_reg <= '0';
			ZPU_8BIT_WRITE_ENABLE_reg <= '0';	
			ZPU_READ_reg <= '0';
		elsif (clk'event and clk='1') then
			zpu_memory_fetch_pending_reg <= zpu_memory_fetch_pending_next;
			result_reg <= result_next;
			zpu_di_reg <= zpu_di_next;
			zpu_do_reg <= zpu_do_next;
			memory_ready_reg <= memory_ready_next;
			zpu_addr_reg <=zpu_addr_next;
			ZPU_32BIT_WRITE_ENABLE_reg <= ZPU_32BIT_WRITE_ENABLE_next;
			ZPU_16BIT_WRITE_ENABLE_reg <= ZPU_16BIT_WRITE_ENABLE_next;
			ZPU_8BIT_WRITE_ENABLE_reg <= ZPU_8BIT_WRITE_ENABLE_next;
			ZPU_READ_reg <= ZPU_READ_next;
		end if;
	end process;

-- a little glue
	process(zpu_ADDR_unsigned)
	begin
		block_mem <= '0';
		config_mem <= '0';
		special_mem <= '0';
		-- $00000-$0FFFF = Own ROM/RAM
		-- $10000-$1FFFF = Atari
		-- $20000-$2FFFF = Atari - savestate (gtia/antic/pokey have memory behind them)
		-- $40000-$4FFFF = Config area
		if (or_reduce(std_logic_vector(zpu_ADDR_unsigned(23 downto 21))) = '0') then -- special area
			block_mem <= not(zpu_addr_unsigned(18) or zpu_addr_unsigned(17) or zpu_addr_unsigned(16));
			config_mem <= zpu_addr_unsigned(18);
			special_mem <= zpu_addr_unsigned(17);
		end if;	
	end process;

	ZPU_READ_TEMP <= zpu_32bit_read_enable_temp or zpu_8BIT_read_enable_temp;
	ZPU_WRITE_TEMP<= zpu_32BIT_WRITE_ENABLE_temp or zpu_16BIT_WRITE_ENABLE_temp or zpu_8BIT_WRITE_ENABLE_temp;
	process(zpu_addr_reg,pause,memory_ready,zpu_memory_fetch_pending_next,request_type, zpu_memory_fetch_pending_reg, memory_ready_reg, zpu_ADDR_unsigned, zpu_8bit_read_enable_temp, zpu_write_temp, result_reg, block_mem, config_mem, special_mem, memory_access,
	        zpu_read_reg,zpu_8BIT_WRITE_ENABLE_reg, zpu_16BIT_WRITE_ENABLE_reg, zpu_32BIT_WRITE_ENABLE_reg,
			  zpu_read_temp,zpu_8BIT_WRITE_ENABLE_temp, zpu_16BIT_WRITE_ENABLE_temp, zpu_32BIT_WRITE_ENABLE_temp,
	zpu_do_unsigned, zpu_do_reg
			  )
	begin
		zpu_memory_fetch_pending_next <= zpu_memory_fetch_pending_reg;
		result_next <= result_reg;
		memory_ready_next <= memory_ready;
		zpu_stACK_WRITE <= (others=>'0');
		ZPU_ROM_WREN <= '0';
		ZPU_config_write <= '0';
		ZPU_config_read <= '0';
		zpu_addr_next <= zpu_addr_reg;
		zpu_do_next <= zpu_do_reg;
	
		ZPU_MEM_BUSY <= pause;
		
		MEMORY_ACCESS <= zpu_READ_temp or ZPU_WRITE_temp;

		if (memory_access = '1') then
			zpu_do_next <= std_logic_vector(zpu_do_unsigned);
		end if;
		
		memory_fetch <= zpu_memory_fetch_pending_reg;
		
		zpu_read_next <= zpu_read_reg;
		zpu_8bit_write_enable_next <= zpu_8bit_write_enable_reg;
		zpu_16bit_write_enable_next <= zpu_16bit_write_enable_reg;
		zpu_32bit_write_enable_next <= zpu_32bit_write_enable_reg;	
		
		request_type <= config_mem&block_mem&(zpu_addr_unsigned(14) and zpu_addr_unsigned(15))&memory_access&zpu_memory_fetch_pending_reg;
		case request_type is
			when "00010"|"00110" =>
				zpu_memory_fetch_pending_next <= '1';
				if (special_mem='0') then
					result_next <= result_external;
				else
					result_next <= result_external_special;
				end if;
				
				ZPU_MEM_BUSY <= '1';
				zpu_addr_next <= std_logic_vector(zpu_addr_unsigned);
				zpu_read_next <= zpu_read_temp;
				zpu_8bit_write_enable_next <= zpu_8bit_write_enable_temp;
				zpu_16bit_write_enable_next <= zpu_16bit_write_enable_temp;
				zpu_32bit_write_enable_next <= zpu_32bit_write_enable_temp;					
			when "01010" =>
				if (zpu_8bit_read_enable_temp='1') then
					case (zpu_addr_unsigned(1 downto 0)) is
					when "00" =>
						result_next <= result_rom_8bit_3;										
					when "01" =>
						result_next <= result_rom_8bit_2;
					when "10" =>
						result_next <= result_rom_8bit_1;										
					when "11" =>
						result_next <= result_rom_8bit_0;
					when others =>
						--nop
					end case;
				else
					result_next <= result_rom;
				end if;
				ZPU_ROM_WREN <= ZPU_WRITE_TEMP;
				ZPU_MEM_BUSY <= '1';
				zpu_addr_next <= std_logic_vector(zpu_addr_unsigned);
			when "01110" =>
				if (zpu_8bit_read_enable_temp='1' or zpu_8BIT_WRITE_ENABLE_temp='1') then
					case (zpu_addr_unsigned(1 downto 0)) is
					when "00" =>
						result_next <= result_ram_8bit_3;										
						ZPU_STACK_WRITE(3) <= zpu_8BIT_write_enable_temp;
					when "01" =>
						result_next <= result_ram_8bit_2;
						ZPU_STACK_WRITE(2) <= zpu_8BIT_write_enable_temp;
					when "10" =>
						result_next <= result_ram_8bit_1;					
						ZPU_STACK_WRITE(1) <= zpu_8BIT_write_enable_temp;					
					when "11" =>
						result_next <= result_ram_8bit_0;
						ZPU_STACK_WRITE(0) <= zpu_8BIT_write_enable_temp;
					when others =>
						--nop
					end case;
				else
					result_next <= result_ram;
					ZPU_STACK_WRITE <= (others=>zpu_write_temp);					
				end if;
				ZPU_MEM_BUSY <= '1';				
				zpu_addr_next <= std_logic_vector(zpu_addr_unsigned);
			when "10110"|"10010" =>
				result_next <= result_config;
				ZPU_MEM_BUSY <= '1';
				ZPU_config_write <= ZPU_WRITE_temp; 
				ZPU_config_read <= ZPU_READ_temp; 
				zpu_addr_next <= std_logic_vector(zpu_addr_unsigned);				
			when "00001"|"00011"|"00101"|"00111"|"01001"|"01011"|"01101"|"01111"|
				  "10001"|"10011"|"10101"|"10111"|"11001"|"11011"|"11101"|"11111"|"00X01" =>
				ZPU_MEM_BUSY <= not(memory_ready_reg) or pause;
				zpu_memory_fetch_pending_next <= not(memory_ready);
			when others =>
				-- nop
		end case;
	end process;

	zpu_di_next <= zpu_di;
	process(result_reg, zpu_di_reg, zpu_rom_di, zpu_ram_di, zpu_config_di)
	begin
		zpu_di_use <= (others=>'0');
		case result_reg is 
			when result_external =>
				zpu_di_use <= zpu_di_reg;
			when result_external_special =>
				zpu_di_use(7 downto 0) <= zpu_di_reg(15 downto 8);				
			when result_rom =>
				zpu_di_use <= zpu_rom_DI;
			when result_rom_8bit_0 =>
				zpu_di_use(7 downto 0) <= zpu_rom_DI(7 downto 0);
			when result_rom_8bit_1 =>
				zpu_di_use(7 downto 0) <= zpu_rom_DI(15 downto 8);
			when result_rom_8bit_2 =>
				zpu_di_use(7 downto 0) <= zpu_rom_DI(23 downto 16);
			when result_rom_8bit_3 =>
				zpu_di_use(7 downto 0) <= zpu_rom_DI(31 downto 24);				
			when result_ram =>
				zpu_di_use <= zpu_ram_DI;
			when result_ram_8bit_0 =>
				zpu_di_use(7 downto 0) <= zpu_ram_DI(7 downto 0);
			when result_ram_8bit_1 =>
				zpu_di_use(7 downto 0) <= zpu_ram_DI(15 downto 8);
			when result_ram_8bit_2 =>
				zpu_di_use(7 downto 0) <= zpu_ram_DI(23 downto 16);
			when result_ram_8bit_3 =>
				zpu_di_use(7 downto 0) <= zpu_ram_DI(31 downto 24);				
			when result_config =>
				zpu_di_use <= zpu_config_di;
			when others =>
				-- nothing
		end case;
	end process;

-- zpu itself
	--zpu_enable <= enable and not(pause);
	zpu_enable <= '1'; -- does nothing useful...
	
myzpu: ZPUMediumCore
	port map (clk_i=>clk, reset_i=>reset,enable_i=>zpu_enable,break_o=>zpu_break,dbg_o=>zpu_debug,mem_busy_i=>ZPU_MEM_BUSY,
	data_i=>zpu_di_unsigned,data_o=>zpu_do_unsigned,addr_o=>zpu_addr_unsigned,write_en_o=>zpu_32bit_write_enable_temp,read_en_o=>zpu_32bit_read_enable_temp,
	byte_read_o=>zpu_8bit_read_enable_temp, byte_write_o=>zpu_8bit_write_enable_temp,short_write_o=>zpu_16bit_write_enable_temp);
	
	zpu_di_unsigned <= unsigned(zpu_di_use);
	zpu_do <= zpu_do_next;
	ZPU_ADDR_ROM_RAM <= zpu_addr_next(15 downto 0);
	ZPU_ADDR_FETCH <= zpu_addr_reg;
		
	zpu_read_enable <= zpu_read_reg;
	zpu_8bit_write_enable <= zpu_8bit_write_enable_reg;
	zpu_16bit_write_enable <= zpu_16bit_write_enable_reg;
	zpu_32bit_write_enable <= zpu_32bit_write_enable_reg;

end sticky;
