synth/uart/uart.vhdl
2021-02-17 13:20:30 -08:00

247 lines
6.0 KiB
VHDL

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity uart is
port
(
clk : in std_logic;
rst : in std_logic;
-- hardware
rx_pin : in std_logic;
tx_pin : out std_logic;
-- bus interface
we : in std_logic;
re : in std_logic;
addr : in std_logic_vector(15 downto 0);
din : in std_logic_vector(15 downto 0);
dout : out std_logic_vector(15 downto 0)
);
end uart;
--
-- Mem layout:
-- 0x00: 8-bit read/write
-- 0x02: flags: [rxne, txe]
-- Rx FIFO 4 bytes long
-- Tx FIFO 4 bytes long
-- Mnemonic: receive from the left, transmit to the right
architecture Behavioral of uart is
constant BAUD: positive := 115_200;
constant SYSFREQ: natural := 100_000_000;
constant CLKCNT: natural := SYSFREQ / BAUD;
type Fifo is array(integer range <>) of std_logic_vector(7 downto 0);
---- all dffs below
signal rxfifo : Fifo(0 to 3);
signal txfifo : Fifo(3 downto 0);
signal rxcnt : unsigned(2 downto 0);
signal txcnt : unsigned(2 downto 0);
signal sysclk : unsigned(9 downto 0); -- up to 868 counts
signal uarten : std_logic; -- 1 when state machines can do stuff
type RxState_t is (IDLE, SHIFT_IN);
type TxState_t is (IDLE, SHIFT_OUT);
signal rxstate: RxState_t;
signal txstate: TxState_t;
signal txpopped : std_logic;
signal rxpushed : std_logic;
signal txshift : std_logic_vector(7 downto 0);
signal rxshift : std_logic_vector(7 downto 0);
signal txshiftcnt : unsigned(3 downto 0);
signal rxshiftcnt : unsigned(3 downto 0);
begin
-- rx process
-- drives rxstate, rxpushed, rxshift, rxshiftcnt, rxfifo
process(clk, rst)
begin
if rst = '1' then
rxstate <= IDLE;
rxpushed <= '0';
rxshift <= x"00";
rxshiftcnt <= "0000";
for i in 0 to 3 loop
rxfifo(i) <= x"00";
end loop;
elsif rising_edge(clk) and uarten = '1' then
rxpushed <= '0';
case rxstate is
when IDLE =>
if rx_pin = '0' then -- start bit!! (hopefully)
rxshift <= x"00";
rxshiftcnt <= "0000";
rxstate <= SHIFT_IN;
end if;
when SHIFT_IN =>
if rxshiftcnt = 8 then
-- by now we should be seeing the stop bit, check
if rx_pin = '1' then -- all good, push away
for i in 2 downto 0 loop -- right to left
rxfifo(i + 1) <= rxfifo(i);
end loop;
rxfifo(0) <= rxshift;
rxpushed <= '1';
end if;
rxstate <= IDLE; -- either way, we're done
else
rxshift <= rx_pin & rxshift(7 downto 1);
rxshiftcnt <= rxshiftcnt + 1;
end if;
end case;
end if;
end process;
-- tx process
-- drives txstate, txpopped, txshift, txshiftcnt, tx_pin
process(clk, rst)
begin
if rst = '1' then
txstate <= IDLE;
txpopped <= '0';
txshift <= x"00";
txshiftcnt <= "0000";
tx_pin <= '1';
elsif rising_edge(clk) and uarten = '1' then
txpopped <= '0';
case txstate is
when IDLE =>
if txcnt > 0 then
txshiftcnt <= "0000";
tx_pin <= '0'; -- start bit
txstate <= SHIFT_OUT;
txshift <= txfifo(0);
txpopped <= '1';
else
tx_pin <= '1';
end if;
when SHIFT_OUT =>
if txshiftcnt = 8 then
tx_pin <= '1';
txstate <= IDLE;
else
txshiftcnt <= txshiftcnt + 1;
tx_pin <= txshift(0);
txshift(6 downto 0) <= txshift(7 downto 1);
end if;
end case;
end if;
end process;
process(clk, rst) -- drives sysclk, uarten
begin
if rst = '1' then
sysclk <= to_unsigned(0, 10);
uarten <= '0';
elsif rising_edge(clk) then
if sysclk = to_unsigned(CLKCNT - 1, 10) then
sysclk <= to_unsigned(0, 10);
uarten <= '1';
else
sysclk <= sysclk + 1;
uarten <= '0';
end if;
end if;
end process;
process(clk, rst) -- drives dout, rxcnt, txcnt, txfifo
variable txn: unsigned(2 downto 0);
variable rxn: unsigned(2 downto 0);
variable txpopdone : std_logic := '0'; -- latch
variable rxpushdone : std_logic := '0'; -- latch
begin
rxn := rxcnt;
txn := txcnt;
if rst = '1' then
for i in 0 to 3 loop
txfifo(i) <= x"00";
end loop;
rxcnt <= "000";
txcnt <= "000";
txpopdone := '0';
rxpushdone := '0';
elsif rising_edge(clk) then
dout <= x"0000";
-- Fifo grooming
if txpopped = '1' then
if txpopdone = '0' then
-- shift our fifo to the right
for i in 0 to 2 loop -- 0 to 2 is right to left
txfifo(i) <= txfifo(i + 1);
end loop;
txpopdone := '1';
txn := txcnt - 1;
end if;
else
txpopdone := '0';
end if;
if rxpushed = '1' then
if rxpushdone = '0' then
-- our fifo was already moved, just update rxn
if rxcnt < 4 then
rxn := rxcnt + 1;
end if;
rxpushdone := '1';
end if;
else
rxpushdone := '0';
end if;
txcnt <= txn;
rxcnt <= rxn;
-- logic here
case addr is
when x"0000" =>
if we = '1' then
if to_integer(txn) < 4 then
txfifo(to_integer(txn)) <= din(7 downto 0);
txcnt <= txn + 1;
end if;
elsif re = '1' then
if to_integer(rxn) > 0 then
dout(7 downto 0) <= rxfifo(to_integer(rxn) - 1);
rxcnt <= rxn - 1;
end if;
end if;
when x"0002" =>
if to_integer(rxn) > 0 then
dout(1) <= '1';
else
dout(1) <= '0';
end if;
if to_integer(txn) = 0 then
dout(0) <= '1';
else
dout(0) <= '0';
end if;
when others =>
-- nada
end case;
end if;
end process;
end Behavioral;