Now sampling 3 times per bit and shifting the clock accordingly. Had to split the rx and tx clocks.
		
			
				
	
	
		
			298 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			VHDL
		
	
	
	
	
	
			
		
		
	
	
			298 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			VHDL
		
	
	
	
	
	
| library ieee;
 | |
| use ieee.std_logic_1164.all;
 | |
| use ieee.numeric_std.all;
 | |
| 
 | |
| entity uart is
 | |
|   generic
 | |
|   (
 | |
|     baudrate : in natural := 1_000_000
 | |
|   );
 | |
| 
 | |
|   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 := baudrate;
 | |
|   constant SYSFREQ: natural := 100_000_000;
 | |
|   constant RXCLKCNT: natural := SYSFREQ / BAUD / 3;
 | |
|   constant TXCLKCNT: natural := SYSFREQ / BAUD;
 | |
| 
 | |
|   type Fifo is array(integer range <>) of std_logic_vector(7 downto 0);
 | |
| 
 | |
|   ---- all dffs below
 | |
| 
 | |
|   signal rxfifo : Fifo(0 to 7);
 | |
|   signal txfifo : Fifo(3 downto 0);
 | |
| 
 | |
|   signal rxcnt  : unsigned(3 downto 0);
 | |
|   signal txcnt  : unsigned(3 downto 0);
 | |
| 
 | |
|   signal rxclk : unsigned(15 downto 0);  -- possibly down to 1525 baud
 | |
|   signal txclk : unsigned(15 downto 0);  -- possibly down to 1525 baud
 | |
|   signal rxen : std_logic;  -- 1 when rx state machine can do stuff
 | |
|   signal txen : std_logic;  -- 1 when tx state machine 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);
 | |
| 
 | |
|   signal clockoffset : unsigned(15 downto 0);
 | |
|   signal rxpinprev: std_logic_vector(1 downto 0);
 | |
|   signal rxsamplecount: unsigned(2 downto 0);
 | |
| begin
 | |
| 
 | |
|   -- rx process
 | |
|   -- drives rxstate, rxpushed, rxshift, rxshiftcnt, rxfifo, clockoffset
 | |
|   process(clk, rst)
 | |
|   begin
 | |
|     if rst = '1' then
 | |
|       rxstate <= IDLE;
 | |
|       rxpushed <= '0';
 | |
|       rxshift <= x"00";
 | |
|       rxshiftcnt <= "0000";
 | |
|       clockoffset <= (others => '0');
 | |
|       rxpinprev <= (others => '0');
 | |
|       rxsamplecount <= (others => '0');
 | |
| 
 | |
|       for i in rxfifo'low to rxfifo'high loop
 | |
|         rxfifo(i) <= x"00";
 | |
|       end loop;
 | |
|     elsif rising_edge(clk) then
 | |
|       if rxen = '1' and unsigned(rxsamplecount) < 2 then
 | |
|         rxpinprev(to_integer(unsigned(rxsamplecount))) <= rx_pin;
 | |
|         rxsamplecount <= rxsamplecount + 1;
 | |
|       end if;
 | |
| 
 | |
|       if rxen = '1' and unsigned(rxsamplecount) = 2 then
 | |
|         rxpushed <= '0';
 | |
|         rxsamplecount <= (others => '0');
 | |
| 
 | |
|         if rxpinprev(0) /= rxpinprev(1) then
 | |
|           -- we are too fast, slow down
 | |
|           clockoffset <= clockoffset - RXCLKCNT / 2;
 | |
|         elsif rx_pin /= rxpinprev(1) then
 | |
|           -- we are too slow, speed up
 | |
|           clockoffset <= clockoffset + RXCLKCNT / 2;
 | |
|         end if;
 | |
| 
 | |
|         -- use rxpinprev(1) as the value
 | |
|         case rxstate is
 | |
|           when IDLE =>
 | |
|             if rxpinprev(1) = '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 rxpinprev(1) = '1' then -- all good, push away
 | |
|                 for i in rxfifo'high - 1 downto rxfifo'low 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 <= rxpinprev(1) & rxshift(7 downto 1);
 | |
|               rxshiftcnt <= rxshiftcnt + 1;
 | |
|             end if;
 | |
|         end case;
 | |
|       end if;
 | |
|     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) then
 | |
|       if txen = '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 if;
 | |
|   end process;
 | |
| 
 | |
|   process(clk, rst)  -- drives rxclk, rxen
 | |
|   begin
 | |
|     if rst = '1' then
 | |
|       rxclk <= (others => '0');
 | |
|       rxen <= '0';
 | |
|     elsif rising_edge(clk) then
 | |
|       if unsigned(rxclk + clockoffset) = RXCLKCNT - 1 then
 | |
|         rxclk <=  0 - clockoffset;
 | |
|         rxen <= '1';
 | |
|       else
 | |
|         rxclk <= rxclk + 1;
 | |
|         rxen <= '0';
 | |
|       end if;
 | |
|     end if;
 | |
|   end process;
 | |
| 
 | |
|   process(clk, rst)  -- drives txclk, rxen
 | |
|   begin
 | |
|     if rst = '1' then
 | |
|       txclk <= (others => '0');
 | |
|       txen <= '0';
 | |
|     elsif rising_edge(clk) then
 | |
|       if unsigned(txclk) = TXCLKCNT - 1 then
 | |
|         txclk <=  (others => '0');
 | |
|         txen <= '1';
 | |
|       else
 | |
|         txclk <= txclk + 1;
 | |
|         txen <= '0';
 | |
|       end if;
 | |
|     end if;
 | |
|   end process;
 | |
| 
 | |
|   process(clk, rst)  -- drives dout, rxcnt, txcnt, txfifo
 | |
|     variable txn: unsigned(3 downto 0);
 | |
|     variable rxn: unsigned(3 downto 0);
 | |
| 
 | |
|     variable txpopdone  : std_logic := '0'; -- latch
 | |
|     variable rxpushdone : std_logic := '0'; -- latch
 | |
|   begin
 | |
| 
 | |
|     if rst = '1' then
 | |
|       for i in txfifo'low to txfifo'high loop
 | |
|         txfifo(i) <= x"00";
 | |
|       end loop;
 | |
| 
 | |
|       rxcnt <= (others => '0');
 | |
|       txcnt <= (others => '0');
 | |
| 
 | |
|       txpopdone := '0';
 | |
|       rxpushdone := '0';
 | |
|     elsif rising_edge(clk) then
 | |
|       rxn := rxcnt;
 | |
|       txn := txcnt;
 | |
| 
 | |
|       dout <= x"0000";
 | |
| 
 | |
|       -- Fifo grooming
 | |
|       if txpopped = '1' then
 | |
|         if txpopdone = '0' then
 | |
|           -- shift our fifo to the right
 | |
|           for i in txfifo'high to txfifo'low - 1 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) <= txfifo'low 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;
 |