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

194 lines
8.4 KiB
VHDL

--+-----------------------------------+-------------------------------------+--
--| ___ ___ | (c) 2013-2014 William R Sowerbutts |--
--| ___ ___ ___ ___( _ ) / _ \ | will@sowerbutts.com |--
--| / __|/ _ \ / __|_ / _ \| | | | | |--
--| \__ \ (_) | (__ / / (_) | |_| | | A Z80 FPGA computer, just for fun |--
--| |___/\___/ \___/___\___/ \___/ | |--
--| | http://sowerbutts.com/ |--
--+-----------------------------------+-------------------------------------+--
--| UART implementation |--
--+-------------------------------------------------------------------------+--
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity uart is
generic (
clk_frequency : natural := (128 * 1000000)
);
port ( clk : in std_logic;
serial_out : out std_logic;
serial_in : in std_logic;
data_in : in std_logic_vector(7 downto 0);
data_in_load : in std_logic;
data_out : out std_logic_vector(7 downto 0);
data_out_ready : out std_logic;
bad_bit : out std_logic;
transmitter_busy : out std_logic;
can_transmit : in std_logic
);
end uart;
architecture Behavioral of uart is
-- tested at 1,000,000bps with 48MHz clock. Works (apparently).
constant rx_sample_interval : unsigned(13 downto 0) := to_unsigned(clk_frequency / (115200 * 16) - 1, 14); -- clock speed / (baud x 16) - 1 ; eg 32MHz / (9600 * 16) - 1 = 207
constant bit_duration : unsigned(13 downto 0) := to_unsigned(clk_frequency / (115200 * 1) - 1, 14); -- clock speed / baud - 1 ; eg 32MHz / 9600 - 1 = 3332
signal tx_counter : unsigned(13 downto 0) := to_unsigned(0, 14);
signal tx_shift_reg : std_logic_vector(8 downto 0) := "111111111";
signal tx_bits_left : unsigned(3 downto 0) := to_unsigned(0, 4);
signal tx_busy : std_logic;
signal rx_counter : unsigned(13 downto 0) := to_unsigned(0, 14);
signal rx_shift_reg : std_logic_vector(8 downto 0) := "000000000";
signal rx_bits_got : unsigned(3 downto 0) := to_unsigned(0, 4);
signal rx_state : unsigned(7 downto 0) := (others => '0'); -- 10 bits x 16 samples each = at least 160 states.
signal rx_out_ready : std_logic := '0';
signal data_out_buf : std_logic_vector(7 downto 0) := "00000000";
signal rx_clkin1 : std_logic := '0';
signal rx_clkin2 : std_logic := '0';
signal rx_sample1 : std_logic := '0';
signal rx_sample2 : std_logic := '0';
signal rx_sample3 : std_logic := '0';
signal rx_sample_majority : std_logic;
signal rx_badbit : std_logic := '0';
begin
-- -- receiver -- --
--
-- Incoming data is oversampled 16 times. We check three samples in the
-- middle of each bit and take a simple majority. There is provision for
-- rejecting noise where a start bit should have been. We compensate for
-- small amounts of clock drift by potentially cutting a stop bit short.
--
-- This is not dissimilar to how the AVR USART receiver works.
--
data_out_ready <= rx_out_ready;
bad_bit <= rx_badbit;
data_out <= data_out_buf;
rx_sample_majority <= (rx_sample1 and rx_sample2) or (rx_sample1 and rx_sample3) or (rx_sample2 and rx_sample3); -- simple majority wins
receiver: process(clk)
begin
if rising_edge(clk) then
-- Bring serial_in into our clock domain
rx_clkin1 <= serial_in;
rx_clkin2 <= rx_clkin1; -- rx_clkin2 should now be safe to use.
-- We latch the incoming serial data at full clock speed (NOT divided down)
rx_sample1 <= rx_clkin2;
rx_out_ready <= '0';
-- bad bit
rx_badbit <= rx_badbit;
-- clock divider
rx_counter <= rx_counter + 1;
if rx_counter = rx_sample_interval then
rx_counter <= (others => '0');
if rx_state = "00000000" then
-- line is in the idle state, we're waiting for a start bit!
-- the anticipation is killing me.
if rx_sample1 = '0' then
rx_state <= "00000001"; -- and we're off!
rx_counter <= (others => '0');
end if;
elsif rx_state = "10011010" then
-- wait for the line to be idle. we don't leave this state until the serial line
-- goes high (it should be already, because we should be in mid stop bit).
if rx_sample1 = '1' then
rx_state <= "00000000";
end if;
else
-- we're in the normal bit reception pattern
rx_state <= rx_state + 1;
-- rx_sample1 contains the incoming serial data, latched
rx_sample3 <= rx_sample2;
rx_sample2 <= rx_sample1;
-- when we have the three middle samples, update the shift register
if rx_state(3 downto 0) = "1001" then
rx_shift_reg <= rx_sample_majority & rx_shift_reg(rx_shift_reg'length-1 downto 1);
-- false start bit noise rejection: if we read the start bit as a logical 1, start over again.
if (rx_state(7 downto 4) = "0000") and (rx_sample_majority = '1') then
rx_badbit <= '1';
rx_state <= "00000000";
end if;
-- check stop bit framing and alert CPU if valid byte received
if (rx_state(7 downto 4) = "1001") then
if (rx_sample_majority = '1') then
data_out_buf <= rx_shift_reg(8 downto 1);
rx_out_ready <= '1';
rx_badbit <= '0';
else
rx_badbit <= '1';
end if;
-- if line is high we can skip waiting for the line to go idle which buys us a little more tolerance for clock drift.
if rx_sample1 = '1' then
rx_state <= "00000000";
end if;
end if;
end if;
end if;
end if;
end if;
end process;
-- -- transmitter -- --
--
-- just clock out the bits, damn it.
--
serial_out <= tx_shift_reg(0); -- we always output the bottom bit of the shift register.
transmitter_busy <= tx_busy; -- or data_in_load;
transmitter: process(clk)
begin
if rising_edge(clk) then
tx_busy <= '1';
if tx_bits_left = 0 then
-- idle
if data_in_load = '1' then
tx_shift_reg <= data_in & '0'; -- data bits, start bit
tx_bits_left <= to_unsigned(10, 4); -- total ten bits to transmit including stop bit
tx_counter <= (others => '0'); -- reset counter
else
if can_transmit = '0' then
tx_busy <= '1';
else
tx_busy <= '0';
end if;
end if;
else
-- busy
if (tx_counter = 0) and (tx_bits_left = 10) and (can_transmit = '0') then
-- do nothing, we're waiting for our peer to indicate that we can transmit
tx_counter <= (others => '0');
else
tx_counter <= tx_counter + 1;
if tx_counter = bit_duration then
-- shift out the next bit
tx_shift_reg <= '1' & tx_shift_reg(8 downto 1); -- stop bit and line idle state are both 1 so shift that in the top
tx_counter <= (others => '0'); -- reset counter
tx_bits_left <= tx_bits_left - 1;
end if;
end if;
end if;
end if;
end process;
end Behavioral;