Using FIFO's

A look at using Data FIFO's via the Zynq's UART module

827

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.