--+-----------------------------------+-------------------------------------+-- --| ___ ___ | (c) 2013-2014 William R Sowerbutts |-- --| ___ ___ ___ ___( _ ) / _ \ | will@sowerbutts.com |-- --| / __|/ _ \ / __|_ / _ \| | | | | |-- --| \__ \ (_) | (__ / / (_) | |_| | | A Z80 FPGA computer, just for fun |-- --| |___/\___/ \___/___\___/ \___/ | |-- --| | http://sowerbutts.com/ |-- --+-----------------------------------+-------------------------------------+-- --| A simple timer peripheral for timing intervals and generating periodic |-- --| interrupts. |-- --+-------------------------------------------------------------------------+-- -- -- There are two timers; a 1MHz 32-bit counter which always counts up (unless reset to 0) -- and whose value can be transferred atomically to a 32-bit latch, and a 1MHz 24-bit down -- counter which triggers an interrupt and is reset to a programmable value upon reaching -- zero. Writes to the register at base+1 perform timer operations according to the value -- written. The 1MHz is derived by prescaling the system clock. -- -- register layout: -- -- address read value write operation -- --------- ------------------------------------ ------------------------------------- -- base+0 status register set status register -- base+1 (unused) perform operation according to value written -- base+2 (unused) (no operation) -- base+3 (unused) (no operation) -- base+4 muxed register value (low byte) update muxed register value -- base+5 muxed register value update muxed register value -- base+6 muxed register value update muxed register value -- base+7 muxed register value (high byte) update muxed register value -- -- -- operation values (for writes to base+1): -- 00 -- acknowledge interrupt -- 01 -- reset upcounter value to zero -- 02 -- update latched value from upcounter value -- 03 -- reset downcounter -- 10 -- set register mux select to upcounter current -- 11 -- set register mux select to upcounter latched -- 12 -- set register mux select to downcounter current -- 13 -- set register mux select to downcountre reset -- -- -- control/status register layout: -- bits 0, 1, -- register mux select (controls which register is visible in registers at base+4 through base+7): -- 0 0 upcounter current value -- 0 1 upcounter latched value -- 1 0 downcounter current value -- 1 1 downcounter reset value -- (bits 2, 3, 4, 5 are currently unused) -- bit 6 -- countdown timer interrupt enable (0=disable, 1=enable) -- bit 7 -- interrupt flag (0=no interrupt, 1=one or interrupts occurred but not yet acknowledged) -- library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity timer is generic ( clk_frequency : natural := (128 * 1000000) ); port ( clk : in std_logic; reset : in std_logic; cpu_address : in std_logic_vector(2 downto 0); data_in : in std_logic_vector(7 downto 0); data_out : out std_logic_vector(7 downto 0); enable : in std_logic; req_read : in std_logic; req_write : in std_logic; interrupt : out std_logic ); end timer; architecture Behavioral of timer is signal upcounter_value : unsigned(31 downto 0) := (others => '0'); signal upcounter_latch : unsigned(31 downto 0) := (others => '0'); signal downcounter_value : unsigned(31 downto 0) := (others => '0'); signal downcounter_start : unsigned(31 downto 0) := (others => '0'); -- if using frequencies > 128MHz this counter will need to be wider than 7 bits signal counter_prescale : unsigned(6 downto 0) := (others => '0'); constant prescale_wrap : unsigned(6 downto 0) := to_unsigned((clk_frequency / 1000000) - 1, 7); -- aim for a 1MHz counter signal interrupt_enable : std_logic := '0'; signal interrupt_signal : std_logic := '0'; signal regmux_select : std_logic_vector(1 downto 0) := "00"; signal regmux_output : std_logic_vector(31 downto 0); signal regmux_updated : std_logic_vector(31 downto 0); signal status_register_value: std_logic_vector(7 downto 0); begin interrupt <= (interrupt_signal and interrupt_enable); with cpu_address select data_out <= status_register_value when "000", regmux_output(7 downto 0) when "100", regmux_output(15 downto 8) when "101", regmux_output(23 downto 16) when "110", regmux_output(31 downto 24) when "111", status_register_value when others; status_register_value <= interrupt_signal & interrupt_enable & "0000" & regmux_select; with regmux_select select regmux_output <= std_logic_vector(upcounter_value ) when "00", std_logic_vector(upcounter_latch ) when "01", std_logic_vector(downcounter_value) when "10", std_logic_vector(downcounter_start) when "11", std_logic_vector(downcounter_start) when others; with cpu_address(1 downto 0) select regmux_updated <= regmux_output(31 downto 8) & data_in when "00", regmux_output(31 downto 16) & data_in & regmux_output(7 downto 0) when "01", regmux_output(31 downto 24) & data_in & regmux_output(15 downto 0) when "10", data_in & regmux_output(23 downto 0) when "11", data_in & regmux_output(23 downto 0) when others; counter_proc: process(clk) begin if rising_edge(clk) then if reset = '1' then upcounter_value <= (others => '0'); upcounter_latch <= (others => '0'); downcounter_value <= (others => '0'); downcounter_start <= (others => '0'); counter_prescale <= (others => '0'); interrupt_enable <= '0'; interrupt_signal <= '0'; regmux_select <= "00"; else -- prescaled counter if counter_prescale = prescale_wrap then counter_prescale <= (others => '0'); -- reset prescale counter upcounter_value <= upcounter_value + 1; if downcounter_value = 0 then downcounter_value <= downcounter_start; interrupt_signal <= '1'; else downcounter_value <= downcounter_value - 1; end if; else counter_prescale <= counter_prescale + 1; end if; if enable = '1' and req_write = '1' then if cpu_address = "000" then interrupt_signal <= data_in(7); interrupt_enable <= data_in(6); regmux_select <= data_in(1 downto 0); elsif cpu_address = "001" then case data_in is when "00000000" => interrupt_signal <= '0'; when "00000001" => upcounter_value <= (others => '0'); when "00000010" => upcounter_latch <= upcounter_value; when "00000011" => downcounter_value <= downcounter_start; when "00010000" => regmux_select <= "00"; when "00010001" => regmux_select <= "01"; when "00010010" => regmux_select <= "10"; when "00010011" => regmux_select <= "11"; when others => end case; elsif cpu_address(2) = '1' then case regmux_select is when "00" => upcounter_value <= unsigned(regmux_updated); when "01" => upcounter_latch <= unsigned(regmux_updated); when "10" => downcounter_value <= unsigned(regmux_updated); when "11" => downcounter_start <= unsigned(regmux_updated); when others => end case; end if; end if; end if; end if; end process; end Behavioral;