194 lines
8.4 KiB
VHDL
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;
|
|
|