----------------------------------------------------------------------------------
-- Company: 
-- Engineer: 
-- 
-- Create Date:    10:36:54 04/05/2009 
-- Design Name: 
-- Module Name:    dft_core - Behavioral 
-- Project Name: 
-- Target Devices: 
-- Tool versions: 
-- Description: 
--
-- Dependencies: 
--
-- Revision: 
-- Revision 0.01 - File Created
-- Additional Comments: 
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
use IEEE.NUMERIC_STD.ALL;

---- Uncomment the following library declaration if instantiating
---- any Xilinx primitives in this code.
--library UNISIM;
--use UNISIM.VComponents.all;

entity DFT_CORE is
   generic
      (
      DFT_SLV_DWIDTH: integer := 32;
      DFT_MST_AWIDTH: integer := 32;
      DFT_MST_DWIDTH: integer := 32
      );
   port
      ( 
      Clk: in std_logic;
      Reset: in std_logic;
      dft_busy : in std_logic;
      dft_go : out std_logic;
      dft_intern_req : out std_logic;
      dft_time_addr : in std_logic_vector(0 to DFT_SLV_DWIDTH-1);
      dft_real_addr : in std_logic_vector(0 to DFT_SLV_DWIDTH-1);
      dft_imag_addr : in std_logic_vector(0 to DFT_SLV_DWIDTH-1);
      dft_read_cmd : out std_logic;
      dft_write_cmd : out std_logic;
      dft_out_addr : out std_logic_vector(0 to DFT_MST_AWIDTH-1);
      dft_read_data : in std_logic_vector(0 to DFT_MST_DWIDTH-1);
      dft_write_data : out std_logic_vector(0 to DFT_MST_DWIDTH-1);
      dft_read_data_xfer : in std_logic;
      dft_write_data_xfer : in std_logic;
      dft_interrupt_done : out std_logic
      );
end DFT_CORE;

architecture Behavioral of DFT_CORE is

   component sin_cos_core
      port (
         THETA: IN std_logic_VECTOR(8 downto 0);
         SINE: OUT std_logic_VECTOR(13 downto 0);
         COSINE: OUT std_logic_VECTOR(13 downto 0));
   end component;

   type dft_states is (start_state, out_loop_state, in_loop_state, sin_cos_state, accum_state, 
      inc_i_state, write_state, done_state, final_state);
   signal current_state, next_state : dft_states;
   signal out_data_cur_reg, out_data_next_reg : std_logic_vector(0 to DFT_MST_DWIDTH-1);
   signal real_data_cur_reg, real_data_next_reg : std_logic_vector(0 to DFT_MST_DWIDTH-1);
   signal imag_data_cur_reg, imag_data_next_reg : std_logic_vector(0 to DFT_MST_DWIDTH-1);
   signal theta_cur_reg, theta_next_reg : std_logic_vector(8 downto 0); 
   signal sine_data, cosine_data : std_logic_vector(13 downto 0);
   signal cos_cur_reg, cos_next_reg : std_logic_vector(13 downto 0);
   signal sin_cur_reg, sin_next_reg : std_logic_vector(13 downto 0);
   signal i_cur_reg, i_next_reg, j_cur_reg, j_next_reg : std_logic_vector(15 downto 0);
   signal write_xfer_cur_reg, write_xfer_next_reg : std_logic;
   signal ri_cnter_cur_reg, ri_cnter_next_reg : std_logic_vector(0 to 1);
   signal read_data_cur_reg, read_data_next_reg : std_logic_vector(0 to DFT_MST_DWIDTH-1);
   signal num_pts_cur_reg, num_pts_next_reg : std_logic_vector(15 downto 0);
   signal read_num_pts_cur_reg, read_num_pts_next_reg : std_logic;
   signal half_num_pts : std_logic_vector(15 downto 0);
   signal interrupt_cur_reg, interrupt_next_reg : std_logic;

   signal read_data_signed : signed(15 downto 0);
   signal cos_signed : signed(13 downto 0);
   signal sin_signed : signed(13 downto 0);
   signal real_data_signed : signed(0 to DFT_MST_DWIDTH-1);
   signal imag_data_signed : signed(0 to DFT_MST_DWIDTH-1);

begin

   sin_cos_core_inst : sin_cos_core
      port map (
         THETA => theta_cur_reg,
         SINE => sine_data,
         COSINE => cosine_data);

-- ============================================================================================
-- DFT STATE MACHINE: Sequential Logic
-- ============================================================================================
   DFT_SEQ : process( Clk, Reset ) is
      begin
      if ( Clk'event and Clk = '1' ) then
         if ( Reset = '1' ) then
            current_state <= start_state;
            out_data_cur_reg <= (others => '0');
            real_data_cur_reg <= (others => '0');
            imag_data_cur_reg <= (others => '0');
            i_cur_reg <= (others => '0');
            j_cur_reg <= (others => '0');
            write_xfer_cur_reg <= '0';
            ri_cnter_cur_reg <= "00";
            theta_cur_reg <= (others => '0');
            cos_cur_reg <= (others => '0');
            sin_cur_reg <= (others => '0');
            read_data_cur_reg <= (others => '0');
            read_num_pts_cur_reg <= '0';
            num_pts_cur_reg <= (others => '0');
            interrupt_cur_reg <= '0';
         else
            current_state <= next_state;
            out_data_cur_reg <= out_data_next_reg;
            real_data_cur_reg <= real_data_next_reg;
            imag_data_cur_reg <= imag_data_next_reg;
            i_cur_reg <= i_next_reg;
            j_cur_reg <= j_next_reg;
            write_xfer_cur_reg <= write_xfer_next_reg;
            ri_cnter_cur_reg <= ri_cnter_next_reg;
            theta_cur_reg <= theta_next_reg;
            cos_cur_reg <= cos_next_reg;
            sin_cur_reg <= sin_next_reg;
            read_data_cur_reg <= read_data_next_reg;
            read_num_pts_cur_reg <= read_num_pts_next_reg;
            num_pts_cur_reg <= num_pts_next_reg;
            interrupt_cur_reg <= interrupt_next_reg;
         end if;
      end if;
   end process;

-- Connect the output data bus to the register holding the value to be written to
-- memory.
   dft_write_data <= out_data_cur_reg;

-- Divide the number of points read from the slave register by 2.
   half_num_pts <= '0' & num_pts_cur_reg(15 downto 1);

   dft_interrupt_done <= interrupt_cur_reg;

-- ============================================================================================
-- DFT STATE MACHINE: Combinational Logic
-- ============================================================================================
   DFT_PROCESS : process( current_state, dft_busy, dft_time_addr, dft_real_addr, dft_imag_addr, 
      out_data_cur_reg, real_data_cur_reg, imag_data_cur_reg, 
      dft_read_data_xfer, dft_write_data_xfer, write_xfer_cur_reg,
      i_cur_reg, j_cur_reg, ri_cnter_cur_reg, dft_read_data, read_data_cur_reg, 
      read_num_pts_cur_reg, num_pts_cur_reg, interrupt_cur_reg, 
      read_data_signed, cos_signed, sin_signed, real_data_signed, imag_data_signed ) is
      begin

-- Default assignments.
      dft_go <= '0';
      dft_intern_req <= '0';
      dft_read_cmd <= '0';
      dft_write_cmd <= '0';
      dft_out_addr <= (others => '0');
      out_data_next_reg <= out_data_cur_reg;
      real_data_next_reg <= real_data_cur_reg;
      imag_data_next_reg <= imag_data_cur_reg;
      i_next_reg <= i_cur_reg;
      j_next_reg <= j_cur_reg;
      write_xfer_next_reg <= write_xfer_cur_reg;
      ri_cnter_next_reg <= ri_cnter_cur_reg;
      theta_next_reg <= theta_cur_reg;
      cos_next_reg <= cos_cur_reg;
      sin_next_reg <= sin_cur_reg;
      read_data_next_reg <= read_data_cur_reg;
      read_num_pts_next_reg <= read_num_pts_cur_reg;
      num_pts_next_reg <= num_pts_cur_reg;
      interrupt_next_reg <= interrupt_cur_reg;
      read_data_signed <= (others => '0');
      cos_signed <= (others => '0');
      sin_signed <= (others => '0');
      real_data_signed <= (others => '0');
      imag_data_signed <= (others => '0');

      case current_state is

-- Wait for data address to become non-zero -- once both are set, go.
         when start_state => 

-- I write the number of points to the dft_time_addr software register first
-- and then re-write it with the base address of the time domain data. 
            if ( dft_time_addr /= X"00000000" and read_num_pts_cur_reg = '0' ) then
               read_num_pts_next_reg <= '1';
               num_pts_next_reg <= dft_time_addr(16 to 31);
            end if; 

            if ( dft_time_addr = X"00000000" or dft_real_addr = X"00000000" or 
               dft_imag_addr = X"00000000" ) then
               next_state <= start_state;

            else
               next_state <= out_loop_state;
            end if;

-- OUTTER LOOP: Move through j values. 
         when out_loop_state => 

-- If after incrementing j, leaves us pointing the the last position + 1, we're done.
	    if ( j_cur_reg = half_num_pts ) then
               next_state <= done_state;

-- Zero out the accumulator registers. 
            else
               real_data_next_reg <= (others => '0');
               imag_data_next_reg <= (others => '0');
               next_state <= in_loop_state;
            end if; 

-- INNER LOOP: Read a y_value and set the argument for theta in theta reg.
         when in_loop_state => 
            theta_next_reg <= i_cur_reg*j_cur_reg;
	    if ( dft_busy = '1' ) then
               next_state <= in_loop_state;
            else
               dft_go <= '1';
               dft_read_cmd <= '1';

-- dft_out_addr is latched into the master register on next rising edge, so we don't need
-- to hold it there.
               dft_out_addr <= dft_time_addr + (i_cur_reg(13 downto 0) & "00");
               next_state <= sin_cos_state;
            end if;

-- Wait for the y data read to complete. Latch the sine/cosine outputs -- note theta is registered above.
         when sin_cos_state => 
            if ( dft_read_data_xfer = '0' ) then
               next_state <= sin_cos_state;
            else

-- Remember, the dft_read_data is ONLY valid as long as dft_read_data_xfer is '1' -- MUST be saved
-- here.
               read_data_next_reg <= dft_read_data;
               cos_next_reg <= cosine_data;
               sin_next_reg <= sine_data;
               next_state <= accum_state;
            end if;

-- Accumulate real and imag components.
         when accum_state => 
            read_data_signed <= signed(read_data_cur_reg(16 to 31));
            cos_signed <= signed(cos_cur_reg);
            sin_signed <= signed(sin_cur_reg);
            real_data_signed <= signed(real_data_cur_reg); 
            imag_data_signed <= signed(imag_data_cur_reg); 

            real_data_next_reg <= real_data_signed + read_data_signed * cos_signed;
            imag_data_next_reg <= imag_data_signed + read_data_signed * sin_signed;
            next_state <= inc_i_state;

-- Increment i and loop or break.
         when inc_i_state => 

-- If last i, clear i and move to write state.
	    if ( i_cur_reg + 1 = num_pts_cur_reg ) then
               i_next_reg <= (others => '0');
               next_state <= write_state;
            else
               i_next_reg <= i_cur_reg + 1;
               next_state <= in_loop_state;
            end if;

-- Write real/imag values to memory. Always wait for the dft_busy to become '0' 
-- If dft_busy AND dft_write_data_xfer are BOTH 1, then remember the latter was 1 so we know
-- it's okay to move on to the next write. Not sure if this is necessary because I think the 
-- sequence will always be that dft_busy is 1 until the write request is issued. The 
-- dft_write_data_xfer will not become 1 until sometime later, after busy is set back to 0 
-- because of the time it takes the memory operation to complete. 
         when write_state => 
            dft_intern_req <= '1';

-- wait for busy to be clear -- if the write is being acknowledged at the same time,
-- remember this so we can proceed once busy is 0.
            if ( dft_busy = '1' ) then
               if ( dft_write_data_xfer = '1' ) then
                  write_xfer_next_reg <= '1';
               end if;
               next_state <= write_state;

-- If the write is acknowledged, or was acknowledged in a previous cycle when busy was set,
-- or if this is the first write, start write command.
            elsif ( dft_write_data_xfer = '1' or write_xfer_cur_reg = '1' or
               ri_cnter_cur_reg = "00" ) then
               write_xfer_next_reg <= '0';
               dft_go <= '1';
               dft_write_cmd <= '1';

-- We need to do this twice, once for the real value and once for the imag value.
               if ( ri_cnter_cur_reg = "00" ) then
                  ri_cnter_next_reg <= "01";

-- dft_out_addr is latched into the master register on next rising edge, so we don't need
-- to hold it there. 
                  dft_out_addr <= dft_real_addr + (j_cur_reg(13 downto 0) & "00");

-- Latch data to be written on rising edge.
                  out_data_next_reg <= real_data_cur_reg;
                  next_state <= write_state;

-- imag write.
               elsif ( ri_cnter_cur_reg = "01" ) then
                  ri_cnter_next_reg <= "10";

                  dft_out_addr <= dft_imag_addr + (j_cur_reg(13 downto 0) & "00");
                  out_data_next_reg <= imag_data_cur_reg;
                  next_state <= write_state;

-- Both writes completed, inc j and start again.
               else
                  ri_cnter_next_reg <= "00";
                  j_next_reg <= j_cur_reg + 1;
                  next_state <= out_loop_state;
               end if; 
            else
               next_state <= write_state;
            end if; 

-- Completed.
         when done_state => 
	    interrupt_next_reg <= '1';
            next_state <= final_state;
         when final_state => 
	    interrupt_next_reg <= '0';
            next_state <= final_state;
         when others => 
            next_state <= start_state;
      end case;
   end process;

end Behavioral;

