-- dcfSim.vhd
--------------------------------------------------------------------------------
--		ajm		06-jul-2016
--------------------------------------------------------------------------------
--
-- entity	dcfSim		-simulate DCF77 source
--				-1 msec timescale
--				-butNoise: 4 noise levels
--				 off -> 1/128 -> 1/64 -> 1/32 -> 1/16
--				-butJitter: 4 jitter levels
--				 off -> 2..5 bit signed
--				-initial date/time:	Mi. 06.07.2016	12:34

-- architecture	behavior
--
--------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

-- entity	----------------------------------------------------------------
--------------------------------------------------------------------------------
entity dcfSim is
generic(resetAC	: std_logic		:= '0';		-- active: reset
	keyAC	: std_logic		:= '0';		-- active: button
	ledAC	: std_logic		:= '1';		-- active: led
	clkFreqC: positive		:= 50000000;	-- clock frequency
	--			-initial date/time:	Mi. 06.07.2016	12:34
	min1C	: unsigned (3 downto 0)	:= "0100";	-- 4
	min10C	: unsigned (2 downto 0)	:=  "011";	-- 3
	hour1C	: unsigned (3 downto 0)	:= "0010";	-- 2
	hour10C	: unsigned (1 downto 0)	:=   "01";	-- 1
	wdayC	: unsigned (2 downto 0)	:=  "011";	-- 3
	day1C	: unsigned (3 downto 0)	:= "0110";	-- 6
	day10C	: unsigned (1 downto 0)	:=   "00";	-- 0
	month1C	: unsigned (3 downto 0)	:= "0111";	-- 7
	month10C: std_logic		:=    '0';	-- 0
	year1C	: unsigned (3 downto 0)	:= "0110";	-- 6
	year10C	: unsigned (3 downto 0)	:= "0001";	-- 1
	xtraC	: std_logic_vector (15 to 19):= "00010");-- MESZ
port (	clk50	: in	std_logic;		-- 50 MHz external clock
	reset	: in	std_logic;		-- reset signal		['0']
	keyJitter:in	std_logic;		-- jitter button	['0']
	keyNoise: in	std_logic;		-- noise  button	['0']
	led	: out	std_logic_vector( 7 downto 0); -- led output	['1']
	dcfSig	: out	std_logic);		-- DCF77 signal
end entity dcfSim;


-- architecture	----------------------------------------------------------------
--------------------------------------------------------------------------------
architecture behavior of dcfSim is
  type	keyStateT	is (s0, s1, s2, s3, s4);	-- states jitter/noise

  signal	clk1K	: std_logic;			-- 1 KHz clock
  signal	lfsr	: std_logic_vector(31 downto 1);-- := (others => '0');
  signal	noiseSt, jitterSt	: keyStateT;
  signal	noise	: std_logic;			-- xor noise
  signal	jitter	: signed (4 downto 0);		-- 0 to -16..15

  -- stDecode	- output 4 LEDs
  ------------------------------------------------------------------------------
  function stDecode	(state	: keyStateT)	-- keyState -> led bar
	return		  std_logic_vector is
  begin
    case state is
    when s0 =>	return (not ledAC)&(not ledAC)&(not ledAC)&(not ledAC);	--"0000"
    when s1 =>	return (not ledAC)&(not ledAC)&(not ledAC)&     ledAC;	--"0001"
    when s2 =>	return (not ledAC)&(not ledAC)&     ledAC &     ledAC;	--"0011"
    when s3 =>	return (not ledAC)&     ledAC &     ledAC &     ledAC;	--"0111"
    when s4 =>	return      ledAC &     ledAC &     ledAC &     ledAC;	--"1111"
    end case;
  end function stDecode;

begin
-- clk1P	- generate 1KHz clock from (50MHz) input clock
--------------------------------------------------------------------------------
clk1P: process (clk50, reset) is
    constant	clkMaxC	: integer	:= (clkFreqC/2000)-1;	-- 24999;
    variable	clkCnt	: integer range 0 to clkMaxC;
  begin
    if reset = resetAC then	clkCnt	:= clkMaxC;
				clk1K	<= '1';
    elsif rising_edge(clk50) then
      if clkCnt = 0 then	clkCnt  := clkMaxC;
				clk1K	<= not clk1K;
		    else	clkCnt	:= clkCnt-1;
      end if;
    end if;
  end process clk1P;

-- lfsrP	- pseudo-random numbers
--		- using 31-bit LFSR
--------------------------------------------------------------------------------
lfsrP: process (clk50, reset) is
  begin
    if reset = resetAC then	lfsr	<= (others => '0');
    elsif rising_edge(clk50) then
	lfsr <= lfsr(30 downto 1) & (lfsr(31) xnor lfsr(28));
    end if;
  end process lfsrP;

-- keyFSMs	- keyJitter => jitterSt	=> led(7 downto 4)
--		- keyNoise  => noiseSt	=> led(3 downto 0)
--		- key... toggles state: s0 -> s1 -> s2 -> s3 -> s4 -> s0
--------------------------------------------------------------------------------
keyJP: process (clk1K, reset) is
	variable key0En	: boolean;	-- enable key='0' action
  begin
    if reset = resetAc then	jitterSt<= keyStateT'left;
				key0En	:= true;
    elsif rising_edge(clk1K) then
      if keyJitter = keyAC then
	if key0En then		key0En	:= false;
	  if jitterSt = keyStateT'right
		then		jitterSt<= keyStateT'left;
		else		jitterSt<= keyStateT'succ(jitterSt);
	  end if;
	end if;
      else			key0En	:= true;
      end if;
    end if;
  end process keyJP;

keyNP: process (clk1K, reset) is
	variable key0En	: boolean;	-- enable key='0' action
  begin
    if reset = resetAC then	noiseSt	<= keyStateT'left;
				key0En	:= true;
    elsif rising_edge(clk1K) then
      if keyNoise = keyAC then
	if key0En then		key0En	:= false;
	  if noiseSt = keyStateT'right
		then		noiseSt	<= keyStateT'left;
		else		noiseSt	<= keyStateT'succ(noiseSt);
	  end if;
	end if;
      else			key0En	:= true;
      end if;
    end if;
  end process keyNP;

  led(7 downto 4) <= stDecode(jitterSt);
  led(3 downto 0) <= stDecode(noiseSt);
--led(7 downto 4) <= noise&noise&noise&noise;
--led(3 downto 0) <= std_logic_vector(jitter(4 downto 1));

-- noiseP	- set noise
--		- use 7-bit from lfsr as reference: 0..127
--------------------------------------------------------------------------------
noiseP: process (clk1K, reset) is
  begin
    if reset = resetAC then	noise	<= '0';
    elsif falling_edge(clk1K) then
	case noiseSt is
	when s0 =>		noise	<= '0';
	when s1 =>  if lfsr(16 downto 10) = "0000001"	-- 1/128
			 then	noise	<= '1';
			 else	noise	<= '0';
		    end if;
	when s2 =>  if lfsr(15 downto 10) = "000001"	-- 1/64
			 then	noise	<= '1';
			 else	noise	<= '0';
		    end if;
	when s3 =>  if lfsr(14 downto 10) = "00001"	-- 1/32
			 then	noise	<= '1';
			 else	noise	<= '0';
		    end if;
	when s4 =>  if lfsr(13 downto 10) = "0001"	-- 1/16
			 then	noise	<= '1';
			 else	noise	<= '0';
		    end if;
	end case;
    end if;
  end process noiseP;

-- jitterP	- set jitter
--		- use 0, 2..5-bit from lfsr as offset: -16..15
--------------------------------------------------------------------------------
jitterP: process (clk1K, reset) is
  begin
    if reset = resetAC then	jitter	<= (others => '0');
    elsif falling_edge(clk1K) then
	case jitterSt is
	when s0 =>	jitter	<= (others => '0');
	when s1 => 	jitter	<= (0 => lfsr(20), others => lfsr(21));
	when s2 => 	jitter	<= (0 => lfsr(20), 1 => lfsr(21),
					others => lfsr(22));
	when s3 => 	jitter	<= (0 => lfsr(20), 1 => lfsr(21),
					2 => lfsr(22), others => lfsr(23));
	when s4 => 	jitter	<= signed(lfsr(24 downto 20));
	end case;
    end if;
  end process jitterP;

-- dcfP		- dcfSender
--		- compute dcfCode, send bits: dcfSig xor noise
--		- timescale 1ms: 100/200/900/800/1900/1800 +jitter
--------------------------------------------------------------------------------
  dcfP: process (clk1K, reset) is
    variable	min1	: unsigned (3 downto 0);
    variable	min10	: unsigned (2 downto 0);
    variable	hour1	: unsigned (3 downto 0);
    variable	hour10	: unsigned (1 downto 0);
    variable	secCnt	: integer range 0 to 58;
    variable	msecCnt	: integer range 0 to 2000;
    variable	dcfCode	: std_logic_vector (0 to 58);
    variable	dcfOut	: std_logic;

    -- flip	- change bit order in n-bit vector
    ----------------------------------------------------------------------------
    function flip (inp	: unsigned (natural range <>))
	return		std_logic_vector is
      variable	result	: std_logic_vector (inp'reverse_range);
    begin
	for i in inp'reverse_range loop
		result(i) := inp(i);
	end loop;
	return result;
    end function flip;

    -- toDCF	- compute dcfCode
    ----------------------------------------------------------------------------
    procedure	toDCF	is
    begin
	dcfCode(0 to 14)	:= "000000000000000";
	dcfCode(15 to 19)	:= xtraC;
	dcfCode(20)		:= '1';
	dcfCode(21 to 24)	:= flip(min1);
	dcfCode(25 to 27)	:= flip(min10);
	dcfCode(28)		:= '0';
	for i in 21 to 27 loop
	  dcfCode(28)		:= dcfCode(28) xor dcfCode(i);
	end loop;

	dcfCode(29 to 32)	:= flip(hour1);
	dcfCode(33 to 34)	:= flip(hour10);
	dcfCode(35)		:= '0';
	for i in 29 to 34 loop
	  dcfCode(35)		:= dcfCode(35) xor dcfCode(i);
	end loop;

	dcfCode(36 to 39)	:= flip(day1C);
	dcfCode(40 to 41)	:= flip(day10C);
	dcfCode(42 to 44)	:= flip(wdayC);
	dcfCode(45 to 48)	:= flip(month1C);
	dcfCode(49)		:= month10C;
	dcfCode(50 to 53)	:= flip(year1C);
	dcfCode(54 to 57)	:= flip(year10C);
	dcfCode(58)		:= '0';
	for i in 36 to 57 loop
	  dcfCode(58)		:= dcfCode(58) xor dcfCode(i);
	end loop;
    end procedure toDCF;

    -- incTime	- increment time variables: hour10, hour1, min10, min1
    ----------------------------------------------------------------------------
    procedure incTime is
    begin
	if min1 = 9 then	min1	:= "0000";
	  if min10 = 5 then	min10	:=  "000";
	    if hour1 = 9 or (hour1 = 3 and hour10 = 2)
	    then		hour1	:= "0000";
	      if hour10 = 2
	      then		hour10	:=   "00";
	      else		hour10	:= hour10 + 1;
	      end if;
	    else		hour1	:= hour1 + 1;
	    end if;
	  else			min10	:= min10 + 1;
	  end if;
	else			min1	:= min1 + 1;
	end if;
    end procedure incTime;

  begin
    if reset = resetAC then	min1	:= min1C;	-- init time
				min10	:= min10C;
				hour1	:= hour1C;
				hour10	:= hour10C;
				secCnt	:= 0;		-- sec to send
				msecCnt	:= 250;		-- initial delay
				toDCF;			-- init vector
				dcfOut	:= '1';		-- output level 1/0
				dcfSig	<= '1';		-- dcf signal
    elsif rising_edge(clk1K) then	-- 1 msec timescale
      if msecCnt = 0 then		-- timeout, change state
	if dcfOut = '1' then		-- finished '1': send bit
	  if dcfCode(0) = '0'	then msecCnt :=  99 + to_integer(jitter);
				else msecCnt := 199 + to_integer(jitter);
	  end if;
	else				-- finished '0': shift/new dcfCode
	  if secCnt = 58 then		-- already 59 bit send
	    if dcfCode(0) = '0'	then msecCnt := 1899 + to_integer(jitter);
                                else msecCnt := 1799 + to_integer(jitter);
	    end if;
	    secCnt := 0;		-- new dcfCode
	    incTime;
	    toDCF;
	  else
	    if dcfCode(0) = '0'	then msecCnt := 899 + to_integer(jitter);
                                else msecCnt := 799 + to_integer(jitter);
	    end if;
	    secCnt  := secCnt + 1;	-- shift dcfCode
	    dcfCode := dcfCode(1 to 58) & '0';
	  end if;
	end if;
	dcfOut := not dcfOut;		-- change state
      else	msecCnt := msecCnt - 1;
      end if;
      dcfSig <= dcfOut xor noise;	-- send something, 1ms timing
    end if;
  end process dcfP;

end architecture behavior;

--------------------------------------------------------------------------------
-- dcfSim.vhd - end
