Memory-Mapped I/O
Memory-mapped I/O is one method of implementing communication between microprocessors and system peripherals with the most popular alternative known as port I/O. A system using port I/O contains two logically separate buses: the memory bus and the I/O bus. The memory bus in this type of system is used exclusively to access memory and is access using microprocessor instructions such as LOAD and STORE. The I/O bus is used exclusively to access system peripherals and is accessed using different microprocessor instructions such as IN and OUT.
In contrast, memory-mapped I/O contains only a single bus and a single set of instructions to access that bus. This simplifies the system design significantly and leads to cheaper, faster, and simpler hardware; a particular advantage in embedded systems. But, if the system only contains a single bus and a single instruction set to access that bus, how do we know when we are accessing memory and when we are performing I/O?
Address Spaces
The answer is that address spaces are used to assign different parts of the memory space to different components of the system. In this way we can divide up the memory space into a section for memory and one section for each peripheral. These sections are known as address spaces and must be continuous blocks of memory space. For example, here is a memory-map for an example system-on-chipi design on the XUPi development board:
This memory-map if for a system containing 1GB of main memory and three system peripherals. To access the peripherals the processor simply performs read or write operations to addresses in the given peripherals address space.
For example, in the OPB GPIOi peripheral provided by Xilinxi we know that the GPIOi_DATA register is located at the base address +0 and that the GPIOi_TRI register is located at the base address +4. Thus, we can access the LEDi's GPIOi_DATA register by reading and writing to memory location 0x70000000 and its GPIOi_TRI register by reading and writing to the memory location 0x70000004.
I/O in the C Language
In the C language, memory-mapped I/O can be done using the language's support for pointers. To do this, you first assign a pointer to the memory location that you want to access and then you use the dereference and assignment operators to read and write to that memory location. For example, the following C snippet writes to the TRI register in a GPIOi device and then reads from the DATA register:
volatile unsigned int *led_data = 0x70000000; volatile unsigned int *led_tri = 0x70000004; unsigned int value; *led_tri = 0xFFFFFFFF; value = *led_data;
One important thing to notice about this code is the use of the volatile keyword on the pointers used to access the memory-mapped I/O address space. This keyword informs the compiler that the pointers do not access standard memory and prevents the compiler from optimizing access to that variable. You will almost always want to use this keyword on pointers which access memory-mapped peripherals.
Assembly Level I/O
At the assembly level, memory-mapped I/O is performed using standard LOAD and STORE operations supported by all microprocessors. For instance, the MicroBlazei soft-processori from Xilinxi supports the LW and SW instructions. LW stands for Load Word and can be used to read a 32-bit value from a 4-byte aligned memory location. Likewise, the SW instruction stands for Store Word and can be used to write a 32-bit value to a 4-byte aligned memory location.
As an example, consider the following MicroBlazei assembly snippet:
mmap_io:
; Load the value 0x70000000 into register r5
XOR r5, r5, r5
ORI r5, r5, 0x7000000
; Load the value 0xFFFFFFFF into register r6
XOR r7, r7, r7
ORI r7, r7, 0xFFFFFFF
; Write to the GPIO_TRI register
SWI r7, r5, 4
; Read from the GPIO_DATA register
LWI r6, r5, 0
This example shows how to write to the GPIOi_TRI register and read from the GPIOi_DATA register inside of a GPIOi peripheral. Reading is done using the LWI instruction, a variant of the LW instruction which uses an immediate value rather than a register as its second input. Writing, similarly, is done using the SWI instruction. You can find out more about the instructions supported by the MicroBlazei by reading the MicroBlaze reference manual.
