Adrian Adamczyk
Adrian Adamczyk

Reputation: 3070

Self implemented UART in VHDL always skips second character

I am learning VHDL right now and I tried to implement UART (1 start bit, 8 data bits, 1 stop bit) to periodically send a hardcoded string.

Everything works as expected - I receive string every 1 second. However, there is no second character.

No matter how long the string is, which character it is. I checked this fact on a oscilloscope and there is no waveform for this particular character. 1 start bit, 8 bits for first character, stop bit, start bit and 8 bits for third character, not the second one.

Following code is for 10 MHz clock divided to send with ~38 400 bits per second, I also tried with 9600 bits per second, both the same problem.

I'm using Altera MAX10 dev board: http://maximator-fpga.org/

Short video how it works: https://gfycat.com/JoyousIlliterateGuillemot

UART.vhd:

LIBRARY ieee;
    USE ieee.std_logic_1164.all;
    use ieee.numeric_std.ALL;
    use ieee.std_logic_arith.all;

entity UART is
    port (
        clk_10mhz: in STD_LOGIC;
        txPin: out STD_LOGIC
    );
end entity;

architecture Test of UART is
    signal txStart: STD_LOGIC;
    signal txIdle: STD_LOGIC;
    signal txData: STD_LOGIC_VECTOR(7 downto 0);

    component TX is
        port (
            clk_in: in STD_LOGIC;
            start: in STD_LOGIC;
            data: in STD_LOGIC_VECTOR(7 downto 0);
            tx: out STD_LOGIC;
            txIdle: out STD_LOGIC
        );
    end component TX;


begin
    process (clk_10mhz, txIdle) 
        variable clkDividerCounter : integer range 0 to 10000000;
        variable textToSend : string(1 to 31) := "Hello darkness my old friend!" & CR & LF; 
        variable currentCharacterIndex : integer range 0 to 31;
    begin       
        if (rising_edge(clk_10mhz)) then
            if (clkDividerCounter < 10000000) then
                clkDividerCounter := clkDividerCounter + 1;
            else
                clkDividerCounter := 0;
                currentCharacterIndex := 1;
            end if;


            if (txIdle = '1' and currentCharacterIndex > 0) then
                txData <= CONV_STD_LOGIC_VECTOR(character'pos(textToSend(currentCharacterIndex)),8);
                txStart <= '1';

                if (currentCharacterIndex < 31) then
                    currentCharacterIndex := currentCharacterIndex + 1;
                else
                    currentCharacterIndex := 0;
                    txStart <= '0';
                end if;
            end if;
        end if;
    end process;


    u1: TX port map (clk_10mhz, txStart, txData, txPin, txIdle);
end Test;

TX.vhd:

LIBRARY ieee;
    USE ieee.std_logic_1164.all;
    use ieee.numeric_std.ALL;

entity TX is
    port (
        clk_in: in STD_LOGIC;
        start: in STD_LOGIC;
        data: in STD_LOGIC_VECTOR(7 downto 0);

        tx: out STD_LOGIC;
        txIdle: out STD_LOGIC
    );
end entity;

architecture Test of TX is
    signal idle: STD_LOGIC;
begin
    process (clk_in) 
        variable bitIndex : integer range 0 to 9;
        variable clkDividerCounter : integer range 0 to 260;

        variable dataFrame : STD_LOGIC_VECTOR(9 downto 0);
        variable dataFrameCurrentIndex : integer range 0 to 9;
    begin
        if (rising_edge(clk_in)) then
            if (start = '1' and idle = '1') then
                dataFrame(0) := '0';
                dataFrame(8 downto 1) := data;
                dataFrame(9) := '1';

                dataFrameCurrentIndex := 0;

                idle <= '0';
            end if;

            if (idle = '0') then                
                if (clkDividerCounter < 260) then
                    clkDividerCounter := clkDividerCounter + 1;
                else
                    if (dataFrameCurrentIndex <= 9) then
                        tx <= dataFrame(dataFrameCurrentIndex);
                        dataFrameCurrentIndex := dataFrameCurrentIndex + 1;
                    else
                        idle <= '1';
                    end if;

                    clkDividerCounter := 0;
                end if;
            end if; 

            txIdle <= idle; 
        end if;
    end process;
end Test;

Upvotes: 0

Views: 538

Answers (1)

Jonathan Drolet
Jonathan Drolet

Reputation: 3388

Move the line

txIdle <= idle;

from TX.vhd outside the process. Signals take their new value after the process ends.

For example:

idle <= '0';
txIdle <= idle;

Will set txIdle to '1' if idle was '1' when the two statements were executed inside a process. You should notice that this means that txIdle will be '1' for two consecutive cycles and causes currentCharacterIndex to increment twice at the start.

Note that contrary to signals, variable take their new value when the assigning statement is encountered, and not at the end of the process as signals do.

While your code is not that terrible for a beginner, I recommend to use only signal when you start learning VHDL. It is much easier to make mistake with variables, or describe sub-optimal or broken implementation.

Also, as Brian mentioned, don't use std_logic_arith, especially when using numeric_std. They are conflicting with each other (some tools deal with it though) and std_logic_arith is not a IEEE standard, while numeric_std is.

Finally, simulation is a crucial part of hardware design. To avoid uninitialized pin, add a reset to your circuit, which is generally a good idea.

Upvotes: 1

Related Questions