summaryrefslogtreecommitdiffstats
path: root/hdl/timer0.vhd
blob: c2846dc0a1fce7d5e8324551dce50fe719292396 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity timer0 is
  generic (
    CLK_FREQ : positive := 50_000_000
    );
  port (
    clk   : in std_logic;
    reset : in std_logic;

    -- External clock pin (used when T0CS='1')
    t0cki : in std_logic;

    -- OPTION_REG control bits
    t0cs : in std_logic;                -- 0=Fosc/4, 1=T0CKI
    t0se : in std_logic;                -- 0=rising edge, 1=falling edge
    psa  : in std_logic;  -- 0=prescaler->Timer0, 1=prescaler->WDT
    ps   : in std_logic_vector(2 downto 0);  -- prescaler rate select

    -- TMR0 register interface (mapped at 0x01)
    tmr0_we  : in  std_logic;
    tmr0_in  : in  std_logic_vector(7 downto 0);
    tmr0_out : out std_logic_vector(7 downto 0);

    -- Overflow flag: pulses one clock on FFh->00h rollover
    t0if : out std_logic
    );
end entity timer0;

architecture rtl of timer0 is

  pure function ps_rate(sel : std_logic_vector(2 downto 0)) return natural is
  begin
    case sel is
      when "000"  => return 2;
      when "001"  => return 4;
      when "010"  => return 8;
      when "011"  => return 16;
      when "100"  => return 32;
      when "101"  => return 64;
      when "110"  => return 128;
      when others => return 256;
    end case;
  end function;

  -- Fosc/4 tick generator (one pulse every 4 clocks)
  constant FOSC4_DIV : positive                         := 4;
  signal fosc4_cnt   : natural range 0 to FOSC4_DIV - 1 := 0;
  signal fosc4_tick  : std_logic                        := '0';

  -- 2FF synchroniser for the post-mux clock input
  signal sync_ff1  : std_logic := '0';
  signal sync_ff2  : std_logic := '0';
  signal sync_prev : std_logic := '0';  -- for edge detection after sync
  signal sync_tick : std_logic := '0';  -- one-cycle tick on selected edge

  -- Prescaler
  signal prescaler : unsigned(7 downto 0) := (others => '0');
  signal ps_tick   : std_logic            := '0';  -- one-cycle pulse out of prescaler

  -- TMR0 counter
  signal tmr0 : unsigned(7 downto 0) := (others => '0');

  -- Write inhibit: per datasheet, 2 cycles after a TMR0 write the
  -- prescaler increment is blocked to avoid a spurious count
  signal wr_inhibit : natural range 0 to 2 := 0;

begin

  fosc4_timer_proc : process(clk)
  begin
    if rising_edge(clk) then

      fosc4_tick <= '0';

      if fosc4_cnt = FOSC4_DIV - 1 then
        fosc4_tick <= '1';
        fosc4_cnt  <= 0;
      else
        fosc4_cnt  <= fosc4_cnt + 1;
      end if;
    end if;
  end process fosc4_timer_proc;

  prescaler_proc : process(clk)
  begin
    if rising_edge(clk) then
      ps_tick <= '0';

      if tmr0_we = '1' then
        prescaler <= (others => '0');
      elsif fosc4_tick = '1' and psa = '0' then
        if to_integer(prescaler) + 1 >= ps_rate(ps) then
          prescaler <= (others => '0');
          ps_tick   <= '1';
        else
          prescaler <= prescaler + 1;
        end if;
      end if;
    end if;
  end process prescaler_proc;

  tmr0_out <= std_logic_vector(tmr0);

  timer0_proc : process(clk)
  begin
    if rising_edge(clk) then
      t0if <= '0';

      -- 2FF sync
      sync_ff1 <= '1' when (psa = '1' and fosc4_tick = '1') or
                            (psa = '0' and ps_tick    = '1') else '0';
      sync_ff2 <= sync_ff1;

      if reset = '1' then
        tmr0     <= (others => '0');
        sync_ff1 <= '0';
        sync_ff2 <= '0';
      elsif tmr0_we = '1' then
        tmr0 <= unsigned(tmr0_in);
      elsif sync_ff2 = '1' then
        if tmr0 = x"FF" then
          t0if <= '1';
        end if;
        tmr0 <= tmr0 + 1;
      end if;
    end if;
  end process timer0_proc;
end architecture rtl;