Files
SharpMZ_MiSTer/rtl/i8254/i8254_counter.vhd
birdybro b32c9615e4 Restructure folders
Proposal to restructure all the files and folders to keep things organized and similar to other cores.
2022-06-24 14:04:27 -06:00

524 lines
21 KiB
VHDL

---------------------------------------------------------------------------------------------------------
--
-- Name: i8254_counter.vhd
-- Created: November 2018
-- Author(s): Philip Smart
-- Description: Sharp MZ series i8254 Timer
-- This module emulates the Intel i8254 Programmable Interval Timer.
--
-- Credits: Based on Verilog pit_counter by Aleksander Osman, 2014.
-- Copyright: (c) 2018 Philip Smart <philip.smart@net2net.org>
--
-- History: November 2018 - Initial write.
--
---------------------------------------------------------------------------------------------------------
-- This source file is free software: you can redistribute it and-or modify
-- it under the terms of the GNU General Public License as published
-- by the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This source file is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http:--www.gnu.org-licenses->.
---------------------------------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity i8254_counter is
Port (
CLK : in std_logic;
RESET : in std_logic;
--
DATA_IN : in std_logic_vector(7 downto 0);
DATA_OUT : out std_logic_vector(7 downto 0);
WRITE : in std_logic;
READ : in std_logic;
CTRL_MODE_EN : in std_logic;
LATCH_COUNT_EN : in std_logic;
LATCH_STATUS_EN : in std_logic;
--
CTR_CLK : in std_logic;
CTR_GATE : in std_logic;
CTR_OUT : out std_logic
);
end i8254_counter;
architecture Behavioral of i8254_counter is
subtype LSB is integer range 7 downto 0;
subtype MSB is integer range 15 downto 8;
signal MODE : std_logic_vector(2 downto 0);
signal RW_MODE : std_logic_vector(1 downto 0);
signal BCD : std_logic;
signal REGISTER_IN : std_logic_vector(15 downto 0);
signal REGISTER_OUT : std_logic_vector(15 downto 0);
signal REGISTER_OUT_LATCHED : std_logic;
signal NULL_COUNTER : std_logic;
signal MSB_WRITE : std_logic;
signal MSB_READ : std_logic;
signal STATUS : std_logic_vector(7 downto 0);
signal STATUS_LATCHED : std_logic;
--
signal CLOCK_LAST : std_logic;
signal CLOCK_PULSE : std_logic;
signal GATE_LAST : std_logic;
signal GATE_SAMPLED : std_logic;
signal TRIGGER : std_logic;
signal TRIGGER_SAMPLED : std_logic;
signal WRITTEN : std_logic;
signal LOADED : std_logic;
signal CTR_OUTi : std_logic;
--
signal MODE0 : std_logic;
signal MODE1 : std_logic;
signal MODE2 : std_logic;
signal MODE3 : std_logic;
signal MODE4 : std_logic;
signal MODE5 : std_logic;
signal LOAD : std_logic;
signal LOAD_MODE0 : std_logic;
signal LOAD_MODE1 : std_logic;
signal LOAD_MODE2 : std_logic;
signal LOAD_MODE3 : std_logic;
signal LOAD_MODE4 : std_logic;
signal LOAD_MODE5 : std_logic;
signal LOAD_EVEN : std_logic;
signal ENABLE_MODE0 : std_logic;
signal ENABLE_MODE1 : std_logic;
signal ENABLE_MODE2 : std_logic;
signal ENABLE_MODE3 : std_logic;
signal ENABLE_MODE4 : std_logic;
signal ENABLE_MODE5 : std_logic;
signal ENABLE_DOUBLE : std_logic;
signal ENABLE : std_logic;
signal BCD_DIGIT_1 : std_logic_vector(3 downto 0);
signal BCD_DIGIT_2 : std_logic_vector(3 downto 0);
signal BCD_DIGIT_3 : std_logic_vector(3 downto 0);
signal COUNTER_MINUS_1 : std_logic_vector(15 downto 0);
signal COUNTER_MINUS_2 : std_logic_vector(15 downto 0);
signal COUNTER : std_logic_vector(15 downto 0);
begin
-- Control register settings. A write to the control register sets up the mode of this counter, wether it
-- uses BCD or 16 bit binary and how the data is accessed, ie. LSB, MSB or both.
--
process(RESET, CLK)
begin
if RESET = '1' then
MODE <= "010";
BCD <= '0';
RW_MODE <= "01";
elsif CLK'event and CLK = '1' then
if CTRL_MODE_EN = '1' then
MODE <= DATA_IN(3 downto 1);
BCD <= DATA_IN(0);
RW_MODE <= DATA_IN(5 downto 4);
end if;
end if;
end process;
-- Staging counter loading. Depending on the mode, the byte is stored in the LSB, NSB or according to the write flag
-- for 16 bit mode. The staging counter is used to load the main counter.
--
process(RESET, CLK)
begin
if RESET = '1' then
REGISTER_IN <= (others => '0');
elsif CLK'event and CLK = '1' then
if CTRL_MODE_EN = '1' then
REGISTER_IN <= (others => '0');
elsif WRITE = '1' and RW_MODE = "11" and MSB_WRITE = '0' then
REGISTER_IN(LSB) <= DATA_IN;
elsif WRITE = '1' and RW_MODE = "11" and MSB_WRITE = '1' then
REGISTER_IN(MSB) <= DATA_IN;
elsif WRITE = '1' and RW_MODE = "01" then
REGISTER_IN(LSB) <= DATA_IN;
elsif WRITE = '1' and RW_MODE = "10" then
REGISTER_IN(MSB) <= DATA_IN;
end if;
end if;
end process;
-- Store the counter contents on every clock until a latch request is made, then we suspend storing
-- until data is read.
--
process(RESET, CLK)
begin
if RESET = '1' then
REGISTER_OUT <= (others => '0');
elsif CLK'event and CLK = '1' then
-- Store each clock cycle, stop on the clock between LATCH going active and REGISTER_OUT_LATCHED going active.
--
if LATCH_COUNT_EN = '1' and REGISTER_OUT_LATCHED = '0' then
REGISTER_OUT <= COUNTER(15 downto 0);
elsif REGISTER_OUT_LATCHED = '0' then
REGISTER_OUT <= COUNTER(15 downto 0);
end if;
end if;
end process;
-- Set the output latched signal if LATCH_COUNT_EN goes high, this will stop the storing of the counter until
-- output latch is cleared, which can be done by a control register access or a 1/2 byte read depending on mode.
--
process(RESET, CLK)
begin
if RESET = '1' then
REGISTER_OUT_LATCHED <= '0';
elsif CLK'event and CLK = '1' then
if CTRL_MODE_EN = '1' then
REGISTER_OUT_LATCHED <= '0';
elsif LATCH_COUNT_EN = '1' then
REGISTER_OUT_LATCHED <= '1';
elsif (READ = '1' and (RW_MODE /= "11" or MSB_READ = '1')) then
REGISTER_OUT_LATCHED <= '0';
end if;
end if;
end process;
-- Status flag null count - indicates if the counter can be read (0) or it is being loaded (1).
--
process(RESET, CLK)
begin
if RESET = '1' then
NULL_COUNTER <= '0';
elsif CLK'event and CLK = '1' then
if CTRL_MODE_EN = '1' then
NULL_COUNTER <= '1';
elsif (WRITE = '1' and (RW_MODE /= "11" or MSB_WRITE = '1')) then
NULL_COUNTER <= '1';
elsif LOAD = '1' then
NULL_COUNTER <= '0';
end if;
end if;
end process;
-- Double byte handling for 16 bit load and fetch. An access to the control register resets the flag,
-- but on each write or read it gets toggled. The flag indicates wether the LSB(0) or MSB(1) is being read or writted.
--
process(RESET, CLK)
begin
if RESET = '1' then
MSB_WRITE <= '0';
MSB_READ <= '0';
elsif CLK'event and CLK = '1' then
if CTRL_MODE_EN = '1' then
MSB_WRITE <= '0';
MSB_READ <= '0';
elsif WRITE = '1' and RW_MODE = "11" then
MSB_WRITE <= not MSB_WRITE;
elsif READ = '1' and RW_MODE = "11" then
MSB_READ <= not MSB_READ;
end if;
end if;
end process;
-- Status register, contains the Output pin value, the state on the counter being read (1 = can be read) and the programmed
-- mode of the counter. The current values are latched during the clock between the LATCH_STATUS_EN going active and the latched
-- signal going active.
--
process(RESET, CLK)
begin
if RESET = '1' then
STATUS <= (others => '0');
elsif CLK'event and CLK = '1' then
if LATCH_STATUS_EN = '1' and STATUS_LATCHED = '0' then
STATUS <= CTR_OUTi & NULL_COUNTER & RW_MODE & MODE & BCD;
end if;
end if;
end process;
-- Set the status latch signal if the LATCH_STATUS_EN goes active. Any read or control mode access resets the flag.
--
process(RESET, CLK)
begin
if RESET = '1' then
STATUS_LATCHED <= '0';
elsif CLK'event and CLK = '1' then
if CTRL_MODE_EN = '1' then
STATUS_LATCHED <= '0';
elsif LATCH_STATUS_EN = '1' then
STATUS_LATCHED <= '1';
elsif READ = '1' then
STATUS_LATCHED <= '0';
end if;
end if;
end process;
-- Set the internal counter signals according to the output clock and gate.
--
process(RESET, CLK)
begin
if RESET = '1' then
CLOCK_PULSE <= '0';
CLOCK_LAST <= '0';
GATE_LAST <= '1';
GATE_SAMPLED <= '0';
TRIGGER <= '0';
TRIGGER_SAMPLED <= '0';
elsif CLK'event and CLK = '1' then
CLOCK_LAST <= CTR_CLK;
GATE_LAST <= CTR_GATE;
if CLOCK_LAST = '1' and CTR_CLK = '0' then
CLOCK_PULSE <= '1';
else
CLOCK_PULSE <= '0';
end if;
if CLOCK_LAST = '0' and CTR_CLK = '1' then
GATE_SAMPLED <= CTR_GATE;
TRIGGER_SAMPLED <= TRIGGER;
end if;
if GATE_LAST = '0' and CTR_GATE = '1' then
TRIGGER <= '1';
elsif CLOCK_LAST = '0' and CTR_CLK = '1' then
TRIGGER <= '0';
end if;
end if;
end process;
-- Set the counter output according to programmed mode and events.
--
process(RESET, CLK)
begin
if RESET = '1' then
CTR_OUTi <= '1';
elsif CLK'event and CLK = '1' then
if CTRL_MODE_EN = '1' then
if MODE0 = '1' then
CTR_OUTi <= '0';
elsif MODE1 = '1' or MODE2 = '1' or MODE3 = '1' or MODE4 = '1' or MODE5 = '1' then
CTR_OUTi <= '1';
end if;
elsif MODE0 = '1' then
if WRITE = '1' and RW_MODE = "11" and MSB_WRITE = '0' then
CTR_OUTi <= '0';
elsif WRITTEN = '1' then
CTR_OUTi <= '0';
elsif COUNTER = "0000000000000001" and ENABLE = '1' then
CTR_OUTi <= '1';
end if;
elsif MODE1 = '1' then
if LOAD = '1' then
CTR_OUTi <= '0';
elsif COUNTER = "0000000000000001" and ENABLE = '1' then
CTR_OUTi <= '1';
end if;
elsif MODE2 = '1' then
if CTR_GATE = '0' then
CTR_OUTi <= '1';
elsif COUNTER = "0000000000000010" and ENABLE = '1' then
CTR_OUTi <= '0';
elsif LOAD = '1' then
CTR_OUTi <= '1';
end if;
elsif MODE3 = '1' then
if CTR_GATE = '0' then
CTR_OUTi <= '1';
elsif LOAD = '1' and COUNTER = "000000000000010" and CTR_OUTi = '1' and REGISTER_IN(0) = '0' then
CTR_OUTi <= '0';
elsif LOAD = '1' and COUNTER = "000000000000000" and CTR_OUTi = '1' and REGISTER_IN(0) = '1' then
CTR_OUTi <= '0';
elsif LOAD = '1' then
CTR_OUTi <= '1';
end if;
elsif MODE4 = '1' then
if LOAD = '1' then
CTR_OUTi <= '1';
elsif COUNTER = "0000000000000010" and ENABLE = '1' then
CTR_OUTi <= '0';
elsif COUNTER = "0000000000000001" and ENABLE = '1' then
CTR_OUTi <= '1';
end if;
elsif MODE5 = '1' then
if COUNTER = "0000000000000010" and ENABLE = '1' then
CTR_OUTi <= '0';
elsif COUNTER = "0000000000000001" and ENABLE = '1' then
CTR_OUTi <= '1';
end if;
end if;
end if;
end process;
-- Setup flags to indicate if the counter has been written to or loaded. These flags then determine loading operation
-- of the staging counter into the counter.
--
process(RESET, CLK)
begin
if RESET = '1' then
WRITTEN <= '0';
LOADED <= '0';
elsif CLK'event and CLK = '1' then
if CTRL_MODE_EN = '1' then
WRITTEN <= '0';
elsif WRITE = '1' and RW_MODE /= "11" then
WRITTEN <= '1';
elsif WRITE = '1' and RW_MODE = "11" and MSB_WRITE = '1' then
WRITTEN <= '1';
elsif LOAD = '1' then
WRITTEN <= '0';
end if;
if CTRL_MODE_EN = '1' then
LOADED <= '0';
elsif LOAD = '1' then
LOADED <= '1';
end if;
end if;
end process;
-- Process to present the requested data, according to mode, to the uC. The data is latched for timing delay to allow the uC
-- more time to read the byte.
--
process(RESET, CLK)
begin
if RESET = '1' then
DATA_OUT <= (others => '0');
elsif CLK'event and CLK = '1' then
if STATUS_LATCHED = '1' then
DATA_OUT <= STATUS;
elsif RW_MODE = "11" and MSB_READ = '0' then
DATA_OUT <= REGISTER_OUT(LSB);
elsif RW_MODE = "11" and MSB_READ = '1' then
DATA_OUT <= REGISTER_OUT(MSB);
elsif RW_MODE = "01" then
DATA_OUT <= REGISTER_OUT(LSB);
else
DATA_OUT <= REGISTER_OUT(MSB);
end if;
end if;
end process;
-- Load up the primary counter according to the programmed mode and load signals coming from the uC.
--
process(RESET, CLK)
begin
if RESET = '1' then
COUNTER <= (others => '1');
elsif CLK'event and CLK = '1' then
if LOAD_EVEN = '1' then
COUNTER <= REGISTER_IN(15 downto 1) & '0';
elsif LOAD = '1' then
COUNTER <= REGISTER_IN;
elsif ENABLE_DOUBLE = '1' then
COUNTER <= COUNTER_MINUS_2;
elsif ENABLE = '1' then
COUNTER <= COUNTER_MINUS_1;
end if;
end if;
end process;
-- Quick reference signals to indicate programmed mode.
--
MODE0 <= '1' when MODE = "000"
else '0';
MODE1 <= '1' when MODE = "001"
else '0';
MODE2 <= '1' when MODE(1 downto 0) = "10"
else '0';
MODE3 <= '1' when MODE(1 downto 0) = "11"
else '0';
MODE4 <= '1' when MODE = "100"
else '0';
MODE5 <= '1' when MODE = "101"
else '0';
-- Quick reference signals to indicate a load is required for a given mode.
--
LOAD_MODE0 <= '1' when MODE0 = '1' and WRITTEN = '1'
else '0';
LOAD_MODE1 <= '1' when MODE1 = '1' and WRITTEN = '1' and TRIGGER_SAMPLED = '1'
else '0';
LOAD_MODE2 <= '1' when MODE2 = '1' and (WRITTEN = '1' or TRIGGER_SAMPLED = '1' or (LOADED = '1' and GATE_SAMPLED = '1' and COUNTER = "0000000000000001"))
else '0';
LOAD_MODE3 <= '1' when MODE3 = '1' and (WRITTEN = '1' or TRIGGER_SAMPLED = '1' or (LOADED = '1' and GATE_SAMPLED = '1' and ((COUNTER = "0000000000000010" and (REGISTER_IN(0) = '0' or CTR_OUTi = '0')) or (COUNTER = "0000000000000000" and REGISTER_IN(0) = '1' and CTR_OUTi = '1'))))
else '0';
LOAD_MODE4 <= '1' when MODE4 = '1' and WRITTEN = '1'
else '0';
LOAD_MODE5 <= '1' when MODE5 = '1' and (WRITTEN = '1' or LOADED = '1') and TRIGGER_SAMPLED = '1'
else '0';
-- Quick reference signals to indicate a programmed mode can be enabled and set running.
--
ENABLE_MODE0 <= '1' when MODE0 = '1' and GATE_SAMPLED = '1' and MSB_WRITE = '0'
else '0';
ENABLE_MODE1 <= '1' when MODE1 = '1'
else '0';
ENABLE_MODE2 <= '1' when MODE2 = '1' and GATE_SAMPLED = '1'
else '0';
ENABLE_MODE3 <= '1' when MODE3 = '1' and GATE_SAMPLED = '1'
else '0';
ENABLE_MODE4 <= '1' when MODE4 = '1' and GATE_SAMPLED = '1'
else '0';
ENABLE_MODE5 <= '1' when MODE5 = '1'
else '0';
-- Signals to indicate the type of data to be loaded into the primary counter according to programmed mode.
--
LOAD <= '1' when CLOCK_PULSE = '1' and (LOAD_MODE0 = '1' or LOAD_MODE1 = '1' or LOAD_MODE2 = '1' or LOAD_MODE3 = '1' or LOAD_MODE4 = '1' or LOAD_MODE5 = '1')
else '0';
LOAD_EVEN <= '1' when LOAD = '1' and MODE3 = '1'
else '0';
ENABLE <= '1' when LOAD = '0' and LOADED = '1' and CLOCK_PULSE = '1' and (ENABLE_MODE0 = '1' or ENABLE_MODE1 = '1' or ENABLE_MODE2 = '1' or ENABLE_MODE4 = '1' or ENABLE_MODE5 = '1')
else '0';
ENABLE_DOUBLE<= '1' when LOAD = '0' and LOADED = '1' and CLOCK_PULSE = '1' and ENABLE_MODE3 = '1'
else '0';
-- BCD logic. Calculate each digit to ease the main
BCD_DIGIT_3 <= COUNTER(15 downto 12) - X"1";
BCD_DIGIT_2 <= COUNTER(11 downto 8) - X"1";
BCD_DIGIT_1 <= COUNTER(7 downto 4) - X"1";
-- Count down of the primary counter, 1 clock at a time. If we are in BCD mode, adjust count to reflect the BCD value, otherwise make
-- a normal binary countdown.
--
COUNTER_MINUS_1 <= X"9999" when BCD = '1' and COUNTER = X"0000"
else
BCD_DIGIT_3 & X"999" when BCD = '1' and COUNTER(11 downto 0) = X"000"
else
COUNTER(15 downto 12) & BCD_DIGIT_2 & X"99" when BCD = '1' and COUNTER(7 downto 0) = X"00"
else
COUNTER(15 downto 8) & BCD_DIGIT_1 & X"9" when BCD = '1' and COUNTER(3 downto 0) = X"0"
else
COUNTER - X"0001";
-- Count down evenly. Same as above but we count down 2 clocks at a time.
--
COUNTER_MINUS_2 <= X"9998" when BCD = '1' and COUNTER = X"0000"
else
BCD_DIGIT_3 & X"998" when BCD = '1' and COUNTER(11 downto 0) = X"000"
else
COUNTER(15 downto 12) & BCD_DIGIT_2 & X"98" when BCD = '1' and COUNTER(7 downto 0) = X"00"
else
COUNTER(15 downto 8) & BCD_DIGIT_1 & X"8" when BCD = '1' and COUNTER(3 downto 0) = X"0"
else
COUNTER - X"0002";
-- Counter output.
--
CTR_OUT <= CTR_OUTi;
end Behavioral;