-------------------------------------------------------------------------------
--
-- Simple VGA raster display
--
-- Stephen A. Edwards
-- sedwards@cs.columbia.edu
--
-------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.numeric_std.all;

entity de2_vga_raster is
  
  port (
    -- Avalon slave interface for mouse and movie addresses
    reset : in std_logic;
    clk   : in std_logic;                    -- Should be 25.125 MHz
    chipselect : in  std_logic;
    write      : in  std_logic;
    address : in unsigned(0 downto 0);       -- just two locations
    writedata  : in unsigned(31 downto 0);    

    -- Create our Avalon Master to access SRAM and SDRAM
    avm_read_master_read : out std_logic;
    avm_read_master_address : out std_logic_vector (31 downto 0);  -- use the same 32 bit addressing as CPU
    avm_read_master_readdata : in std_logic_vector (31 downto 0);  -- the avalon bus always reads 32 bits, so we read 2 pixels worth at a time
    avm_read_master_waitrequest : in std_logic;
    
    -- Outputs for Actual VGA
    VGA_CLK,                         -- Clock
    VGA_HS,                          -- H_SYNC
    VGA_VS,                          -- V_SYNC
    VGA_BLANK,                       -- BLANK
    VGA_SYNC : out std_logic;        -- SYNC
    VGA_R,                           -- Red[9:0]
    VGA_G,                           -- Green[9:0]
    VGA_B : out unsigned(9 downto 0) -- Blue[9:0]
    );

end de2_vga_raster;

architecture rtl of de2_vga_raster is
  -- declare LineBuffer component
  -- the buffer (dual port RAM) component was generated using the Quartus II MegaWizard Plug-In Manager
  -- Output is registered 
  component LineBuffer
  port (
             clock		: IN STD_LOGIC ;
             data		: IN STD_LOGIC_VECTOR (15 DOWNTO 0);
             rdaddress  : IN STD_LOGIC_VECTOR (9 DOWNTO 0);
             wraddress	: IN STD_LOGIC_VECTOR (9 DOWNTO 0);
             wren		: IN STD_LOGIC;
             q		    : OUT STD_LOGIC_VECTOR (15 DOWNTO 0)
  );
  end component;
  
 
  -- Video parameters  
  constant HTOTAL       : integer := 800;
  constant HSYNC        : integer := 96;
  constant HBACK_PORCH  : integer := 48;
  constant HACTIVE      : integer := 640;
  constant HFRONT_PORCH : integer := 16;

  constant HACTIVE_BYTE      : integer := 1280;
  
  constant VTOTAL       : integer := 525;
  constant VSYNC        : integer := 2;
  constant VBACK_PORCH  : integer := 33;
  constant VACTIVE      : integer := 480;
  constant VFRONT_PORCH : integer := 10;

  -- Used to define the active screen area
  constant HSTART : integer := HSYNC + HBACK_PORCH;
  constant HEND   : integer := HSTART + HACTIVE;
  constant VSTART : integer := VSYNC + VBACK_PORCH;
  constant VEND   : integer := VSTART + VACTIVE;

  constant RADIUS : integer := 10 * 10;
  constant START_X : integer := HSYNC + HBACK_PORCH + (HACTIVE / 2);
  constant START_Y : integer := VSYNC + VBACK_PORCH - 1 + (VACTIVE / 2);
  constant SDRAM_BASE : unsigned (31 downto 0) := x"0000_0000";  -- SOPC dram address
  constant BMP_OFFSET : unsigned (31 downto 0) := x"0000_0036"; -- Standard 16bit bmp data offset
 
  constant Frame1_BASE : unsigned (31 downto 0) := x"0009_6000";  -- SOPC dram address
  constant Frame2_BASE : unsigned (31 downto 0) := x"0012_C000";  -- SOPC dram address

  -- Signals for the video controller
  signal clk25 : std_logic := '0';
  signal Hcount : unsigned(9 downto 0);  -- Horizontal position (0-800)
  signal Vcount : unsigned(9 downto 0);  -- Vertical position (0-524)
  signal EndOfLine, EndOfField : std_logic;

  signal vga_hblank, vga_hsync,
         vga_vblank, vga_vsync : std_logic;  -- Sync. signals

  signal rectangle_h, rectangle_v, rectangle : std_logic;  -- rectangle area

  -- Signals to communicate from outside
  signal center_x : unsigned(9 downto 0); 
  signal center_y : unsigned(9 downto 0); 
  signal pixel_base : unsigned(31 downto 0);     -- SOPC dram address
  signal pixel_offset : unsigned (31 downto 0);  -- Standard 16bit bmp data offset 
  signal frame_count : unsigned(5 downto 0);     -- Use this to count to 20 frames -> 3 frames per second 
 
  -- state machine for Avalon master
  type read_states_T is (idle, running, stopping);
  signal read_state : read_states_T;
  
  -- extra signal for read master
  signal temp_read_address : std_logic_vector (31 downto 0);
  signal words_read : std_logic_vector (16 downto 0);            -- tracks the words read

    -- instiantiaton for lineBuffer
  signal buffer_write : std_logic;
  signal buffer_waddress, buffer_raddress : std_logic_vector(9 DOWNTO 0);

  -- signal to save color information
  signal bmp_pixel, bmp_pixel_even, bmp_pixel_odd : std_logic_vector(15 downto 0);  

-------------------------------------------------------------------------------
-- Buffer instantiation
-------------------------------------------------------------------------------
begin
even_line_buffer: LineBuffer  -- 1024 WORD Buffer
port map (
      clock     => clk,
      data      => avm_read_master_readdata(15 downto 0),
      rdaddress => buffer_raddress,
      wraddress => buffer_waddress,
      wren      => buffer_write,
      q         => bmp_pixel_even
);

odd_line_buffer: LineBuffer  -- 1024 WORD Buffer
port map (
      clock     => clk,
      data      => avm_read_master_readdata(31 downto 16),
      rdaddress => buffer_raddress,
      wraddress => buffer_waddress,
      wren      => buffer_write,
      q         => bmp_pixel_odd
);

bmp_pixel <= bmp_pixel_even when Hcount(0) = '0' else bmp_pixel_odd;


-------------------------------------------------------------------------------
-- Avalon master state machine
-------------------------------------------------------------------------------
  AvalonInit : process (clk)
  begin 
    if reset = '1' then
       read_state <= idle;
       temp_read_address <= (others => '0');
       words_read <= (others => '0');
    else if rising_edge(clk) then 
      case read_state is
        --IDLE, sit and wait until we can start loading data
        when idle =>
          -- Load the pixels at the start of each line. 
          if (Hcount = 0) and (Vcount >= VSTART) and ((Vcount < VEND + 1)) then
          --if (Hcount = 3) and (Vcount >= VSTART) and ((Vcount < VEND + 1)) then  
          read_state <= running;
            -- keep in mind bmp raster order is reversed from bottom to top
            --temp_read_address <= STD_LOGIC_VECTOR(pixel_base + pixel_offset + (300 * HACTIVE));
            --temp_read_address <= STD_LOGIC_VECTOR(pixel_base + pixel_offset + (VACTIVE - (Vcount - VSTART)) * HACTIVE);  
            temp_read_address <= STD_LOGIC_VECTOR(pixel_base + pixel_offset + ((479 - (Vcount - VSTART)) * HACTIVE));
            words_read <= (others => '0');

            -- if (Hcount > HSTART) and (Hcount < HEND + 1) and (Vcount > VSTART) and (Vcount < VEND + 1) then  
            -- translate bmp reversed row order, row 1 translated to row 480, row 480 is translated to row 1
            --temp_read_address <= STD_LOGIC_VECTOR(SDRAM_BASE + (Hcount - 1) + ((VACTIVE - 1 - (Vcount - VSTART)) * HACTIVE));  
            
            --temp_read_address <= STD_LOGIC_VECTOR(pixel_base + pixel_offset + (Hcount - HSTART) + ((VACTIVE - (Vcount - VSTART)) * HACTIVE));  -- translate raster to linear address
            --temp_read_address <= "00000000100000000000000000000000";  -- 0x800000 starting SDRAM address
          end if;

        --RUNNING
        --Hold all signals constant until waitrequest is not active   
        when running =>  
          if avm_read_master_waitrequest /= '1' then
              temp_read_address <= temp_read_address + 2;  -- Update address DMA style
              words_read <= words_read + 1;
              if words_read = 320 then  -- 640 in total (0 indexing), a whole lines worth
                 read_state <= stopping;
--              else
--                 read_state <= running;
              end if;

            --bmp_pixel2 <= avm_read_master_readdata;
            --read_state <= stopping;
            --temp_blue <= avm_read_master_readdata(9 downto 0);
            
          end if;

        -- STOPPING
        -- Required to implement a cycle delay before going idle 
        -- This ensures that the fifo empty flag is updated before going idle
        -- so that the write state machine does not register a false completion
        when stopping =>
--          if (Hcount > HEND) then
--             read_state <= idle;
--          end if;
            read_state <= idle;
      end case;
    end if;
  end if;
  end process;

  -- combinational signal for avalon master
  avm_read_master_read <= '1' when read_state = running else '0';    
  avm_read_master_address <= temp_read_address;

  -- simply write data into the buffer as it comes in (read asserted and waitrequest not active)
  buffer_waddress(9 downto 0) <= words_read(9 downto 0);
  buffer_write <= '1' when read_state = running and avm_read_master_waitrequest = '0' else '0';

  -- read the FIFO for display as needed per pixel
  buffer_raddress <= STD_LOGIC_VECTOR((Hcount - HSTART) srl 1);
  --fifo_read <= '1' when vga_hblank = '0' and vga_vblank ='0' and fifo_empty = '0' else '0';


-------------------------------------------------------------------------------
-- Software interfaces
-------------------------------------------------------------------------------  
  SetCenter : process (clk)
  begin 
    if rising_edge(clk) then      
      if reset = '1' then
         center_x <= TO_UNSIGNED(START_X, 10);
         center_y <= TO_UNSIGNED(START_Y, 10);
      else 
         if chipselect = '1' then
            if write = '1' then           
                  center_x <= writedata(9 downto 0);
                  center_y <= writedata(25 downto 16);
               end if;
            end if;
         end if;
      end if;
  end process SetCenter;        

-------------------------------------------------------------------------------
-- Animation generation
-------------------------------------------------------------------------------  
  MoveFrame : process (clk)
  begin 
    if rising_edge(clk) then      
      if reset = '1' then
        pixel_base <= SDRAM_BASE;
        pixel_offset <= BMP_OFFSET;
        frame_count <= (others => '0');
      else
        if (Hcount = 0) and (Vcount = 0) then
   frame_count <= frame_count + 1;
end if;
        if (frame_count = 0) then
           pixel_base <= SDRAM_BASE;
        end if;
if (frame_count = 20) then
           pixel_base <= Frame1_BASE;
        end if;
        if (frame_count = 40) then
           pixel_base <= Frame2_BASE;
        end if;
        if (frame_count > 60) then
            frame_count <= (others => '0'); 
        end if;
       end if;
    end if;
  end process MoveFrame;     


-------------------------------------------------------------------------------
-- VGA related
-------------------------------------------------------------------------------
  -- Turn down the clock
  process (clk)  -- Let's try running the system at 25 MHz and see if it works
  begin
    if rising_edge(clk) then
      clk25 <= not clk25;
    end if;
  end process;     

  -- Horizontal and vertical counters

  HCounter : process (clk25)
  begin
    if rising_edge(clk25) then      
      if reset = '1' then
        Hcount <= (others => '0');
      elsif EndOfLine = '1' then
        Hcount <= (others => '0');
      else
        Hcount <= Hcount + 1;
      end if;      
    end if;
  end process HCounter;

  EndOfLine <= '1' when Hcount = HTOTAL - 1 else '0';
  
  VCounter: process (clk25)
  begin
    if rising_edge(clk25) then      
      if reset = '1' then
        Vcount <= (others => '0');
      elsif EndOfLine = '1' then
        if EndOfField = '1' then
          Vcount <= (others => '0');
        else
          Vcount <= Vcount + 1;
        end if;
      end if;
    end if;
  end process VCounter;

  EndOfField <= '1' when Vcount = VTOTAL - 1 else '0';

  -- State machines to generate HSYNC, VSYNC, HBLANK, and VBLANK

  HSyncGen : process (clk25)
  begin
    if rising_edge(clk25) then     
      if reset = '1' or EndOfLine = '1' then
        vga_hsync <= '1';
      elsif Hcount = HSYNC - 1 then
        vga_hsync <= '0';
      end if;
    end if;
  end process HSyncGen;
  
  HBlankGen : process (clk25)
  begin
    if rising_edge(clk25) then
      if reset = '1' then
        vga_hblank <= '1';
      elsif Hcount = HSYNC + HBACK_PORCH then
        vga_hblank <= '0';
      elsif Hcount = HSYNC + HBACK_PORCH + HACTIVE then
        vga_hblank <= '1';
      end if;      
    end if;
  end process HBlankGen;

  VSyncGen : process (clk25)
  begin
    if rising_edge(clk25) then
      if reset = '1' then
        vga_vsync <= '1';
      elsif EndOfLine ='1' then
        if EndOfField = '1' then
          vga_vsync <= '1';
        elsif Vcount = VSYNC - 1 then
          vga_vsync <= '0';
        end if;
      end if;      
    end if;
  end process VSyncGen;

  VBlankGen : process (clk25)
  begin
    if rising_edge(clk25) then    
      if reset = '1' then
        vga_vblank <= '1';
      elsif EndOfLine = '1' then
        if Vcount = VSYNC + VBACK_PORCH - 1 then
          vga_vblank <= '0';
        elsif Vcount = VSYNC + VBACK_PORCH + VACTIVE - 1 then
          vga_vblank <= '1';
        end if;
      end if;
    end if;
  end process VBlankGen;

  -- circle generator, do it in four quadrants
   CircleGen : process (clk25)
   variable x_distance, y_distance : unsigned(9 downto 0);  -- use for computation
   begin
     if rising_edge(clk25) then 
        if reset = '1' then
           rectangle <= '0';
        else          
       if Hcount < center_x then  -- left quadrants
              x_distance := center_x - Hcount;
           else                     -- right quadrants
              x_distance := Hcount - center_x; 
           end if;

           if Vcount < center_y then -- top quadrants
              y_distance := center_y - Vcount;
           else                    -- bottom quadrants
              y_distance := Vcount - center_y;
           end if;
           
           if RADIUS > (x_distance * x_distance) + (y_distance * y_distance) then
              rectangle <= '1';
           else 
              rectangle <= '0';
           end if;
         end if;
       end if;
   end process CircleGen;

--  -- Read the FIFO for display
--  ReadFIFO: process (clk25)
--  begin
--    if clk25'event and clk25 = '1' then
--       if vga_hblank = '0' and vga_vblank ='0' and fifo_empty = '0' then
--             fifo_read <= '1';
--       end if;
--    else 
--       fifo_read <= '0';
--    end if;
--  end process ReadFIFO;

  -- Registered video signals going to the video DAC
  VideoOut: process (clk25, reset)
  variable blue_bits, green_bits, red_bits : unsigned(9 downto 0);  -- use to read pixels from 16 bit bmp
  begin
      -- For 16bit bmp, bits are red->green->blue 5 bits each, using lower 15 bits
    if reset = '1' then
      VGA_R <= "0000000000";
      VGA_G <= "0000000000";
      VGA_B <= "0000000000";
    elsif clk25'event and clk25 = '1' then
      if rectangle = '1' then
        VGA_R <= "1111111111";
        VGA_G <= "0000000000";
        VGA_B <= "0000000000";
      elsif vga_hblank = '0' and vga_vblank ='0' then
        -- Convert from 5 bit color to 10 bit for DAC by shifting left (multiply by 32)
        VGA_R(9 downto 5) <= UNSIGNED(bmp_pixel(14 downto 10)); 
        VGA_R(4 downto 0) <= "00000";
        VGA_G(9 downto 5) <= UNSIGNED(bmp_pixel(9 downto 5));
        VGA_G(4 downto 0) <= "00000";
        VGA_B(9 downto 5) <= UNSIGNED(bmp_pixel(4 downto 0));
        VGA_B(4 downto 0) <= "00000";
      else
        VGA_R <= "0000000000";
        VGA_G <= "0000000000";
        VGA_B <= "0000000000";    
      end if;
    end if;
  end process VideoOut;

  VGA_CLK <= clk25;
  VGA_HS <= not vga_hsync;
  VGA_VS <= not vga_vsync;
  VGA_SYNC <= '0';
  VGA_BLANK <= not (vga_hsync or vga_vsync);

end rtl;