Using FIFOs
A Queue or First-in, First-out (FIFO) is a very common data structure implemented in both hardware and software. FIFO’s have a variety of uses; from queuing audio sample data to collecting measurements from a sensor in order.
A very common application of a FIFO is in communication between devices. On the Blackboard transmit and receive FIFOs are used with the UART, SPI, and the I2C modules to hold transmit data prior to sending and hold received data.
This document covers accessing the Zynq’s UART module transmit and receive FIFO’s, but does not cover configuring the module. It’s recommended you check the other background documents on the uart module so you know how to configure the module before sending/receiving data through it.
Using UART TX/RX FIFOs
The FIFOs in the Zynq’s UART module make receiving and sending data simple to the programmer and also reduce processor overhead. In older embedded systems a UART may have had 1 or 2 byte-sized buffer that the processer would have to service before more data arrived. The zynq’s UART FIFOs are 64 bytes deep, so the processor can spend less time transfering to and from the UART buffers. Use of the Zynq’s UART FIFO’s is detailed below
After configuring the UART, you can use the module’s data FIFO’s to send and receive data to/from a serial terminal. The transmit and receive FIFO’s are accessed through writing/reading from a single address. A write to this memory location queues data into the transmit FIFO (provided there is room), and a read gets the next byte from the receive FIFO. Since it’s possible to overflow both FIFO’s and to underflow the recieve FIFO, care needs to be taken before accessing the FIFOs. Without first checking the status flags, there is no guarantee a read or write from/to the UART data register will be valid. Before reading data it is wise to check status register, which holds information on the current state of the TX/RX FIFOs as well as some information on the status of the module’s transmitter and receiver.
The addresses for UART1’s status and data (FIFO) registers is given below.
UART1 Data (FIFO): 0xE0001030
UART1 Status: 0xE000102C
Checking the status flags
Bit 4 of the UART status register is the status flag for TX FIFO full
, if this bit is set no more entries can be enqueued into the transmit FIFO. Similarly bit 1 of the same register is the RX FIFO empty
flag; If this bit is clear, there are valid entires ready to read from the receive FIFO. Prior to accessing either of the FIFOs, it would be a very good idea to check the status of the queue you’re accessing.
Code example A pair of functions is provided to illustrate checking a FIFO status flag before accessing the corresponding FIFO.
First constants are defined for more readable code
.equ UART1_SR, 0xE000102C
.equ UART1_DATA, 0xE0001030
This function checks the status register and returns 1 if the tx-full bit is set. Since checking this flag is a common operation, it’s a good idea to have a function dedicated to it.
#Checks status of uart1 TX FIFO
#parameters: none
#returns r0, status of tx fifo 1:full, 0:not full
uart1_tx_empty:
mov r0,#0 @init r0
ldr r2, =UART1_SR
ldr r1, [r2] @get status flags
teq r1, #16 @check if 'bit 4' is set
moveq r0,#1 @return 1 if set
bx lr @return
Accessing the UART TX FIFO
This function will be used for easy checking of the TX FIFO full flag. Now we can add a function that writes data to the transmit FIFO. The function below utilizes the previous function to get the status of the TX FIFO. It also uses a passed parameter to create an easy to use interface from the calling function.
#Places the lowest byte of the passed parameter into the UART TX FIFO.
#Function blocks until space is availible in the FIFO
#parameters: r0, data to be sent
#returns: none
uart1_put_char:
push {r4,lr} @backup registers used
and r4,r0,#0xFF @save bottom byte of parameter to r4
check_full_loop:
bl uart1_tx_empty @check if uart1 tx has room
cmp r0,#1 @check the return value
beq check_full_loop @keep checking until room in FIFO
#empty check returned 0, so there is now room in tx
ldr r1, =UART1_DATA
str r4,[r1] @write to tx fifo
pop {r4,lr} @restore registers
bx lr
This function uses the stack to backup register 4 and the link register and then loads r0 into r4. Why does the function do this? If you don’t know the answer, it might be a good time to brush up on your knowledge of ARM calling convention.
After writing the above function, you can send a character over uart in an assembly program by just loading the character’s value into r0 and calling the function. You will of course need to configure the UART module first.
#constant holding the ascii value for capital F
.equ F_ASCII_CONST, 70
main:
bl configure_uart1 @configure the uart module
mov r0, #F_ASCII_CONST @load 'F' as parameter to send
bl uart1_put_char @send 'F'
b. @halt program here
Accessing UART FIFO’s in C code
When writing in C the behavior and addresses of any peripherals don’t change. In fact, the operations performed by C code can be nearly identical; C programs will look more compact, since operations can unfold to multiple instructions. The end result is still machine code, whether compiled from C code or assembled from ASM.
The below code assumes you have had exposure to C programming in an embedded context. If you haven’t coded with C on the Blackboard yet, check out the Embedded C Primer
document before examining the code here.
//UART1 status reg
#define UART1_SR *((uint32_t *) 0xE000102C)
//UART1 TX/RX FIFO DATA
#define UART1_DATA *((uint32_t *) 0xE0001030)
//check if room for bytes in tx fifo
int uart1_tx_full()
{
//check to see if bit 4 (TX fifo full) in the SR is set
//return 1 if set, otherwise 0
return (UART1_SR & 16)!=0;
}
void uart1_put_char(char dat)
{
while(uart1_tx_full()); //wait for room in FIFO
UART1_DATA = dat; //write to TX FIFO
}
Dissassembly
Compiling a c program produces machine code, the same as assembling an assembly program. Since Assembly instructions have a one-to-one mapping to machine code, machine code can be ‘disassambeled’ into assembly language. This can give us insight into how our C programs actually work on the hardware.
The disassembly for the above C function int uart1_tx_full()
is shown below with a few notes to show it’s nearly structurally the same as the assembly version of the function provided above.
After compiling a program in xsdk, you can see the disassembly from the generated .elf
file. The code below is copied from an ARM elf binary (with notes added). The first column is the address of the instruction, followed by the opcode found at that address. The disassembly for that instruction is shown.
Annotated Dissassembly of int uart1_tx_full():
int uart1_tx_full()
{
Note: Frame pointer operations, used in C ABI
100910: e52db004 push {fp} ; (str fp, [sp, #-4]!)
100914: e28db000 add fp, sp, #0
Note:These two instructions load the Status Register's address
100918: e301302c movw r3, #4140 ; 0x102c
10091c: e34e3000 movt r3, #57344 ; 0xe000
Note: Loads the status flag and check if 'bit 4' is clear
100920: e5933000 ldr r3, [r3]
100924: e2033010 and r3, r3, #16
100928: e3530000 cmp r3, #0
Note: set if bit 4 is clear
10092c: 13a03001 movne r3, #1
100930: 03a03000 moveq r3, #0
Note: zero-extension
100934: e6ef3073 uxtb r3, r3
}
Note: Copy to return parameter
100938: e1a00003 mov r0, r3
Note: Return Frame pointer operations
10093c: e24bd000 sub sp, fp, #0
100940: e49db004 pop {fp} ; (ldr fp, [sp], #4)
100944: e12fff1e bx lr
With the addition of frame pointer operations, which are used to manage a c call stack, the structure of the function is identical to the provided assembly check function. The address of the status register is loaded, the data from that address is loaded then the specific bit is checked. If it’s set, a 1 is loaded as the return value, other wise a 0 is loaded.