socz80-de0/vhdl/timer.vhd
Sergio L. Pascual 8da567e4b6 Initial commit.
2014-09-06 02:01:39 +02:00

184 lines
8.8 KiB
VHDL

--+-----------------------------------+-------------------------------------+--
--| ___ ___ | (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;