Hello! In this article I show how to implement an autobaud UART in VHDL. UART is the acronym for Universal Asynchronous Receiver Transmitter and is nothing more than a serial communication interface commonly found on microcontrollers and used to interface with external devices (such as GPS). Being an asynchronous interface, both the transmitting and receiving UARTs need to be configured to work at the same speed (commonly known as baud rate). An autobaud UART is an special UART capable of automatically synchronize and adjust its speed to meet the remote transmitter speed. The code presented in this article was specifically written for my FPz8 softcore but can be easily changed for other applications.
Asynchronous Communication Principles
Before studying our UART implementation, let’s take a look at some basic asynchronous communication principles. The first thing to note is that an asynchronous communication means the absence of a synchronism signal between the communication sides while communication is ongoing, that opposes to a synchronous communication (found on serial protocols such as I2C, SPI, etc) where a synchronization signal (a clock line) is present and used to synchronize all devices.
As there is no synchronization line, asynchronous communication relies on a special pattern to synchronize the receiver to the transmitter thus delimiting new data. This pattern is characterized by a transition from mark (communication line in idle state, usually a high logic level) to space (communication line in active state, usually a low logic level). Data bits are sent in sequence an each one has an equal and fixed timing which usually follows a standardized speed (300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600 or 115200 bps).
The first bit is the start bit and is always a space (low logic level), followed by data bits (usually eight, starting from the lowest significant bit) and then a stop bit which is always a mark (high logic level). The communication line must idle to mark state (high logic level). Figure 1 shows a typical asynchronous frame commonly known as 8N1 (eight data bits, no parity and one stop bit).
Figure 1 – A typical asynchronous frame
Note: it is possible to invert serial communication levels, thus making a mark a low logic level and a space a high logic level. In such case a start bit is signaled by a rising edge of the communication line!
An UART implementation is relatively simple and uses basically two shift-registers along with timers/clocks for data transmitting and receiving. In general, the transmitter is easier to implement as all you need is a parallel-in serial-out shift-register along with a clock signal operating in the desired communication speed.
The receiver block, on the other hand, is a more complex design as one needs to sample the communication line preferably on the middle of each bit-time. Our approach relies on two timings: a full bit-time and half bit-time. After detecting a falling edge (from mark to space), the receiver waits for half bit-time, check communication line for a low logic level (which characterizes a start bit) and if true, on every bit-time it samples the communication line and line state is stored onto a shift-register (in digital electronics a serial-in parallel-out shift-register). Once all eight data bits are sampled, the receiver checks for a valid stop bit (which must be a high logic level). If true, the newly received data is stored onto a register, otherwise a receiving error occurred, which can be caused by several factors such as: different speed setting between transmitting and receiving sides, noise, synchronization failure or a break character (signaled by keeping communication line low for more than ten bit-times). Figure 2 depicts a signal sampling sequence.
Figure 2 – Serial samples taken by receiver
In order to specify the transmitter state and the receiver state, FPz8’s UART makes use of two user-defined types: Tdbg_uarttxstate (transmitter state) and Tdbg_uartrxstate (receiver state). A structure named Tdbg_uart has all UART variables (registers).
The possible UART receiver states are:
type Tdbg_uartrxstate is ( DBGST_NOSYNC, -- UART is not synchronized to the transmitter DBGST_WAITSTART, -- UART is waiting for a synchronization character (0x80) DBGST_MEASURING, -- UART is measuring a synchronization character (0x80) DBGST_IDLE, -- UART is synchronized and awaiting data DBGST_START, -- UART received a start bit DBGST_RECEIVING, -- UART is receiving new data DBGST_ERROR -- UART is in error state );
The possible UART transmitter states are:
type Tdbg_uarttxstate is ( DBGTX_INIT, -- UART initializing DBGTX_IDLE, -- UART awaiting new data for transmission DBGTX_START, -- UART is sending a start bit DBGTX_TRASMITTING, -- UART is sending data DBGTX_BREAK, -- UART is preparing to send a break DBGTX_BREAK2 -- UART awaiting for a break completion );
The UART structure Tdbg_uart is shown below. We can see there are two variables for controlling transmitter and receiver state, registers for data shifting, counters for controlling the number of bits sent/received, counters for controlling transmitting and receiving speed along with some flags for UART state signaling.
type Tdbg_uart is record RX_STATE : Tdbg_uartrxstate; TX_STATE : Tdbg_uarttxstate; RX_DONE : std_logic; -- new data is available (1 bit) TX_EMPTY : std_logic; -- tx buffer is empty (1 bit) DBG_SYNC : std_logic; -- debugger is synchronized to host (1 bit) WRT : std_logic; -- write/read command flag (1 bit) LAST_SMP : std_logic; -- last sample read from DBG_RX pin (1 bit) SIZE : std_logic_vector(15 downto 0); -- 16-bit size of command (16 bits) TXSHIFTREG : std_logic_vector(8 downto 0); -- transmitter shift register (9 bits) RXSHIFTREG : std_logic_vector(8 downto 0); -- receiver shift register (9 bits) TX_DATA : std_logic_vector(7 downto 0); -- TX buffer (8 bits) RX_DATA : std_logic_vector(7 downto 0); -- RX buffer (8 bits) RXCNT : integer range 0 to 15; -- received bit counter (4 bits) TXCNT : integer range 0 to 15; -- transmitted bit counter (4 bits) BAUDPRE : integer range 0 to 2; -- baud prescaler (2 bits) BAUDCNTRX : std_logic_vector(11 downto 0); -- RX baud divider (12 bits) BAUDCNTTX : std_logic_vector(11 downto 0); -- TX baud divider (12 bits) BITTIMERX : std_logic_vector(11 downto 0); -- RX bit-time register (1/2 bit-time) (12 bits) BITTIMETX : std_logic_vector(11 downto 0); -- TX bit-time register (12 bits) end record;
Note that WRT bit and SIZE variable are only used by FPz8’s debugging system and would not be present on a general-purpose UART. Also note that RX_DONE flag signals new data was received and is available on RX_DATA register. TX_EMPTY bit signals the transmitter shift-register is empy and ready to receive new data to be transmitted (on TX_DATA register). Keep in mind we did not include any kind of buffer over/under run, meaning that if new data is received before the previous one is read by application, old data is lost. The same occurs for transmitting buffer. It would be easy to include these controls if needed by your application.
Registers BITTIMETX and BITTIMERX control bit duration (BITTIMETX stores a full bit time duration, while BITTIMERX stores a half bit-time).
Transmitter code is fairly simple and is shown below:
case DBG_UART.TX_STATE is when DBGTX_INIT => DBG_UART.TX_EMPTY := '1'; DBG_UART.TX_STATE:=DBGTX_IDLE; when DBGTX_IDLE => -- UART is idle and not transmitting DBG_TX <= '1'; if (DBG_UART.TX_EMPTY='0' and DBG_UART.DBG_SYNC='1') then -- there is new data in TX_DATA register DBG_UART.BAUDCNTTX:=x"000"; DBG_UART.TX_STATE := DBGTX_START; end if; when DBGTX_START => if (DBG_UART.BAUDCNTTX=DBG_UART.BITTIMETX) then DBG_UART.BAUDCNTTX:=x"000"; DBG_UART.TXSHIFTREG := '1'&DBG_UART.TX_DATA; DBG_UART.TXCNT := 10; DBG_UART.TX_STATE := DBGTX_TRASMITTING; DBG_TX <= '0'; end if; when DBGTX_TRASMITTING => -- UART is shifting data if (DBG_UART.BAUDCNTTX=DBG_UART.BITTIMETX) then DBG_UART.BAUDCNTTX:=x"000"; DBG_TX <= DBG_UART.TXSHIFTREG(0); DBG_UART.TXSHIFTREG := '1'&DBG_UART.TXSHIFTREG(8 downto 1); DBG_UART.TXCNT :=DBG_UART.TXCNT - 1; if (DBG_UART.TXCNT=0) then DBG_UART.TX_STATE:=DBGTX_IDLE; DBG_UART.TX_EMPTY := '1'; end if; end if; when DBGTX_BREAK => DBG_UART.BAUDCNTTX:=x"000"; DBG_UART.TX_STATE:=DBGTX_BREAK2; when DBGTX_BREAK2 => DBG_TX <= '0'; DBG_UART.RX_STATE := DBGST_NOSYNC; if (DBG_UART.BAUDCNTTX=x"FFF") then DBG_UART.TX_STATE:=DBGTX_INIT; end if; end case;
Regarding the receiver, there are some details which are worth commenting:
- Due to its asynchronous nature (relatively to FPGA), DBG_RX input must be sampled by using one or preferably two flip-flops (we used two) so that the external signal is sampled synchronously to the internal system clock. Keep in mind that absolutely strange things can happen when you feed an external unsynchronized signal through an FPGA! The reason for that is not obvious and it is not difficult to understand: FPGAs are just like logical building blocks, but they don’t usually implement a digital logic circuit the way we expect/know. In fact, as FPGAs are built from logic blocks (which usually include a look-up table and a register) and an interconnection matrix for connecting these blocks, the final result is that even the simplest circuits can virtually use several paths and blocks within the FPGA, thus resulting in different delays and making an asynchronous signal to be interpreted in different ways according to the FPGA internal logic configuration. An asynchronous signal fed into a complex FPGA design can lead to several and sometimes unexpected results, including some “impossible” states! Thence it’s very important to synchronize every external signal before feeding them into the inner application logic. Regarding our UART, DBG_RX input is sampled by RXSYNC2, which is sampled by RXSYNC1, which is the actual signal read by UART;
- Start bit detection is nothing more than a falling-edge detection and to accomplish that we used a flip-flop which stores the previous state of DBG_RX input. That way, by comparing current state of DBG_RX with its previous state it is possible do detect a falling (or rising) edge! The “if” command related to that operation is found inside DBST_IDLE state in the code below:
DBG_UART.BAUDPRE := DBG_UART.BAUDPRE+1; -- baudrate prescaler if (DBG_UART.BAUDPRE=0) then DBG_UART.BAUDCNTRX := DBG_UART.BAUDCNTRX+1; DBG_UART.BAUDCNTTX := DBG_UART.BAUDCNTTX+1; end if; RXSYNC2 <= DBG_RX; -- DBG_RX input synchronization RXSYNC1 <= RXSYNC2; -- RXSYNC1 is a synchronized DBG_RX signal case DBG_UART.RX_STATE is when DBGST_NOSYNC => DBG_UART.DBG_SYNC := '0'; DBG_UART.RX_DONE := '0'; DBG_CMD := DBG_WAIT_CMD; DBG_UART.RX_STATE := DBGST_WAITSTART; when DBGST_WAITSTART => if (RXSYNC1='0' and DBG_UART.LAST_SMP='1') then DBG_UART.RX_STATE := DBGST_MEASURING; DBG_UART.BAUDCNTRX := x"000"; end if; when DBGST_MEASURING => if (DBG_UART.BAUDCNTRX/=x"FFF") then if (RXSYNC1='1') then DBG_UART.DBG_SYNC := '1'; DBG_UART.RX_STATE := DBGST_IDLE; DBG_UART.BITTIMERX := "0000"&DBG_UART.BAUDCNTRX(11 downto 4); DBG_UART.BITTIMETX := "000"&DBG_UART.BAUDCNTRX(11 downto 3); end if; else DBG_UART.RX_STATE := DBGST_NOSYNC; end if; when DBGST_IDLE => DBG_UART.BAUDCNTRX:=x"000"; DBG_UART.RXCNT:=0; if (RXSYNC1='0' and DBG_UART.LAST_SMP='1') then -- it is a start bit DBG_UART.RX_STATE := DBGST_START; end if; when DBGST_START => if (DBG_UART.BAUDCNTRX=DBG_UART.BITTIMERX) then DBG_UART.BAUDCNTRX:=x"000"; if (RXSYNC1='0') then DBG_UART.RX_STATE := DBGST_RECEIVING; else DBG_UART.RX_STATE := DBGST_ERROR; DBG_UART.TX_STATE := DBGTX_BREAK; end if; end if; when DBGST_RECEIVING => if (DBG_UART.BAUDCNTRX=DBG_UART.BITTIMETX) then DBG_UART.BAUDCNTRX:=x"000"; -- one bit time elapsed, sample RX input DBG_UART.RXSHIFTREG := RXSYNC1 & DBG_UART.RXSHIFTREG(8 downto 1); DBG_UART.RXCNT := DBG_UART.RXCNT + 1; if (DBG_UART.RXCNT=9) then if (RXSYNC1='1') then -- if the stop bit is 1, rx is completed ok DBG_UART.RX_DATA := DBG_UART.RXSHIFTREG(7 downto 0); DBG_UART.RX_DONE := '1'; DBG_UART.RX_STATE := DBGST_IDLE; else -- if the stop bit is 0, it is a break char, reset receiver DBG_UART.RX_STATE := DBGST_ERROR; DBG_UART.TX_STATE := DBGTX_BREAK; end if; end if; end if; when others => end case; DBG_UART.LAST_SMP := RXSYNC1;
Automatic Speed Detection
Finally, before closing this article, we must see how the UART actually detects the transmitter speed! Well, our UART currently uses character 0x80 for synchronization purposes. That means following a reset or a BREAK character reception, the first data a remote transmitter must send is a 0x80 character. Our UART will measure its period and calculate communication speed based on it! But why 0x80? Several UARTs which include speed detection features usually rely on 0x55 as the synchronization character (as it is encoded as an alternating sequence of 0s and 1s), but Zilog (the manufacturer of Z8 encore, on which FPz8 is based) chosen 0x80 as their synchronization character because it is encoded as a big sequence of 0s (eight zeros in sequence it we include the start bit), thus easing period measurement and increasing overall precision!
That way, following a reset the UART goes automatically to DBGST_NOSYNC state and then to DBGST_WAITSTART where it awaits for a falling edge (the start of a supposed synchronization character). Once the edge is detected, BAUDCNTRX counter is reset and the UART goes to the measurement state, waiting for the data line to get back to high logic level. After that, register BITTIMETX receives the total count divided by 8, that is, a full bit time and register BITTIMERX receives the total count divided by 16, that is, a half bit time! The UART is then configured for the right speed and will only return to a sychronization state following another reset or BREAK character receive!
That is all for now, the complete code for the UART can be found inside the FPz8 project. I hope it can be useful and/or inspire someone in need or studying UARTs!
- FPz8 – https://github.com/fabiopjve/VHDL/tree/master/FPz8
- Original article at Embarcados website – https://www.embarcados.com.br/uart-autobaud-em-vhdl/
5 thoughts on “Autobaud UART in VHDL”
Boa tarde Fabio,
primeiramente meus parabens pelos artigos e implementacoes feitas com logica programavel. Tambem sou usuario e entusiasta. Fabio o motivo do contato tambem eh para verificar se chegaste a desenvolver e implementar codigo VHDL ou Verilog para codificador e decodificador de IR como padrões RC5, RC6 Sony e outros? Estou iniciando desenvolvimento de um gateway IR e caso possa indicar fontes ajudaria em muito.
Um abraço e obrigado!
Fernando Eduardo Schmitz
Obrigado pelo elogio! Pois é lógica programável é um assunto muito interessante e permite dar asas a imaginação! Olha, sobre encoder/decoder IR eu não pensei numa implementação em HDL. Suponho que implementar o modulador/demodulador para 38kHz seria simples, mas o codificador/decodificador seria mais trabalhoso. Sinceramente não sei se vale a pena implementar toda uma FSM relativamente complexa para fazer a codificação e decodificação num padrão qualquer, esta tarefa ainda me parece ser mais fácil de fazer via software do que via hardware.
Bom dia Fabio!
Obrigado pelo retorno.
Vou verificar a implementacao via fw com MCU.
Fiquei tempo longe da eletrônica e estou voltando. Tenho seu livro HCS08 teoria e prática, mas pelo que percebi esse microcontrolador foi descontinuado, é isso mesmo?
Quem entrou no lugar?
Olha, me parece que a NXP (e agora a Qualcomm) não tem interesse em manter as linhas proprietárias de microcontroladores (como o HCS08, Coldfire, etc) e estão migrando para cores como o Cortex-M0 e M0+. Particularmente acho uma pena, pois os HCS08 são microcontroladores muito versáteis, simples e baratos.