----------------------------------------------
-- MSS copyright 2017
-- Filename: SIGNED_SIN_COS_TBL3.VHD
-- Author: Alain Zarembowitch / MSS
-- Inheritance: n/a
-- Edit date: 4/25/17
-- Revision: 1
--
-- Description: ROM table for sine and cosine. 
-- 
-- Implementation: uses RAMB as look-up table spanning 0-90 deg for sine. 
-- Use quadrant symmetry for full 0-360 deg sine/cosine lookup. 
-- Use A/B sides for simultaneous sine and cosine.
-- Able to operate at the maximum speed of one sin/cos pair every system clock.
--	Latency between input and output is 2 CLK.
--
-- Rev 1 4/25/17 AZ
-- Bug: sensitivity list + initialized for simulation.
----------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use IEEE.MATH_REAL.ALL;

entity SIGNED_SIN_COS_TBL3 is
	generic (
		NADDR: integer := 12	-- 4K phase points PER QUADRANT. 16K points around circle = 4 18Kb block RAM
	);
	port (
		CLK: in std_logic;
			-- system clock
		PHASE: in std_logic_vector(NADDR+1 downto 0);
			-- angle: [0 to 360 degree[. No 360 representation.
			-- 0x00 -> 0.0 deg, 0xff -> 359.912 deg.
			-- Steps: 0.088 deg.
		SIN_OUT: out std_logic_vector(17 downto 0);
		COS_OUT: out std_logic_vector(17 downto 0)
			-- sine or cosine value. range -1 to +1. 
			-- 18-bit signed (2's complement)
			-- Latency is 2 CLK  
	);
end entity;

architecture BEHAVIOR of SIGNED_SIN_COS_TBL3 is

-----------------------------------------------------------------
-- COMPONENTS
-----------------------------------------------------------------
-----------------------------------------------------------------
-- FUNCTION
-----------------------------------------------------------------
type mem_array is array(0 to (2**NADDR)-1) of signed(17 downto 0);   -- 4k x 18 (when NADDR = 12)
 
  -- function computes contents of sine lookup ROM
  function init_rom return mem_array is
    constant N : integer := 2**NADDR;
    constant N1 : real := real(N);
    variable w, k1 : real;
    variable memx : mem_array;
  begin
    for k in 0 to N-1 loop
      k1 := (real(k)+0.5)/N1;          -- offset of 1/2 necessary to use symmetry
      w := sin(math_pi_over_2 * k1);   -- first quadrant of sine wave
      memx(k) := to_signed(integer(round(131071.0*w)),18);  -- scale to 18-bit signed integer
    end loop;
    return memx;
  end function init_rom;

  constant rom : mem_array := init_rom;

-----------------------------------------------------------------
-- SIGNALS
-----------------------------------------------------------------
signal ADDRA: std_logic_vector(NADDR-1 downto 0) := (others => '0');
signal ADDRB: std_logic_vector(NADDR-1 downto 0) := (others => '0');
signal SIN0: signed(17 downto 0) := (others => '0');
signal COS0: signed(17 downto 0) := (others => '0');
signal PHASE_MSBs: std_logic_vector(1 downto 0) := (others => '0');
signal PHASE_MSBs_D: std_logic_vector(1 downto 0) := (others => '0');

-----------------------------------------------------------------
-- IMPLEMENTATION
-----------------------------------------------------------------
begin   
-- The SINE table spans 0 - 90 deg. Use symmetry to reconstruct full 0 - 360 SINE/COSINE table.
PHASE_MSBs <= PHASE(NADDR+1 downto NADDR);
SYMMETRY_IN: process(PHASE_MSBs, PHASE)
begin
	case(PHASE_MSBs) is
		when "00" => 								-- [0 - 90[ deg
			ADDRA <= PHASE(NADDR-1 downto 0); 		-- rising magnitude
			ADDRB <= not (PHASE(NADDR-1 downto 0));	-- falling magnitude
		when "01" => 								-- [90 - 180[ deg
			ADDRA <= not (PHASE(NADDR-1 downto 0));	-- falling magnitude
			ADDRB <= PHASE(NADDR-1 downto 0);		-- rising magnitude
		when "10" => 							  	-- [180 -> 270[ deg
			ADDRA <= PHASE(NADDR-1 downto 0);		-- rising magnitude
			ADDRB <= not (PHASE(NADDR-1 downto 0));	-- falling magnitude
		when others => 						   -- [270 -> 0[ deg
			ADDRA <= not (PHASE(NADDR-1 downto 0));	-- falling magnitude
			ADDRB <= PHASE(NADDR-1 downto 0);		-- rising magnitude
	end case;	
end process;

-- delay the input PHASE two most significant bits to align with the block ram output.
RECLOCK_001: process(CLK)
begin
	if rising_edge(CLK) then
		PHASE_MSBs_D <= PHASE_MSBs;
	end if;
end process;

-- read table
process(CLK)
begin
	if rising_edge(CLK) then
		SIN0 <= rom(to_integer(unsigned(ADDRA)));
		COS0 <= rom(to_integer(unsigned(ADDRB)));
	end if;
end process;


SYMMETRY_OUT: process(CLK)
begin
	if rising_edge(CLK) then
		if(PHASE_MSBs_D(1) = '0') then
			-- positive SINE
			SIN_OUT <= std_logic_vector(SIN0);
		else
			-- negative SINE
			SIN_OUT <= std_logic_vector(-SIN0);
		end if;

		if(PHASE_MSBs_D(1) = PHASE_MSBs_D(0)) then
			-- positive COSINE
			COS_OUT <= std_logic_vector(COS0);
		else
			-- negative SINE
			COS_OUT <= std_logic_vector(-COS0);
		end if;

	end if;

end process;

				
end BEHAVIOR;

		

