Jump to content
  • 0

VHDL UART RX for Nexys4 DDR?


DoctorWkt

Question

8 answers to this question

Recommended Posts

I did find some UART receive code and fixed a few bugs in it. I've written a VHDL project where the UART reads a character and echoes it back. However, at present I am losing about half the characters that I type with minicom on Linux.

Attached is a Zip with the project. UART_TX_CTRL.vhd comes from the Resource Center, UART_RX_CTRL.vhd is the receive code. serialport.vhd is the top-level file to generate the Nexys4 bitstream. There are also behavioural simulation testbeds uart_tx_test.vhd and uart_rx_test.vhd.

I'm still a VHDL newbie, but if someone could help me get this work then it could be the basis for a project to put into the Resource Center. Right now I have three inferred latches, and I'm not sure what I need to do to fix them.

Any help would be much appreciated! Warren

serialport.zip

Hi Warren,

Here's a nasty/inefficient implementation of a UART that should be relatively bulletproof, as long as there is minimal noise  on the line.

 

----------------------------------------------------------------------------
--    UART_RX_CTRL.vhd -- Simple UART RX controller
----------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity UART_RX_CTRL is
    port ( UART_RX:    in   STD_LOGIC;
           CLK:        in   STD_LOGIC;
           DATA:       out  STD_LOGIC_VECTOR (7 downto 0);
           READ_DATA:  out  STD_LOGIC := '0';
           RESET_READ: in   STD_LOGIC
    );
end UART_RX_CTRL;
architecture behavioral of UART_RX_CTRL is
   constant FREQ : integer := 100000000;
   constant BAUD : integer := 9600;
   -- Needs to hold the count of 9.5 bit-times = approx 105,000
   signal   count     : unsigned(16 downto 0) := (others => '0');
   constant sample_0  : unsigned(16 downto 0) := to_unsigned( 3 * FREQ/(BAUD*2)-1,17);
   constant sample_1  : unsigned(16 downto 0) := to_unsigned( 5 * FREQ/(BAUD*2)-1,17);
   constant sample_2  : unsigned(16 downto 0) := to_unsigned( 7 * FREQ/(BAUD*2)-1,17);
   constant sample_3  : unsigned(16 downto 0) := to_unsigned( 9 * FREQ/(BAUD*2)-1,17);
   constant sample_4  : unsigned(16 downto 0) := to_unsigned(11 * FREQ/(BAUD*2)-1,17);
   constant sample_5  : unsigned(16 downto 0) := to_unsigned(13 * FREQ/(BAUD*2)-1,17);
   constant sample_6  : unsigned(16 downto 0) := to_unsigned(15 * FREQ/(BAUD*2)-1,17);
   constant sample_7  : unsigned(16 downto 0) := to_unsigned(17 * FREQ/(BAUD*2)-1,17);
   constant stop_bit  : unsigned(16 downto 0) := to_unsigned(19 * FREQ/(BAUD*2)-1,17);
   signal   byte      : std_logic_vector(7 downto 0) := (others => '0');
begin
rx_state_process : process (CLK)
   begin
      if(rising_edge(CLK)) then
         
         READ_DATA <= '0';
         case count is 
            when sample_0 => byte <= UART_RX & byte(7 downto 1);
            when sample_1 => byte <= UART_RX & byte(7 downto 1);
            when sample_2 => byte <= UART_RX & byte(7 downto 1);
            when sample_3 => byte <= UART_RX & byte(7 downto 1);
            when sample_4 => byte <= UART_RX & byte(7 downto 1);
            when sample_5 => byte <= UART_RX & byte(7 downto 1);
            when sample_6 => byte <= UART_RX & byte(7 downto 1);
            when sample_7 => byte <= UART_RX & byte(7 downto 1);
            when stop_bit =>  
               -- Send out the data when we see a valid stop bit.
               if UART_RX = '1' then 
                  DATA <= byte;
                  READ_DATA <= '1';
               end if;
            when others =>
               null;
         end case;
            
         if count = stop_bit then
            count <= (others => '0');
         elsif count = 0 then
            if UART_RX = '0' then -- Start bit just seen, so start counting
               count <= count + 1;   
            end if;
         else
             count <= count + 1;   
         end if;
      end if;
   end process;
end behavioral;

If using it in an environment where noise might be a problem you count first filter UART_RX (like denouncing a switch), but other ways are better.

The best way might be to sample the signal at five times the baud rate, and when you see four '0's in a row then wait for three sample intervals (to get to the middle of the first data bit, then sample bits every five sample intervals. It sounds a lot more complex then it is to implement, depending on if you go for a long shift register (code untested, just to illustrate the idea): 


   ... 

   process(clk)

   begin

   if rising_edge(clh) then

     if count = FREQ/BAUD/5 then

       count <= (others => '0');

       shift_reg <= UART_RX & shift_reg(shift_reg'high downto 1);

     else

       count <= count +1;

    end if;

     if shift_reg(3 downto 0) = "0000" then   -- start bit at the head of the register

      DATA(0)  <= shift_reg(7);

      DATA1)  <= shift_reg(12);

      DATA(2)  <= shift_reg(17);

      DATA(3)  <= shift_reg(22);

      DATA(4)  <= shift_reg(27);

      DATA5)  <= shift_reg(32);

      DATA(6)  <= shift_reg(37);

      DATA(7)  <= shift_reg(42);

      READ_DATA <= shift_reg(47);  -- use the stop bit to assert the data strobe :)

      shift_reg <= (others =>'1'); -- empty out the shift register.

    else

      READ_DATA <= '0';

    end if;

  end if

end process;

Link to comment
Share on other sites

Hi Warren,

Your project definitely looks pretty cool! 

I'm not aware of any UART receive code on our wiki, although we would like to get some up there in the future. For now, I would probably check out this RS232 serial interface walkthrough here (as recommended to me by another user).

Let me know if you have any more questions.

Thanks,
JColvin

Link to comment
Share on other sites

I did find some UART receive code and fixed a few bugs in it. I've written a VHDL project where the UART reads a character and echoes it back. However, at present I am losing about half the characters that I type with minicom on Linux.

Attached is a Zip with the project. UART_TX_CTRL.vhd comes from the Resource Center, UART_RX_CTRL.vhd is the receive code. serialport.vhd is the top-level file to generate the Nexys4 bitstream. There are also behavioural simulation testbeds uart_tx_test.vhd and uart_rx_test.vhd.

I'm still a VHDL newbie, but if someone could help me get this work then it could be the basis for a project to put into the Resource Center. Right now I have three inferred latches, and I'm not sure what I need to do to fix them.

Any help would be much appreciated! Warren

serialport.zip

Link to comment
Share on other sites

Hamster gave me a good tip about the inferred latches. I have fixed up the code and added more comments. Now I can transmit a text file down at 9600bps and most of the characters come back. But a few of them are being corrupted. I've checked the states in the receiver and they all seem fine. The corruption is not predictable, i.e. not every N characters are corrupted when I am blasting a text file at 9,600,8,N,1.

Attached is the latest version of the code in case someone wants to chew on it :-)

Cheers, Warren

serialport_v2.zip

Link to comment
Share on other sites

Yet more data. I created a component to make it easier to display hex data on the 7-segment digits, see attached file. Might be useful in other projects. I'm using it to display the last four characters in hex. Using this, I can see that the data is being corrupted in the UART receive code and not in the UART transmit code.

Cheers, Warren

hex_7seg.vhd

Link to comment
Share on other sites

I did find some UART receive code and fixed a few bugs in it. I've written a VHDL project where the UART reads a character and echoes it back. However, at present I am losing about half the characters that I type with minicom on Linux.

Attached is a Zip with the project. UART_TX_CTRL.vhd comes from the Resource Center, UART_RX_CTRL.vhd is the receive code. serialport.vhd is the top-level file to generate the Nexys4 bitstream. There are also behavioural simulation testbeds uart_tx_test.vhd and uart_rx_test.vhd.

I'm still a VHDL newbie, but if someone could help me get this work then it could be the basis for a project to put into the Resource Center. Right now I have three inferred latches, and I'm not sure what I need to do to fix them.

Any help would be much appreciated! Warren

serialport.zip

Hi Warren,

Here's a nasty/inefficient implementation of a UART that should be relatively bulletproof, as long as there is minimal noise  on the line.

 

----------------------------------------------------------------------------
--    UART_RX_CTRL.vhd -- Simple UART RX controller
----------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity UART_RX_CTRL is
    port ( UART_RX:    in   STD_LOGIC;
           CLK:        in   STD_LOGIC;
           DATA:       out  STD_LOGIC_VECTOR (7 downto 0);
           READ_DATA:  out  STD_LOGIC := '0';
           RESET_READ: in   STD_LOGIC
    );
end UART_RX_CTRL;
architecture behavioral of UART_RX_CTRL is
   constant FREQ : integer := 100000000;
   constant BAUD : integer := 9600;
   -- Needs to hold the count of 9.5 bit-times = approx 105,000
   signal   count     : unsigned(16 downto 0) := (others => '0');
   constant sample_0  : unsigned(16 downto 0) := to_unsigned( 3 * FREQ/(BAUD*2)-1,17);
   constant sample_1  : unsigned(16 downto 0) := to_unsigned( 5 * FREQ/(BAUD*2)-1,17);
   constant sample_2  : unsigned(16 downto 0) := to_unsigned( 7 * FREQ/(BAUD*2)-1,17);
   constant sample_3  : unsigned(16 downto 0) := to_unsigned( 9 * FREQ/(BAUD*2)-1,17);
   constant sample_4  : unsigned(16 downto 0) := to_unsigned(11 * FREQ/(BAUD*2)-1,17);
   constant sample_5  : unsigned(16 downto 0) := to_unsigned(13 * FREQ/(BAUD*2)-1,17);
   constant sample_6  : unsigned(16 downto 0) := to_unsigned(15 * FREQ/(BAUD*2)-1,17);
   constant sample_7  : unsigned(16 downto 0) := to_unsigned(17 * FREQ/(BAUD*2)-1,17);
   constant stop_bit  : unsigned(16 downto 0) := to_unsigned(19 * FREQ/(BAUD*2)-1,17);
   signal   byte      : std_logic_vector(7 downto 0) := (others => '0');
begin
rx_state_process : process (CLK)
   begin
      if(rising_edge(CLK)) then
         
         READ_DATA <= '0';
         case count is 
            when sample_0 => byte <= UART_RX & byte(7 downto 1);
            when sample_1 => byte <= UART_RX & byte(7 downto 1);
            when sample_2 => byte <= UART_RX & byte(7 downto 1);
            when sample_3 => byte <= UART_RX & byte(7 downto 1);
            when sample_4 => byte <= UART_RX & byte(7 downto 1);
            when sample_5 => byte <= UART_RX & byte(7 downto 1);
            when sample_6 => byte <= UART_RX & byte(7 downto 1);
            when sample_7 => byte <= UART_RX & byte(7 downto 1);
            when stop_bit =>  
               -- Send out the data when we see a valid stop bit.
               if UART_RX = '1' then 
                  DATA <= byte;
                  READ_DATA <= '1';
               end if;
            when others =>
               null;
         end case;
            
         if count = stop_bit then
            count <= (others => '0');
         elsif count = 0 then
            if UART_RX = '0' then -- Start bit just seen, so start counting
               count <= count + 1;   
            end if;
         else
             count <= count + 1;   
         end if;
      end if;
   end process;
end behavioral;
[/code]

If using it in an environment where noise might be a problem you count first filter UART_RX (like denouncing a switch), but other ways are better.

The best way might be to sample the signal at five times the baud rate, and when you see four '0's in a row then wait for three sample intervals (to get to the middle of the first data bit, then sample bits every five sample intervals. It sounds a lot more complex then it is to implement, depending on if you go for a long shift register (code untested, just to illustrate the idea): 

   ... 
   process(clk)
   begin
   if rising_edge(clh) then
     if count = FREQ/BAUD/5 then
       count <= (others => '0');
       shift_reg <= UART_RX & shift_reg(shift_reg'high downto 1);
     else
       count <= count +1;
     end if;
     if shift_reg(3 downto 0) = "0000" then   -- start bit at the head of the register
      DATA(0)  <= shift_reg(7);
      DATA(1)  <= shift_reg(12);
      DATA(2)  <= shift_reg(17);
      DATA(3)  <= shift_reg(22);
      DATA(4)  <= shift_reg(27);
      DATA(5)  <= shift_reg(32);
      DATA(6)  <= shift_reg(37);
      DATA(7)  <= shift_reg(42);
      READ_DATA <= shift_reg(47);  -- use the stop bit to assert the data strobe :)
      shift_reg <= (others =>'1'); -- empty out the shift register.
    else
      READ_DATA <= '0';
    end if;
  end if
end process;

Link to comment
Share on other sites

Thanks Hamster. That code is much easier to read and it works! I have tidied it up a bit and added some more comments. Attached is a working project that demonstrates the UART receive and transmit functionality on the Nexys4 (DDR). Would it be possible for Digilent to repackage this as a wiki entry on their Resource Center for the Nexys4 DDR?

Cheers & thanks all, Warren

serialport_v3.zip

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...