Andreas Moshovos

Spring 2008, Spring 2013

 

Input/Output Devices Continued: The Serial Interface (UART = Universal Asynchronous Receiver Transmitter)

 

In previous lectures we have seen the Parallel Interface device that provides a number of external connections that can be accessed in parallel (simultaneously as a group). The parallel interface device did not require any means of synchronization when reading or writing from/to it. That is, we assumed that all transactions completed instantaneously. In this lecture we will discuss another device, the serial interface where transactions are not instantaneous. Instead, it will take time for the serial device to complete a request of ours. This will allow us to discuss a new topic that relates to I/O devices: synchronization. We will first explain the programming interface of the UART device and then discuss about how it actually communicates with external devices.

 

The UART device:

 

The NIOS II board as configured for our course includes several serial port devices called UARTs. For practical reasons, we will be focusing on the JTAG UART. This is a UART implemented over the JTAG interface, which in turn is implemented over the USB connection with the host PC. The other UARTs provide a direct physical connection. In this lecture we will ignore the fact that the JTAG UART is implemented over the USB connection and pretend as if it had a dedicated physical connector. Each UART contains a receiver and a transmitter. The JTAG UART receiver can be used to receive characters from the PC (each a byte) while the transmitter can be used to send character to the PC. In the system you will be using, a terminal emulation program will be used to display the characters received on the PC, and to transmit characters from the PC’s keyboard to the DE2. The JTAG UART is a simplified version of other UARTS. Other UART interfaces contain several additional registers which we will not cover. These registers control various aspects of the communication such as its rate, data types, etc.

 

The JTAG UART interface starts at address 0x10001000 and comprises the following 32-bit memory mapped registers:

 

1.  Receiver Register, RR at distance +0 from the base, i.e., location 0x10001000.

2.  Transmitter Register, TR at distance +0 from the base, i.e., location 0x10001000 (not a typo, it *is* the same address as the RR).

3.  Control and Status Register, CSR at distance +4 from the base, i.e., at location 0x10001004.

 

The format of these registers is as follows:

 

 

31

 

 

 

 

 

 

24

 

 

 

 

 

 

 

16

 

 

 

 

 

 

 

8

 

 

 

 

 

 

 

0

RR

data elems available in recv buffer

V

 

 

 

 

 

 

 

data received

TR

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

data to send

CSR

Slots available in transmit buffer

 

 

 

 

 

AC

 

 

 

 

 

 

WIP

RIP

EWI

ERI

 

We’ll discuss the various fields as needed while explaining how program the device.

 

There are two transactions (i.e., operations) that can be asked of the UART: (1) send a character, or (2) receive a character. In a typical, setting the UART is connected via a few wires with an external device that is also capable of sending and receiving characters (such as the PC in the lab). The RR and TR are used respectively for receiving or sending characters (let us assume that characters are 8 bit for simplicity, but please note that in other UARTs there is a control register that can be used to configure different bit lengths for characters). They both map onto the same memory address. When reading from this memory location we are accessing RR, while when we write to this memory location we are accessing the TR. To send a character out we write it to TR, and to receive a character that was send to us, we read the RR. However, communication is a bit more complicated than that. Specifically:

 

1.  Sending characters takes considerable time compared to the CPU’s processing speed (several thousand CPU cycles).  Hence, we must be able to wait while the UART is sending a character before we attempt sending another one.

2.  We should read a character from RR only if one has been received from the external source. While a character is waiting to be read in the RR, the receiver may receive additional characters. The receiver has a buffer where these characters are kept while waiting to be read from the NIOS II. If the buffer is full and a new character is received an existing one may be lost or the new one may be dropped. Either way, we should be reading characters as fast as they are received otherwise we will lose some of them.


Sending a character:

 

Let’s look at sending, i.e., transmitting characters using the JTAG UART. The pseudo algorithm is as follows:

 

   While (transmitter is busy) wait;

   TR = character we want to transmit

 

How do we determine whether the transmitter is busy? That information is contained in the Control and Status Register (CSR). Specifically, when we read this register, bits 16 through 31 contain a number. If that number is non-zero then the transmitter can accept our request. If that number is zero, then the transmitter is busy and we must wait and try later. Why is there a number and not just a bit saying busy/idle? The transmitter contains a small FIFO queue for accepting requests. The number reported by the upper bits of CSR tell us how many of those slots are currently empty. As we write values to the TR, we are filling in the FIFO queue. As characters get transmitted, the queue is drained. The number of FIFO slots available is determined at design time. The FIFO allows us to quickly hand off a number of characters to the transmitter without having to wait after each one before we hand off the next one.

 

Here’s a subroutine for sending the character in register r4. We are using caller-saved registers to avoid having to save/restore within the subroutine:

 

.equ JTAG_UART_BASE, 0x10001000

.equ JTAG_UART_RR, 0

.equ JTAG_UART_TR, 0

.equ JTAG_UART_CSR, 4

 

      .text

putchar:

      movia r8, JTAG_UART_BASE

     

wait:

      ldwio r2, JTAG_UART_CSR(r8)   # read CSR in r2

      srli  r2, r2, 16              # keep only the upper 16 bits

      beq   r2, r0, wait            # as long as the upper 16 bits were zero keep trying

     

      stwio r4, JTAG_UART_TR(r8)    # place it in the FIFO

      ret

 

     

Receiving a character:

 

To receive a character we must wait until one is received (in our setup the PC has to send one to DE2). How do we know if a character was received? Well, we have to ask. Receiving the character and asking whether one is available is all done by accessing register RR. The 32 bits of register RR contain all this information. Specifically:

 

1.  Bits 0-7 contain the character, if one was received.

2.  Bit 15 is 1 if a character was received. In this case, the character appears in bits 0-7. If this bit is zero, the value in bits 0-7 is meaningless.

3.  Bits 31 through 16 contain additional information whether you want it or not. They contain the number of additional characters that have been received and are waiting in the incoming buffer. The UART contains a FIFO where the receiver places characters as it receives them. If this number is non-zero, then more characters are still available in the receive FIFO.

 

 

Here’s the subroutine for reading a character from the JTAG UART. The character is returned in r2:

 

.equ JTAG_UART_BASE, 0x10001000

.equ JTAG_UART_RR, 0

.equ JTAG_UART_TR, 0

.equ JTAG_UART_CSR, 4

 

      .text

getchar:

      movia r8, JTAG_UART_BASE

     

wait:

      ldwio r2, JTAG_UART_RR(r8)    # read RR in r2

      andi  r10, r2, 0x8000         # extract bit 15 in register r10 / keep a copy of r9 since it contains the character if any

      beq   r10, r0, wait           # if bit 15 was zero, there was no character, keep waiting/trying

     

      andi  r2, r2, 0xff            # a character was received, keep only that in r2 (mask out all other bits)

      ret

 

Bits EWI and ERI stand for Enable Write and Enable Read Interrupts. Bits WIP and RIP stand for Write and Read Interrupt Pending respectively. More on these when we cover interrupts.

Bit AC we don’t need to worry about for regular communication. The JTAG UART serves other purposes as well where the other party (the host PC in this case) can use it to probe the FPGA hardware on the DE2 board. The AC bit indicates that such an inspection took place. If you are interested more about this, read the Altera manual.

 

Polling:

 

In both subroutines we used a busy-wait loop where we continuously probe the RR or the CSR until the receiver or the transmitter become available. Such loops are called busy-wait as the processor remains busy (i.e., executes instructions that communicate with the device) while not doing any productive work as it is simply waiting for the device to become available. This style of communication with devices is called POLLING. In polling we continuously probe the device until it becomes available or it completes our request. Polling is simple, but uses processor resources ineffectively. Using polling becomes at least cumbersome when more than one devices are involved as we have to write our code in a way that continuously probes several devices. In some cases, it is simply inappropriate. For example, consider what would happen if windows or X windows used polling for accepting input from the mouse: everything would freeze until you moved the mouse and then freeze again if you stopped moving it.

 

In real life, polling would be the equivalent of handing a piece of work to someone else and then keep knocking on their door asking: “Are you done yet?”. That will quickly help you make a lot of friends besides being completely inefficient. There is an alternative, which requires additional support at the hardware level. We will cover the alternative in a later lecture. It corresponds to the real life scenario where you hand out a task and then do something else while expecting to be notified when the task is completed.

 

As a final example, we present the echo routine, it just repeats the characters it receives:

 

.equ JTAG_UART_BASE, 0x10001000

.equ JTAG_UART_RR, 0

.equ JTAG_UART_TR, 0

.equ JTAG_UART_CSR, 4

 

      .text

echo:

      movia r8, JTAG_UART_BASE

     

waitr:

      ldwio r2, JTAG_UART_RR(r8)    # read RR in r2

      andi  r9, r2, 0x8000          # extract bit 15 in register r10 / keep a copy of r9 since it contains the character if any

      beq   r9, r0, waitr           # if bit 15 was zero, there was no character, keep waiting/trying

     

      andi  r2, r2, 0xff            # a character was received, copy the lower 8 bits to r2 and return

 

waitt:

      ldwio r9, JTAG_UART_CSR(r8)   # read CSR in r9

      srli  r9, r9, 16              # keep only the upper 16 bits

      beq   r9, r0, waitt           # as long as the upper 16 bits were zero keep trying

     

      stwio r2, JTAG_UART_TR(r8)    # place it in the FIFO

      br    waitr                  # life is interesting, keep doing what you do

      ret                           # never reaches here, this is for show

 

 

The other UART

 

Besides the JTAG UART, our board has a regular UART as well. It uses the standard physical serial interface (the JTAG UART in encapsulated under the USB physical interface). The regular, or “RS-232” UART occupies two words starting at address 0x10001010. The name RS-232 refers to the standard the describes the specifications of this interface (physical dimensions, voltage levels, protocol, etc.).

 

It too has three registers with the RB and the RB sharing address 0x10001010. Here’s their format:

 

 

31

 

 

 

 

 

 

24

 

 

 

 

 

 

 

16

 

 

 

 

 

 

 

8

 

 

 

 

 

 

 

0

RR

data elems available in recv buffer

 

 

 

 

 

 

PE

 

data received

TR

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

data to sent

CR

Slots available in transmit buffer

 

 

 

 

 

 

 

 

 

 

 

 

WIP

RIP

EWI

ERI

 

It’s almost identical to the JTAG UART. To read a character, one polls the RR register. Contrary to the JTAG UART, there is no valid bit here. The upper 16-bits of the RR register still tell us how many characters are waiting for us in the receive buffer. Polling here is done in two steps. First we use a ldhio to read just the upper 16 bits. As long as these are non-zero, there is a character that we can read. Then we use another ldhio to read the lower 16 bits to get the character. Reading the lower 16 bits “consumes” the character. The UART automatically decrements the value in the upper 16-bits. Here’s an example code that waits for one character and then returns it in r2:

 

wait:

    movia r8, 0x10001010      # r7 now contains the base address

    ldhio r2, 2(r8)           # read the upper 16 bits. Recall NIOS II is little-endian. So the upper 16 bits (31-16) are at address (base + 2)

    beq   r2, r0, wait        # If this is 0, no data is available

    ldwio r2, 0(r8)           # read the data and decrement the count in the upper 16-bits

    andi  r2, r2, 0xff        # keep just the lower 8 bits – mask out the rest

 

What is bit PE? It stands for “Parity Error”. Parity is an extra bit that is communicated over the serial connection and is used to guard against possible transmition errors. The device is configured for ODD parity, which means that the parity bit value is set so that the total number of 1 in the data transmitted over the wire is always odd. For example, if we want to send the value 00001111 which has four “1”s, we would add an extra parity bit of “1”. The number of “1”s is 5 which is odd. The value transmitted over the wire would be 00001111 1. If the value was 00000111, then we will add an extra 0 and send 0000 0111 0. The number of “1”s here is 3 which is odd. Parity guards against a single error; if one bit is sensed incorrectly, then we will detect it.

 

How about sending a character? This is identical to the JTAG UART except for the base address:

 

 

putchar:

      movia r8, 0x10001010

     

wait:

      ldwio r2, 4(r8)               # read CR in r2

      srli  r2, r2, 16              # keep only the upper 16 bits

      beq   r2, r0, wait            # as long as the upper 16 bits were zero keep trying

     

      stwio r4, 0(r8)               # place it in the FIFO

      ret

 

Bits EWI and ERI stand for Enable Write and Enable Read Interrupts. Bits WIP and RIP stand for Write and Read Interrupt Pending respectively. More on these when we cover interrupts.

 

How serial, asynchronous communication works:

 

At the communication link level the serial device uses the following protocol for sending/receiving characters. Each character is represented as a stream of bits. Specifically, each character is represented in the following format:

 

 

idle

 

 

start

 

 

D0 (lsb)

 

D1

 

 

D2

 

 

D3

 

 

D4

 

 

D5

 

 

D6

 

 

D7

 

 

stop/idle

 

ß VOLTAGE LEVEL

 

 

 

ßà

bit cell

 

 

 

 

 

 

 

 

                  TIME

 

The actual bit pattern of the character appears in bits D0 through D7. There is a preamble START bit with the value of 0 and a postfix STOP bit with the value of 1. Each bit is sent by setting the communication line to the corresponding voltage level for a pre-specified duration. This is the bit cell shown.

 

BAUD RATE: It’s defined as the number of “bit cells” that fit within 1 second. For example for 9600 baud rate we get that bit time = 1/9600 = 104.16 microseconds. Thus it takes at least (8 + 1 + 1) * 104.16 = 1.0416 milliseconds to send a full byte (the +1 is for the start bit and the +1 for the stop bit). Note that baud rate is different than the effective bandwidth since there is the overhead associated with start and stop bits.

 

Ideally, the transmitter and the receiver would use identical time references (e.g., a clock) for communicating. Identical means both same frequency and same phase (i.e., transitions happen at the same time on both sides). This way they could agree on exactly where each bit starts and thus communicate without any errors. Communication in this case would be very simple: the receiver takes a single sample at the center of each bit cell and thus reconstructs the data byte transmitted.

 

IDEAL SCENARIO: TRANSMITTER AND RECEIVER USE EXACTLY THE SAME TIME REFERENCES HENCE THEY AGREE ON WHERE BIT CELLS START:

 

 

 

idle

 

 

start

 

 

D0 (lsb)

 

D1

 

 

D2

 

 

D3

 

 

D4

 

 

D5

 

 

D6

 

 

D7

 

 

stop/idle

 

ß VOLTAGE LEVEL

 

^

sample

at recv.

^

^

^

^

^

^

^

^

^

 

 

However, the receiver and the transmitter do not share a common time reference. Instead, they use their own local time references. While this is highly practical (because there is no need to share a time reference, something that would require additional wires and that would be very hard to do anyhow due to the possibility of using long wires), it introduces two difficulties:

 

1.  The frequency of the two time references may differ

2.  The phase (i.e., the point in time where the transition from 0 to 1 happens) of the two time references will most likely be different.

 

 

REALISTIC SCENARIO: THE TRANSMITTER AND RECEIVER USE THEIR USE TIME REFERENCES. THERE IS A DIFFERENCE IN FREQUENCY AND IN PHASE:

 

WHAT THE TRANSMITTER USES:

 

idle

 

 

start

 

 

D0 (lsb)

 

D1

 

 

D2

 

 

D3

 

 

D4

 

 

D5

 

 

D6

 

 

D7

 

 

stop/idle

 

ß VOLTAGE LEVEL

WHAT THE RECEIVER THINKS/USES:

 

 


idle

 

 

start

 

 

D0 (lsb)

 

D1

 

 

D2

 

 

D3

 

 

D4

 

 

D5

 

 

D6

 

 

D7

 

 

stop/idle

 

ß VOLTAGE LEVEL

     PHASE DIFFERENCE

                         FREQUENCY DIFFERENCES

 

 


                              TIME

 

 

To compensate for the two problems we use the start and stop bits and the receiver uses over-sampling. These are explained in what follows:

 

In the previous figure the differences in frequency are exaggerated. In practice there shouldn’t be a difference of more than 20% at the end between where the stop bit is and where the receiver thinks it is. Here’s why: To compensate for the first difficulty the RS-232C standard (the one used for the common serial port) imposes a requirement that the baud rates used by the two communicating devices should not be different more than 2%. Even so, noticing that we need at least 10 bits to transmit a byte, and even if we assume that initially the two time references are phase synchronized (i.e., both devices agree on where the start bit starts), at the end there may be a difference of up to 10 x 0.02 = 20% on where they think the center is for the stop bit.

 

To compensate for the second problem (phase difference) the serial interface uses the START and STOP bits. Note that the STOP and START bits use different logical values. This way there is always a transition from 1 to 0 and then to 1 when a new character is transmitted. Thus, the START and STOP bits are introduced as means of initial synchronization. The receiver waits until it detects a 1 to 0 transition and interprets this is as the START bit. Then it uses sampling to deal with differences in bit time and phase.

 

The receiver regenerates the transmitted value by over-sampling its input. That is, rather than taking a single sample per bit time it takes several and then uses these to detect the 0 to 1 transition for the stop/idle to start bits. Once this transition is determined, it can then use a single sample carefully chosen to so that it falls under the center of the bit time.

 

For example, if the receiver takes 16 samples per bit cell, then it should be able to detect the stop to start bit transition within 1/16 of the bit cell time in the worst case assuming identical time reference frequencies or within (1/16 x 1.02) of the transmitters bit cell time assuming that the receiver time reference is 2% slower than that of the transmitters. Once the beginning of the start bit is detected, the receiver can attempt to take samples at what it thinks is the center of the bit cell for each bit. The first sample should be taken after 24 cycles (at 16x over-sampling we pass 16 samples to go past the start bit and then pass another 8 samples to reach to the middle of D0). The second sample should be taken after 24+16 cycles and generally the ith sample should be taken at (24 + i x 16) cycles.

 

Even with these measures in place it is possible to encounter communication errors. These are referred to as FRAME errors. To further reduce the possibility of undetectable errors, serial communication often uses an additional parity bit. This can be used to detect single errors.