-- EMACS settings: -*-  tab-width: 2; indent-tabs-mode: t -*-
-- vim: tabstop=2:shiftwidth=2:noexpandtab
-- kate: tab-width 2; replace-tabs off; indent-width 2;
-- =============================================================================
-- Authors:				 	Patrick Lehmann
--
-- Entity:				 	Generic Fan Controller
--
-- Description:
-- 
-- .. code-block:: none
--
--		This module generates a PWM signal for a 3-pin (transistor controlled) or
--		4-pin fan header. The FPGAs temperature is read from device specific system
--		monitors (normal, user temperature, over temperature).
--
--		For example the Xilinx System Monitors are configured as follows:
--
--										|											 /-----\
--		Temp_ov	 on=80	|	-	-	-	-	-	-	/-------/				\
--										|						 /				|				 \
--		Temp_ov	off=60	|	-	-	-	-	-	/	-	-	-	-	|	-	-	-	-	\----\
--										|					 /					|								\
--										|					/						|							 | \
--		Temp_us	 on=35	|	-	 /---/						|							 |	\
--		Temp_us	off=30	|	-	/	-	-|-	-	-	-	-	-	|	-	-	-	-	-	-	-|-  \------\
--										|  /		 |						|							 |					 \
--		----------------|--------|------------|--------------|----------|---------
--		pwm =						|		min	 |	medium		|		max				 |	medium	|	min
--
--
-- License:
-- =============================================================================
-- Copyright 2007-2015 Technische Universitaet Dresden - Germany
--										 Chair of VLSI-Design, Diagnostics and Architecture
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--		http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
-- =============================================================================

library IEEE;
use			IEEE.STD_LOGIC_1164.all;

library PoC;
use			PoC.config.all;
use			PoC.utils.all;
use			PoC.strings.all;
use			PoC.vectors.all;
use			PoC.physical.all;
use			PoC.components.all;
use			PoC.xil.all;


entity [docs]io_FanControl is
	generic (
		CLOCK_FREQ							: FREQ;
		ADD_INPUT_SYNCHRONIZERS	: boolean			:= TRUE;
		ENABLE_TACHO						: boolean			:= FALSE
	);
	port (
		-- Global Control
		Clock										: in	std_logic;
		Reset										: in	std_logic;

		-- Fan Control derived from internal System Health Monitor
		Fan_PWM									: out	std_logic;

    -- Decoding of Speed Sensor (Requires ENABLE_TACHO)
    Fan_Tacho      : in  std_logic := 'X';
    TachoFrequency : out std_logic_vector(15 downto 0)
  );
end entity;


architecture [docs]rtl of io_FanControl is
	-- constant TIME_STARTUP			: TIME																					:= 500 ms;		-- StartUp time
	-- Use frequencies only to make Vivado work.
	constant TIME_STARTUP_INVERSE : FREQ																					:= 2 Hz;		-- StartUp time
	constant PWM_RESOLUTION		: positive																					:= 4;					-- 4 Bit resolution => 0 to 15 steps
	constant PWM_FREQ					: FREQ																							:= 10 Hz;			--

	constant TACHO_RESOLUTION	: positive																					:= 8;

	signal PWM_PWMIn					: std_logic_vector(PWM_RESOLUTION - 1 downto 0);
	signal PWM_PWMOut					: std_logic																					:= '0';

begin
	-- System Monitor and temperature to PWM ratio calculation for Virtex6
	-- ==========================================================================================================================================================
	genXilinx : if VENDOR = VENDOR_XILINX generate
		signal OverTemperature_async	: std_logic;
		signal OverTemperature_sync		: std_logic;

		signal UserTemperature_async	: std_logic;
		signal UserTemperature_sync		: std_logic;

		signal TC_Timeout					: std_logic;
		signal StartUp						: std_logic;
	begin
		genXilinxBoard : if str_imatch(BOARD_NAME, "ML605") or str_imatch(BOARD_NAME, "KC705") or
		                    str_imatch(BOARD_NAME, "VC707") or str_imatch(BOARD_NAME, "KCU105") generate
			SystemMonitor : xil_SystemMonitor
				port map (
					Reset								=> Reset,										-- Reset signal for the System Monitor control logic

					Alarm_UserTemp			=> UserTemperature_async,		-- Temperature-sensor alarm output
					Alarm_OverTemp			=> OverTemperature_async,		-- Over-Temperature alarm output
					Alarm								=> open,										-- OR'ed output of all the Alarms
					VP									=> '0',											-- Dedicated Analog Input Pair
					VN									=> '0'
				);
		end generate;

		sync : entity PoC.sync_Bits_Xilinx
			generic map (
				BITS			=> 2
			)
			port map (
				Clock				=> Clock,
				Input(0)		=> OverTemperature_async,
				Input(1)		=> UserTemperature_async,
				Output(0)		=> OverTemperature_sync,
				Output(1)		=> UserTemperature_sync
			);

		-- timer for warm-up control
		-- ==========================================================================================================================================================
		TC : entity PoC.io_TimingCounter
			generic map (
				TIMING_TABLE				=> (0 => CLOCK_FREQ/TIME_STARTUP_INVERSE)	-- timing table
			)
			port map (
				Clock								=> Clock,				-- clock
				Enable							=> StartUp,			-- enable counter
				Load								=> '0',					-- load Timing Value from TIMING_TABLE selected by slot
				Slot								=> 0,						--
				Timeout							=> TC_Timeout		-- timing reached
			);

		StartUp	<= not TC_Timeout;

		process(StartUp, UserTemperature_sync, OverTemperature_sync)
		begin
			if		(StartUp = '1') then								PWM_PWMIn <= to_slv(2**PWM_RESOLUTION - 1, PWM_RESOLUTION);			-- 100%; start up
			elsif (OverTemperature_sync = '1') then		PWM_PWMIn <= to_slv(2**PWM_RESOLUTION - 1, PWM_RESOLUTION);			-- 100%
			elsif (UserTemperature_sync = '1') then		PWM_PWMIn <= to_slv(2**(PWM_RESOLUTION - 1), PWM_RESOLUTION);			-- 50%
			else																			PWM_PWMIn <= to_slv(4, PWM_RESOLUTION);														-- 13%
			end if;
		end process;
	end generate;

	genAltera : if VENDOR = VENDOR_ALTERA generate
--		signal OverTemperature_async	: STD_LOGIC;
		signal OverTemperature_sync		: std_logic;

--		signal UserTemperature_async	: STD_LOGIC;
		signal UserTemperature_sync		: std_logic;

		signal TC_Timeout					: std_logic;
		signal StartUp						: std_logic;
	begin
		genDE4 : if str_imatch(BOARD_NAME, "DE4") generate
			OverTemperature_sync		<= '0';
			UserTemperature_sync		<= '1';
		end generate;

		-- timer for warm-up control
		-- ==========================================================================================================================================================
		TC : entity PoC.io_TimingCounter
			generic map (
				TIMING_TABLE				=> (0 => CLOCK_FREQ/TIME_STARTUP_INVERSE)	-- timing table
			)
			port map (
				Clock								=> Clock,				-- clock
				Enable							=> StartUp,			-- enable counter
				Load								=> '0',					-- load Timing Value from TIMING_TABLE selected by slot
				Slot								=> 0,						--
				Timeout							=> TC_Timeout		-- timing reached
			);

		StartUp	<= not TC_Timeout;

		process(StartUp, UserTemperature_sync, OverTemperature_sync)
		begin
			if		(StartUp = '1') then								PWM_PWMIn <= to_slv(2**PWM_RESOLUTION - 1, PWM_RESOLUTION);			-- 100%; start up
			elsif (OverTemperature_sync = '1') then		PWM_PWMIn <= to_slv(2**PWM_RESOLUTION - 1, PWM_RESOLUTION);			-- 100%
			elsif (UserTemperature_sync = '1') then		PWM_PWMIn <= to_slv(2**(PWM_RESOLUTION - 1), PWM_RESOLUTION);			-- 50%
			else																			PWM_PWMIn <= to_slv(4, PWM_RESOLUTION);														-- 13%
			end if;
		end process;
	end generate;

	-- PWM signal modulator
	-- ==========================================================================================================================================================
	PWM : entity PoC.io_PulseWidthModulation
		generic map (
			CLOCK_FREQ					=> CLOCK_FREQ,				--
			PWM_FREQ						=> PWM_FREQ,					--
			PWM_RESOLUTION			=> PWM_RESOLUTION			--
		)
		port map (
			Clock								=> Clock,
			Reset								=> Reset,
			PWMIn								=> PWM_PWMIn,
			PWMOut							=> PWM_PWMOut
		);

	-- registered output
	Fan_PWM 		<= PWM_PWMOut	when rising_edge(Clock);

	-- tacho signal interpretation -> convert to RPM
	-- ==========================================================================================================================================================
	genNoTacho : if not ENABLE_TACHO generate
		TachoFrequency <= (TachoFrequency'range => 'X');
	end generate;
	genTacho : if ENABLE_TACHO generate
		signal Tacho_sync					: std_logic;
		signal Tacho_Freq					: std_logic_vector(TACHO_RESOLUTION - 1 downto 0);
	begin
		-- Input Synchronization
		genNoSync : if not ADD_INPUT_SYNCHRONIZERS generate
			Tacho_sync <= Fan_Tacho;
		end generate;
		genSync : if ADD_INPUT_SYNCHRONIZERS generate
			sync_i : entity PoC.sync_Bits
				port map (
					Clock  		=> Clock,					-- Clock to be synchronized to
					Input(0)  => Fan_Tacho,			-- Data to be synchronized
					Output(0) => Tacho_sync			-- synchronised data
				);
		end generate;

		Tacho : entity PoC.io_FrequencyCounter
			generic map (
				CLOCK_FREQ					=> CLOCK_FREQ,					--
				TIMEBASE						=> (60 sec / 64),				-- ca. 1 second
				RESOLUTION					=> 8										-- max. ca. 256 RPS -> max. ca. 16k RPM
			)
			port map (
				Clock								=> Clock,
				Reset								=> Reset,
				FreqIn							=> Tacho_sync,
				FreqOut							=> Tacho_Freq
			);

		-- multiply by 64; divide by 2 for RPMs (2 impulses per revolution) => append 5x '0'
		TachoFrequency	<= resize(Tacho_Freq & "00000", TachoFrequency'length);		-- resizing to 16 bit
	end generate;
end;