LIBRARY ieee;
USE ieee.std_logic_1164.all;
--USE ieee.std_logic_arith.all;


ENTITY timer IS PORT(
	clk				: IN std_logic; -- The clock who's rising edge triggers the Clock
	selectionToggle	: IN std_logic; -- If '1' on a rising clock edge (and 'active' is high, and 'mode' is high), changes the selected field
	inc				: IN std_logic; -- If '1' on a rising clock edge (and 'active' is high, and 'mode' is high) indicates the selected field should be incremented
	reset			: IN std_logic; -- If '1' (and 'active' is high) resets the Clock
	mode			: IN std_logic; -- If '1' the Clock operates in set mode, otherwise it operates in "normal" mode
	active			: IN std_logic; -- If '1' indicates the Clock should respond to it's inputs ('selectionToggle', 'inc' & 'reset')

	-- All the following are 7-segment display outputs.  They are all wired directly to the outputs for each field.

	csecLSD			: BUFFER integer range 0 to 9; -- msecLSD
	csecMSD			: BUFFER integer range 0 to 9; -- msecMSD
	secLSD			: BUFFER integer range 0 to 9;
	secMSD			: BUFFER integer range 0 to 9;
	minLSD			: BUFFER integer range 0 to 9;
	minMSD			: BUFFER integer range 0 to 9;
	hourLSD			: BUFFER integer range 0 to 9;
	hourMSD			: BUFFER integer range 0 to 9;

	alarm			: BUFFER std_logic; -- If '1', indicates the alarm is going off

	enable			: OUT std_logic_vector(7 downto 0); -- Represents the 8 7-segment displays, where a '1' indicates the display should be active, a '0' indicates it should be off
	flash			: BUFFER std_logic_vector (7 DOWNTO 0)); -- As above, but '1' indicates the display should flash on and off, '0' indicates "normal" behaviour
END timer;


ARCHITECTURE main OF timer IS
	SIGNAL resetTime: std_logic; -- If '1', the fields should be reset to the values in the signals below.  Otherwise, they should be left otherwise alone, and the "setting" values for each field come from the defaults

	SIGNAL setCSecLSD: integer range 0 to 9;
	SIGNAL setCSecMSD: integer range 0 to 9;
	SIGNAL setSecLSD: integer range 0 to 9;
	SIGNAL setSecMSD: integer range 0 to 9;
	SIGNAL setMinLSD: integer range 0 to 9;
	SIGNAL setMinMSD: integer range 0 to 9;
	SIGNAL setHourLSD: integer range 0 to 9;
	SIGNAL setHourMSD: integer range 0 to 9;

	SIGNAL digitSelection: std_logic_vector(3 DOWNTO 0); -- The currently selected field ("0001" for centiseconds, "0010" for seconds, "0100" for minutes, "1000" for hours)
	SIGNAL timerActive: std_logic; -- If '1' the timer is active and should be counting down (or alarming if the count is zero)

	SIGNAL centisecondCarry: std_logic; -- '1' if the centiseconds field underflowed
	SIGNAL secondCarry: std_logic; -- '1' if the seconds field underflowed
	SIGNAL minuteCarry: std_logic; -- '1' if the minutes field underflowed
	SIGNAL hourCarry: std_logic; -- '1' if the hours field underflowed

	SIGNAL centisecondsReset: std_logic; -- '1' if the centiseconds field should be reset
	SIGNAL centisecondsInc: std_logic; -- '1' if the centiseconds field should be decremented
	SIGNAL secondsReset: std_logic; -- '1' if the seconds field should be reset
	SIGNAL secondsInc: std_logic; -- '1' if the seconds field should be decremented
	SIGNAL minutesReset: std_logic; -- '1' if the minutes field should be reset
	SIGNAL minutesInc: std_logic; -- '1' if the minutes field should be decremented
	SIGNAL hoursReset: std_logic; -- '1' if the hours field should be reset
	SIGNAL hoursInc: std_logic; -- '1' if the hours field should be decremented

	--SIGNAL centisecondsClock: std_logic; -- Since we can disable the timer when it's off, we need a signal to be the and of the system clock and the timer activity status
	SIGNAL direction: std_logic; -- The direction of the fields (always '0', decrementing)

	-- The minimum values for each of the fields; most are aliased because they are identical and needn't waste additional space
	SIGNAL hoursMinLSD: integer RANGE 0 to 9;
	SIGNAL hoursMinMSD: integer RANGE 0 to 9;
	ALIAS minutesMinLSD: integer RANGE 0 to 9 IS hoursMinLSD;
	ALIAS minutesMinMSD: integer RANGE 0 to 9 IS hoursMinMSD;
	ALIAS secondsMinLSD:integer RANGE 0 to 9 IS minutesMinLSD;
	ALIAS secondsMinMSD:integer RANGE 0 to 9 IS minutesMinMSD;
	ALIAS centisecondsMinLSD:integer RANGE 0 to 9 IS minutesMinLSD;
	ALIAS centisecondsMinMSD:integer RANGE 0 to 9 IS minutesMinMSD;

	-- The maximum values for each of the fields
	SIGNAL hoursMaxLSD: integer RANGE 0 to 9;
	SIGNAL hoursMaxMSD: integer RANGE 0 to 9;
	SIGNAL minutesMaxLSD: integer RANGE 0 to 9;
	SIGNAL minutesMaxMSD: integer RANGE 0 to 9;
	SIGNAL secondsMaxLSD: integer RANGE 0 to 9;
	SIGNAL secondsMaxMSD: integer RANGE 0 to 9;
	SIGNAL centisecondsMaxLSD: integer RANGE 0 to 9;
	SIGNAL centisecondsMaxMSD: integer RANGE 0 to 9;

	COMPONENT clockField IS PORT( -- Yes, clockField, but it works for our purposes too
		clk			: IN std_logic;
		reset		: IN std_logic;
		inc			: IN std_logic;
		direction	: IN std_logic;
		minLSD		: IN integer RANGE 0 to 9;
		minMSD		: IN integer RANGE 0 to 9;
		maxLSD		: IN integer RANGE 0 to 9;
		maxMSD		: IN integer RANGE 0 to 9;

		lsd			: BUFFER integer RANGE 0 to 9;
		msd			: BUFFER integer RANGE 0 to 9;
		carry		: BUFFER std_logic);
	END COMPONENT;
BEGIN
	enable <= X"FF";

	direction <= (active and mode) or alarm;

	hoursMinLSD <= 0;
	hoursMinMSD <= 0;

	centisecondsReset <= ((active and reset) or alarm or resetTime);
	centisecondsInc <= (((not active) or (not mode)) and timerActive) or (mode and digitSelection(0) and inc and active);

	centiseconds: clockField PORT MAP(
		clk => clk,
		reset => centisecondsReset,
		inc => centisecondsInc,
		direction => direction,
		minLSD => centisecondsMinLSD,
		minMSD => centisecondsMinMSD,
		maxLSD => centisecondsMaxLSD,
		maxMSD => centisecondsMaxMSD,
		lsd => csecLSD,
		msd => csecMSD,
		carry => centisecondCarry);

	secondsReset <= ((active and reset) or alarm or resetTime);
	secondsInc <= (((not active) or (not mode)) and centisecondCarry and timerActive) or (mode and digitSelection(1) and inc and active);

	seconds: clockField PORT MAP(
		clk => clk,
		reset => secondsReset,
		inc => secondsInc,
		direction => direction,
		minLSD => secondsMinLSD,
		minMSD => secondsMinMSD,
		maxLSD => secondsMaxLSD,
		maxMSD => secondsMaxMSD,
		lsd => secLSD,
		msd => secMSD,
		carry => secondCarry);

	minutesReset <= ((active and reset) or alarm or resetTime);
	minutesInc <= (((not active) or (not mode)) and secondCarry and timerActive) or (mode and digitSelection(2) and inc and active);

	minutes: clockField PORT MAP(
		clk => clk,
		reset => minutesReset,
		inc => minutesInc,
		direction => direction,
		minLSD => minutesMinLSD,
		minMSD => minutesMinMSD,
		maxLSD => minutesMaxLSD,
		maxMSD => minutesMaxMSD,
		lsd => minLSD,
		msd => minMSD,
		carry => minuteCarry);
		
	hoursReset <= ((active and reset) or alarm or resetTime);
	hoursInc <= (((not active) or (not mode)) and minuteCarry and timerActive) or (mode and digitSelection(3) and inc and active);

	hours: clockField PORT MAP(
		clk => clk,
		reset => hoursReset,
		inc => hoursInc,
		direction => direction,
		minLSD => hoursMinLSD,
		minMSD => hoursMinMSD,
		maxLSD => hoursMaxLSD,
		maxMSD => hoursMaxMSD,
		lsd => hourLSD,
		msd => hourMSD,
		carry => hourCarry);

	minResetter: process(clk) begin
		if (clk'event and clk = '1') then
			if (resetTime = '1') then
				hoursMaxLSD <= setHourLSD;
				hoursMaxMSD <= setHourMSD;
				minutesMaxLSD <= setMinLSD;
				minutesMaxMSD <= setMinMSD;
				secondsMaxLSD <= setSecLSD;
				secondsMaxMSD <= setSecMSD;
				centisecondsMaxLSD <= setCSecLSD;
				centisecondsMaxMSD <= setCSecMSD;
			else
				hoursMaxLSD <= 9;
				hoursMaxMSD <= 9;
				minutesMaxLSD <= 9;
				minutesMaxMSD <= 5;
				secondsMaxLSD <= 9;
				secondsMaxMSD <= 5;
				centisecondsMaxLSD <= 9;
				centisecondsMaxMSD <= 9;
			end if;
		end if;
	end process;

	timerer: process(clk) begin
		if (clk'event and clk = '1') then
			resetTime <= '0';

			if ((timerActive = '1') and (hourLSD = 0) and (hourMSD = 0) and (minLSD = 0) and (minMSD = 0) and (secLSD = 0) and (secMSD = 0) and (csecLSD = 0) and (csecMSD = 0) and ((active = '0') or (mode = '0'))) then
				alarm <= '1';
				timerActive <= '0';
			end if;

			if (alarm = '1') then
				flash <= X"FF";
			elsif (mode = '0') then
				flash <= X"00";
			end if;

			if (active = '1') then
				if (reset = '1') then
					digitSelection <= "0001";
					flash <= X"00";
					timerActive <= '0';
					resetTime <= '0';
				else
					if (mode = '1') then
						setCSecLSD <= csecLSD;
						setCSecMSD <= csecMSD;
						setSecLSD <= secLSD;
						setSecMSD <= secMSD;
						setMinLSD <= minLSD;
						setMinMSD <= minMSD;
						setHourLSD <= hourLSD;
						setHourMSD <= hourMSD;

						alarm <= '0';
						timerActive <= '0';

						if (selectionToggle = '1') then
							case digitSelection is
								when "0001" => digitSelection <= "0010";
								when "0010" => digitSelection <= "0100";
								when "0100" => digitSelection <= "1000";
								--when "1000" => digitSelection <= "0001";
								when others => digitSelection <= "0001";
							end case;
						else
							-- The flash will be held one clock cycle longer (10ms) than it should, as the above changes won't take effect on the case below until the next pass through, but that shouldn't really matter.
						
							case digitSelection is
								when "0001" => flash <= X"03";
								when "0010" => flash <= X"0c";
								when "0100" => flash <= X"30";
								when "1000" => flash <= X"c0";
								when others => digitSelection <= "0001";
							end case;
						end if;
					else
						if (inc = '1') then
							if (alarm = '1') then
								alarm <= '0';
								timerActive <= '0';
								flash <= X"00";
								resetTime <= '1';
							else
								timerActive <= not timerActive;
							end if;
						end if;
					end if;
				end if;
			else
				flash <= X"00";
			end if;
		end if;
	end process;
END main;

