-- -----------------------------------------------------------------------
--
-- VGA-64
--
-- Multi purpose FPGA expansion for the Commodore 64 computer
--
-- -----------------------------------------------------------------------
-- Copyright 2005-2018 by Peter Wendrich (pwsoft@syntiac.com)
-- http://www.syntiac.com/chameleon.html
--
-- This source file is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Lesser General Public License as published
-- by the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This source file 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/>.
--
-- -----------------------------------------------------------------------
--
-- C64 Phi2-clock regeneration and divider
--
-- -----------------------------------------------------------------------

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.numeric_std.all;

-- -----------------------------------------------------------------------

architecture rtl of chameleon_phi_clock is
constant guardBits : integer := 4; -- Extra bits to reduce rounding errors in calculations
signal phi2_n_reg : unsigned(11 downto 0) := (others => '0');
signal phiSync : std_logic := '0';

signal locCnt : unsigned(7 downto 0) := (others => '0');
signal fracCnt : unsigned(guardBits-1 downto 0) := (others => '0');
signal c64Cnt : unsigned(7 downto 0) := (others => '0');
signal slvCnt : unsigned(7 downto 0) := (others => '0');

signal avgDelta : signed(8 downto 0) := (others => '0');
signal avgLen : unsigned((7+guardBits) downto 0) := (others => '0');

signal localPreHalf : std_logic := '0';
signal localHalf : std_logic := '0';
signal localPreEnd : std_logic := '0';
signal localEnd : std_logic := '0';

signal localPhi : std_logic := '0';

signal localPost1 : std_logic := '0';
signal localPost2 : std_logic := '0';
signal localPost3 : std_logic := '0';
signal localPost4 : std_logic := '0';
begin
	-- Average phi length
	phiLength <= avgLen((7+guardBits) downto guardBits);
	
	-- Local generated phi
	phiLocal <= localPhi;
	
	-- Cycle counter (add 1 to max-counter for each Mhz system clock)
	-- For 100Mhz the cycle-counter runs to about 97 (NTSC) or 102 (PAL)
	phiCnt <= locCnt;
	phiPreHalf <= localPreHalf;
	phiHalf <= localHalf;
	phiPreEnd <= localPreEnd;
	phiEnd <= localEnd;
	
	phiPost1 <= localPost1;
	phiPost2 <= localPost2;
	phiPost3 <= localPost3;
	phiPost4 <= localPost4;

	-- Input clock synchronizer
	process(clk) is
	begin
		if rising_edge(clk) then
			phiSync <= '0';
			phi2_n_reg <= phi2_n_reg(phi2_n_reg'high-1 downto 0) & phi2_n;
			-- Detect falling edge of phi2 (is rising edge here as phi2_n input is inverted).
			if phi2_n_reg = "000000000001" then
				phiSync <= '1';
			end if;
		end if;
	end process;

	-- Determine cycle length
	process(clk) is
	begin
		if rising_edge(clk) then
			no_clock <= '0';
			docking_station <= '0';
			avgDelta <= (others => '0');
			avgLen <= unsigned(signed(avgLen) + avgDelta);
			
			if (not c64Cnt) /= 0 then
				c64Cnt <= c64Cnt + 1;
			else
				-- No Sync? Use internal speed.

				-- Values for avgLen are determined experimentally using the testbench to measure actually speed.
				-- Higher numbers slow down clock. Lower numbers speed clock up. Try a few times until optimum is reached
				-- for particular system clock (100 Mhz at time of writing). Clocks can be accurate to atleast 3 digits with 4 guard bits.
				-- PAL mode 0.985248 Mhz
				avgLen <= to_unsigned(1703, 8+guardBits);
				if mode = '1' then
					-- NTSC mode 1.022727 Mhz
					avgLen <= to_unsigned(1643, 8+guardBits);
				end if;
				if phi2_n_reg(11 downto 4) = "11111111" then
					docking_station <= '1';
				end if;
				no_clock <= '1';
			end if;
			if phiSync = '1' then
				avgDelta <= signed("0" & c64Cnt) - signed("0" & avgLen((7+guardBits) downto guardBits));
				c64Cnt <= (others => '0');
			end if;
		end if;	
	end process;

	process(clk) is
	begin
		if rising_edge(clk) then
			localPost1 <= localHalf or localEnd;
			localPost2 <= localPost1;
			localPost3 <= localPost2;
			localPost4 <= localPost3;
		end if;
	end process;

	process(clk) is
		variable newFrac : unsigned(fracCnt'high+1 downto fracCnt'low);
	begin
		if rising_edge(clk) then
			localPreHalf <= '0';
			localHalf <= localPreHalf;
			localPreEnd <= '0';
			localEnd <= localPreEnd;
			
			locCnt <= locCnt + 1;
			if slvCnt >=  avgLen((7+guardBits) downto guardBits) then
				slvCnt <= (others => '0');
			else
				slvCnt <= slvCnt + 1;
			end if;

			if (slvCnt + phase_shift) = avgLen((7+guardBits) downto (1+guardBits)) then
				localPreHalf <= '1';
			end if;
			if (slvCnt + phase_shift) = avgLen((7+guardBits) downto guardBits)
			and localPhi = '1' then
				localPreEnd <= '1';
			end if;
			if localHalf = '1' then
				localPhi <= '1';
			end if;
			if localEnd = '1' then
				-- Add fractional part to clock counter to have higher precision
				newFrac := ("0" & fracCnt) + ("0" & (not avgLen(guardBits-1 downto 0)));
				fracCnt <= newFrac(fracCnt'range);

				localPhi <= '0';
				locCnt <= (others => '0');
				slvCnt <= c64Cnt + ("0000000" & newFrac(newFrac'high));
			end if;
		end if;
	end process;
end architecture;
