Guidelines for Developing a Nios II HAL Device Driver
This application note explains the process of creating and debugging a
hardware abstraction layer (HAL) software device driver. The included example of a HAL
software device driver, called my_uart_driver, illustrates various software development
stages. The example driver targets the Altera_Avalon_UART device,
connected through a Vectored Interrupt Controller (VIC) to the Nios® II processor.
This application note helps you with custom device driver development for Nios II systems in
the following ways:
Shows the development process steps, from sending bits out the transmit pin
in the main() through the construction of device access
macros and automatic device initialization in alt_sys_init()
Shows how to develop a driver with the command-line based Nios II Software Build Tools
Shows how to create applications and a board support package (BSP) based on your
Shows how to import and debug the applications and BSP with the Nios II Software Build
Tools for Eclipse
Explains interrupt latency, interrupt nesting, and determinism
Identifies system calls that cannot be included in a device driver interrupt service
Describes debugging tips and techniques, such as identifying UART transmission errors
Note: This application note uses the Nios II Gen2, Cyclone®
V E FPGA Development Kit, as an example hardware platform.
For more information about the HAL, refer to the "Overview of the Hardware
Abstraction Layer" chapter in the Nios II Software Developer's Handbook. For more
information about interrupt latency, refer to the "Exception Handling" chapter in the Nios
II Software Developer's Handbook.
The HAL application programming interface (API) provides an interface to
hardware similar to a portable operating system interface for unix (POSIX). This
interface abstracts the hardware details from upper-level clients of the HAL, such
as operating systems, networking stacks, or Nios II applications. The HAL provides a
variety of generic device classes, including character-mode, file subsystem,
Ethernet, timestamp and system timers, direct memory access (DMA), and flash memory.
The Altera_Avalon_UART is a member of the character-mode class
of HAL devices. The HAL has an API for character-mode class devices, which you can
use to manipulate the Altera_Avalon_UART. Mutual exclusion
resources are available, provided either by MicroC/OS-II (if present) or by the HAL.
These services include semaphores and event flags. When the HAL device driver makes
calls to these resources, the calls are simply translated to non-operations when the
multi-threading services are not available.
For additional information about HAL services, refer to the "Developing
Programs Using the Hardware Abstraction Layer" chapter in the Nios
II Software Developer's Handbook.
For additional information about the HAL API, refer to the "HAL API Reference"
chapter in the Nios II Software Developer's Handbook.
Nios II Embedded Design Suite (EDS) version 15.0 SP1 or higher.
The an459-design-files.zip archive.
archive contains a hardware design example for the Nios II Cyclone V E FPGA
Development Kit, along with software examples and a driver example named
my_uart_driver. The following software example projects
The bit_bang_uart application
The hello_world_my_uart application
The hal_my_uart BSP
an459-design-files.zip is available on the HAL Device Drivers Design
Example page of the Altera website.
This application note discusses Nios II device drivers and components at several levels. To
understand these levels, you need to be familiar with the following concepts:
HAL-compatible component—A design for a piece of hardware that can be
incorporated into a Nios II system and supported by the HAL. A component is an
abstract IP core that can be configured for a specific application. A component
has abstract parameters, such as base address. These parameters have no specific
value until a component instance is created. The Altera Avalon UART is an
example of a component.
Component instance—A component that is instantiated in a system. Component
instance parameters have specific values, assigned at the time of instantiation.
For example, an instance of the Altera Avalon UART must have a specific base
address. There can be multiple instances of a component in a system. Each
instance has a unique name, such as uart1,
assigned in Qsys.
HAL-device driver—A piece of software written to interface a component to the
HAL. A device driver supports a specific component. A Nios II BSP contains a
single device driver for each component found in the system. If there are
multiple instances of one component, they are all supported by a single driver.
In this application note, my_uart_driver is an
example of a device driver.
HAL-generic device model class—A group of device drivers with similar
characteristics and a common high-level API. Generic device models allow you to
use a consistent set of API calls with a variety of hardware designs. The class
of character-mode devices is an example of a HAL device class, and the printf() function is an example of a function call
supported by this device class.
For more information about HAL device classes, refer to “HAL Architecture” in the
Overview of the Hardware Abstraction Layer chapter of the Nios II
Software Developer's Handbook.
This section walks you through creation of the HAL device driver named my_uart_driver.
Preparing the bit_bang_uart Application and hal_my_uart BSP
First, you need to set up a development and debugging environment for the UART. This example
uses the Nios II Cyclone V E FPGA Development Kit with an accompanying design
example in an459-design-files.zip.
Follow these steps to build the bit_bang_uart project:
Get the most up-to-date version of an459-design-files.zip which is available on the HAL
Device Drivers Design Example page of the Altera website.
Unzip an459-design-files.zip to a working directory, such as
C:/my_design. This application note refers to this
directory as <my_design>. Be sure to preserve the directory
structure of the extracted software archive. Extraction creates a directory
structure tree under <my_design> with the following four
Note: The working directory name you choose must not contain any
After extracting an459-design-files.zip, refer to
<my_design>/readme.txt for a list of any required software
patches or other updated information. If a patch is required, install it according
to the instructions in readme.txt.
This section provides some background on how the my_uart_driver device driver
is associated with a component instance. You must specify the name of the directory
to store both the software device drivers and the custom components. The name should
be descriptive enough to identify the custom component. The directory must be under
directory. The librarian searches for user component files named <component_name>_sw.tcl in
directories below this ip directory.
Every HAL device driver has a software description file. The software
description file naming convention is <component_name>_sw.tcl. This name must match
the corresponding <component_name>_hw.tcl file generated by the Component Editor.
The my_uart_driver device driver’s software description file is
All components generated by the Component Editor have a
<component_name>_hw.tcl file. However, certain components
provided by Altera, such as the Altera_Avalon_UART, are generated outside the
Component Editor, and therefore do not have a
For additional information about creating device driver Tcl scripts, refer to the
“Driver and Software Package Tcl Script Creation” section of the Developing
Device Drivers for the Hardware Abstraction Layer chapter in the Nios II
Software Developer’s Handbook.
Altera provides an additional tool with the Nios II processor, the System Console,
that is useful for testing component instances and software device drivers, and for
constructing BSPs. This application note does not describe the System Console.
For information about the System Console, refer to the System Console User
In this section, you configure the Altera_Avalon_UART hardware component in Qsys.
Perform the following steps:
Start the Quartus II software. In the File menu, click Open
Browse to <my_design>.
Select the Quartus II project file AN459.qpf, and click Open.
In the Tools menu, click Qsys and open system.qsys.
In Qsys, in the Module Name column, double-click on
In the UART (RS-232 Serial Port) - uart1 dialog box,
verify the baud rate is set to 115200 bps, as shown in the figure below.
Figure 1. Verify UART Baud Rate
In the System Contents tab of Qsys, verify the value
for the UART base address.
This design example uses a value of
0x80 for the UART’s register base address. If you use a hardware design
other than the design example accompanying this application note, the value
of the UART's register base address might be different. Open Qsys and find
the UART base address for your board. The "uart1 Peripheral Register Base
Address" figure shows the base address for the UART used in this
Click Generate HDL in Qsys to regenerate the system.
Recompile the Quartus II project.
In the Quartus II software, in the Tools menu, click Programmer.
To program the AN459.sof image to the development board, ensure that
Program/Configure is turned on and click Start.
In Qsys, on the Tools menu, click Nios II Command Shell.
Change the directory to <my_design>/software_examples/app/bit_bang_uart.
Figure 2. uart1 Peripheral Register Base Address
Run the create-this-app script as follows:
step might take several minutes to complete.
The create-this-app script specifies the BSP named hal_my_uart. The hal_my_uart
BSP associates the component instance uart1
with the software driver my_uart_driver, as
shown in the summary.html excerpt in the "Driver
Mapping in summary.html" figure. The create-this-bsp
script for the hal_my_uart BSP selects component
instance uart1 for the stdio device. Build messages are stored in bsp/hal_my_uart/hal_my_uart_build_log.txt.
The compiler might report a small number of harmless
warnings when you build bit_bang_uart. For a list of
expected warnings, refer to the <my_design>/readme.txt file, extracted from an459-design-files.zip.
Change the directory to
Edit alt_sys_init.c in your favorite editor. The vi editor is available from the Nios II Command
Figure 3. Driver Mapping in summary.html
Disable the automatic invocation of the HAL UART device driver initialization
function by commenting out invocations of the following macros in
Rebuild the bit_bang_uart project by changing the directory back to
Connect a serial cable from the 9-pin console port on the Nios II
development board to an RS-232 serial port on your development host
Ensure that your host serial port is configured with the following
Figure 4. Tera Term Serial Settings
Follow these steps to import the bit_bang_uart application project and the
hal_my_uart BSP project:
In Qsys, in the tools menu, select Nios II Software Build
Tools for Eclipse to launch the Nios II SBT for Eclipse.
In Nios II SBT for Eclipse, in the File menu, click Import. The Import dialog box appears.
Expand the Nios II Software Build Tools Project folder, and select
Import Nios II Software Build Tools Project.
Click Next. The Import Software Build Tools Project wizard
Next to Project Location, click Browse. Navigate to and select the
Name the project bit_bang_uart.
Turn on Managed Project to have Nios II SBT for Eclipse manage your
makefile for you.
Click Finish. The wizard imports the bit_bang_uart application
Repeat steps 2 through 9, but at step 5 instead import the
For additional information about importing Nios II SBT command-line projects, refer
to “Importing a Command-Line Project” in the Getting Started with the Graphical
User Interface chapter of the Nios II Software Developer's
This example demonstrates how you can verify hardware functionality by specifying an explicit,
hard-coded memory address. In the Nios II SBT for Eclipse Project Explorer
tab, in the Nios II Project Explorer view, expand the bit_bang_uart project,
and open bit_bang_uart.c. In the main() procedure of
bit_bang_uart.c, you can see that the first call to
IOWR() uses a hard-coded base address of 0x80 for
Using a hard-coded address can be helpful when you first bring up new
hardware, to rule out any software errors, such as C pointer reference software
coding errors, in obtaining the peripheral's memory-mapped registers base address.
This technique provides confidence that your software is referencing the actual
hardware peripheral register.
Preview: Customizing the Design
This section describes typical modifications you might make to the software or
hardware after you have verified that it works with hard-coded addresses.
Before carrying out these modifications, work through the steps in Debugging the bit_bang_uart Project. Design modifications are
described in detail starting in The BitBangUartTransmit()
After you confirm successful communication from the software to the hardware, you can
change the hard-coded address to a symbolic name found in system.h. Replacing
the hard-coded register address with a symbolic definition enables the Nios II SBT
to update the software if the peripheral's register base address changes in the
UART1_BASE is a definition provided by system.h. When
defining macros in system.h, the Nios II SBT takes the peripheral name as
defined in Qsys, and converts it to uppercase. The Nios II SBT creates the
peripheral's base address by appending _BASE to the peripheral's
UART1_BASE is defined in system.h, a generated header file, and used in the bit_bang_uart.c source file. If you use a different hardware design, and
the UART peripheral name is not uart1, search and
replace the occurrences of UART1_BASE in bit_bang_uart.c with the name <your_uart_peripheral_name>_BASE. Find the UART peripheral module
name and register base on the System Contents tab in Qsys.
Refer to the "uart1 Peripheral Register Base Address" figure in the "Configuring the
Altera_Avalon_UART Component" section.
If you make hardware design modifications, you must regenerate your Nios II system in Qsys and
recompile the Quartus II project in the Quartus II software. In the regeneration
step, Qsys updates the SOPC Information File (.sopcinfo) for your hardware
If you have a pre-existing BSP, such as the example design, an updated
.sopcinfo file requires that you regenerate your BSP in the BSP Editor
(or by executing nios2-bsp on the command line). Then you must do a clean
build of the BSP as well as the application that depends on it. The clean build is
required because the software needs to obtain the new value of the
_BASE symbol from system.h.
For additional information about updating BSP files after a hardware change,
refer to Board Support Packages in the "Nios II Software Build Tools" chapter of the
Nios II Software Developer's Handbook.
In the Nios II Command Shell, to force the SBT to copy or regenerate all BSP and
application files, simply perform the following steps:
Delete the application makefile, for example
Delete the BSP’s public.mk file, for example
Execute the create-this-app script in the application directory, for
example app/bit_bang_uart/create-this-app. The create-this-app
script runs the create-this-bsp script, which in turn runs
This section demonstrates debugging techniques with the bit_bang_uart project. To start
debugging bit_bang_uart, perform the following steps:
In the Project Explorer view of the Nios II SBT for Eclipse, right click the bit_bang_uart, and select Nios II -> BSP Editor. Ensure the stdin, stdout and stderr are set to uart1.
Open a Tera Term terminal and configure the connection to
In the Tera Term, on the setup menu, click serial port.
Configure the serial port settings as shown in Debug Configuration
Click the New launch configuration button to
create a new debug configuration. To name the debug configuration, in the Name box type neek_uart, and click Apply. Refer to the "Debug
Configuration Project Tab" figure.
Figure 5. Debug Configuration Project Tab
On the Project tab, set Project name to bit_bang_uart, and
set ELF file name to the path name of the application project’s
Executable and Linking Format File (.elf).
Verify that none of the tabs contains a red “x”, indicating an error. If any
do, select that tab, and fill in the required data necessary to resolve the
error as indicated by the tool's messages. For example, if more than one
USB-Blaster cable is connected to your development host computer, the Target Connection tab has a red “x“. In this case, you
must select the appropriate cable under Processors to
resolve the error.
Note: If the message at the top of the dialog box says
Actual system ID not found at target base address, on the
Target Connection tab, click Refresh Connections. You
might need to click Refresh Connections several times to establish a
Note: If the message at the top of the dialog box says System timestamp mismatch, on the Target connection tab, check on the Ignore
mismatched system timestamp selection under the System ID checks.
Click the Target Connection tab, see the Debug Configuration Target Connection Tab figure below.
The message at the top of the dialog box says The expected
Stdout device name does not match the selected target byte stream device
name. This message is expected, because in the Connections panel, under Byte Stream Devices,
the listed device is jtag_uart, while the stdout device used by the bit_bang_uart application is uart1.
You use Tera Term to send and receive serial I/O. Tera Term is required because the Nios II SBT for
Eclipse does not support the use of a UART as a byte stream device.
The jtag_uart byte stream device
is used to receive Altera logging messages, as described in Debugging with the Altera Logging Functions
For additional information about setting up a
debug configuration for Nios II SBT projects, refer to "Run Configurations"
section in the "Getting Started with the Graphical User Interface" chapter
of the Nios II Software Developer's
If Eclipse prompts you to switch to the Nios II Debug perspective, click
Note: Depending on how the Eclipse preferences are configured,
Eclipse might automatically switch to the Nios II Debug
Select the Nios II Console view.
On the Window menu, choose to Show View and select Memory to open the Memory view.
If the Memory view appears in the lower left corner, sharing a tabbed area with
the Console view, drag the memory tab to the upper right corner of the
perspective. This arrangement allows you to view the Console and Memory views
Click the Add Memory Monitor button in
the Memory view, as shown in the Specifying Memory Address to
Monitor figure below. This action opens a Monitor
Memory dialog box in which you can type the memory address that you want
Enter the UART peripheral's register base address, as shown in the Specifying
Memory Address to Monitor figure below (0x80 for the
uart1 peripheral in the design example accompanying this
In the Memory view, right-click any cell under the column labeled 0 – 3,
and click Format. Set Column Size to 1 unit per column.
Figure 7. Specifying Memory Address to Monitor
Use the Step Over button to
advance the program execution over the IOWR()
macro. This macro transmit an asterisk to Tera Term by
writing directly to the UART's transmit register as shown in the Asterisk Transmitted from Memory-Mapped Register
If you do not see an asterisk in Tera Term, verify your hardware cable is properly
connected and your UART peripheral base address matches the one in your
Figure 8. Asterisk Transmitted from Memory-Mapped
The red numbers in the Memory view indicate which
memory values changed during the last “step over” operation. This change
helps you verify that a new peripheral is functioning correctly. The 2A in the Memory view is the hexadecimal value
for the asterisk character (*), as shown in the Transmit
Asterisk figure below.
Figure 9. Transmit Asterisk
To show the Memory view in ASCII rather than hexadecimal, click the New Renderings tab. Refer to the "Adding an ASCII
Rendering to the Memory View" figure below. In the New
Renderings tab, select ASCII and click Add Rendering(s).
The 2A in the Memory view changes to an
Figure 10. Adding an ASCII Rendering to the Memory View
You can transmit characters over the UART by directly changing memory
values in the Memory view as follows:
In the ASCII rendering, type an h in the cell
currently occupied by the asterisk in the Memory view. This cell
represents the transmit register.
Type an i in the same
cell in the Memory view.
The word hi appears
in Tera Term, as shown in the "Characters
Transmitted by manipulating UART Register" figure below.
The peripheral memory-mapped registers bypass the cache.
Therefore, the status register value displayed in the Memory view reflects
any changes to the status register made by the peripheral. The IOWR() and IORD() macros always bypass the cache.
Figure 11. Characters Transmitted by manipulating UART
This section examines the BitBangUartTransmit() function in
bit_bang_uart.c. The BitBangUartTransmit() function
demonstrates transmission of characters over the UART.
In the Nios II SBT for Eclipse, step over the BitBangUartTransmit() function.
The characters BIT BANH appear in Tera Term, as shown in the
"Stepping Over the BitBangUartTransmit() Function" figure below. The following steps
show why the string appears as it does.
Figure 12. Stepping Over the BitBangUartTransmit() Function
To begin analyzing BitBangUartTransmit(), perform the following
Restart the debugging session as follows:
to stop the
current debugging session.
In the Run menu, click Debug Configurations.
With the neek_uart debug configuration selected (the
default), click Debug.
Click Step Over to step to the call to the
Click Step Into
to step into the
Click Step Over to execute one line at a time until the string
BIT BANGBASH appears in Tera Term, as shown in the
Transmitting BIT BANGBASH by Stepping Through the Function figure
bit_bang_uart.c writes a value of zero to the status
register to clear any existing errors on the UART. The IOWR() macro accomplishes this step by writing to UART1_BASE.
Next, a loop cycles through the bitbang array, printing out the
characters BIT BANG to the UART. To prevent overruns, the loop
checks the transmit ready bit before each subsequent character transmission.
Immediately after the loop, the software transmits characters BASH
one after the other without checking the transmit ready bit.
If you step through each line to the end of the
BitBangUartTransmit() function, the software transmits the
characters BIT BANGBASH through the UART. These characters appear
in Tera Term, as shown in the Transmitting BIT BANGBASH by Stepping
Through the Function figure below. There is no transmitter overrun, because
the UART transmits each character much faster than you can single-step.
Figure 13. Transmitting BIT BANGBASH by Stepping Through the
To observe BitBangUartTransmit()’s real-time behavior, perform the
Restart the debugging session, as in the above Step 1begin Analyzing BitBangUartTransmit().
Place a breakpoint in BitBangUartTransmit(), on the following
uart_status = IORD (UART1_BASE, 2);
a breakpoint, double-click in the gray area left of the line, as shown in
the Setting a Breakpoint in BitBangUartTransmit() figure
Click the Resume button . The program runs until it reaches the
In the Variables view, right-click the uart_status variable name, point to Format,
and click Hexadecimal.
Figure 14. Setting a Breakpoint in BitBangUartTransmit()
Step over the assignment of uart_status.
The Variables view shows that the value of uart_status has changed to 0x170, as shown in the figure
Figure 15. Value of uart_status Variable is 0x170
The register map for the Altera Avalon UART core shows that the status register's
value of 0x170 means that the following bits are set:
Bit 8, the exception (E) bit
Bit 4, the transmitter overrun error (TOE) bit
The register map for the Altera Avalon UART core is described in the UART Core chapter in Embedded Peripherals
IP User Guide.
Because the software does not wait for the transmitter to be ready before writing the
final characters (GBASH), the transmitter is overrun and only the
last character, H, is transmitted, as shown in the Transmitter
Overrun figure below.
This section examines the BitBangUartReceive() function in
bit_bang_uart.c. The BitBangUartReceive() function
demonstrates receiving characters over the UART.
To analyze the BitBangUartReceive() function, perform the following
Step into the BitBangUartReceive() function.
Set a breakpoint on the while loop immediately after the
statement that reads a character into incoming_character, as
shown in the Setting a Breakpoint in BitBangUartReceive() figure
Click the Resume button.
Figure 17. Setting a Breakpoint in BitBangUartReceive()
In the following loop statement, the Nios II processor is waiting for the
receive ready (RRDY) bit to go high:
while (!(uart_status=IORD(UART1_BASE, 2) & 0x80));
Tera Term displays a prompt, as shown
in the figure below.
Figure 18. Waiting to Receive Character on UART
In the Tera Term, type * (asterisk). The debugger hits the breakpoint you
set, as shown in the figure below.
Examine the Variables view (expand it if necessary to see the incoming_character
variable). The incoming_character variable holds the asterisk
you sent through Tera Term, as shown in the figure below.
Completing these steps verifies that both the transmit and receive functions of the
UART work in polled mode.
Figure 19. incoming_character Variable Is Set to the Character Entered
on the Console
Creating Device Hardware Access Macros
After you verify the functionality of the peripheral registers with the
bit_bang_uart test software, you can replace the IORD()
and IOWR() macros and their hard-coded address parameters with
register access macros. You define the register access macros for the component,
The base address, component instance name, and interrupt request (IRQ) priority are
all available to HAL device drivers from system.h. You can write macros that
access specific peripheral registers by name, constructed from the information
provided in system.h. The macros remove the hard-coded nature of the register
accesses and instead pull the register base address information out of
system.h. This procedure allows automatic incorporation of any changes
made to the component instance base address in the hardware design. For example, to
access the UART's transmit register, the code in bit_bang_uart.c uses an
IOWR() macro with a hard-coded offset (value 1). Convert this
method to a device access macro that can adapt to changes in system.h
The Device Access Macros in my_uart_regs.h example (from
my_uart_regs.h) defines a set of device access macros and related access
masks for the UART status register.
The Altera Nios II component also provides the address construction macro
IO_CALC_ADDRESS_NATIVE(). The UART device access macros in
nios2eds/components/altera_nios2/HAL/inc/io.h use this macro.
IO_CALC_ADDRESS_NATIVE() computes the native address of a
specified peripheral register. To compute this address, it adds the second parameter
(offset) to the first parameter (peripheral base address). The offset is represented
in system bus width units, for example, 32 bits. The IORD() and
IOWR() macros translate to the Nios II assembler instructions,
ldwio and stwio, respectively.
Native addressing mode is deprecated, because Altera is moving to a direct addressing
model. New components should be written to use byte-enable signals. Write new device
drivers for these components with direct addressing macros, such as
IORD_32DIRECT(), which utilize the byte-enable signals. Offsets
for direct address macros are always represented in bytes. The bit_bang_uart
example application uses native addressing. The my_uart device driver also
uses native addressing.
For example, the following addressing macro:
IOWR(UART1_BASE, 2, 0);
translates to the following direct addressing macro:
IOWR_32DIRECT(UART1_BASE, 8, 0);
Notice that the offset specified is now eight bytes, instead of two long words.
For more details on direct addressing macros, refer to "Writing Device
Drivers" in the "Cache and Tightly-Coupled Memory" chapter in the Nios II Software Developer's Handbook.
In the BitBangUartTransmit() function in bit_bang_uart.c, you use an IORD() macro with hard-coded values to read the UART status
uart_status = IORD(UART1_BASE, 2);
You can achieve the same functionality by using the UART's device access macro:
uart_status = IORD_MY_UART_STATUS(UART1_BASE)
Using this macro makes the device driver code easier to write and easier to
understand after it is written.
Altera recommends that you create device access macros for all of your custom
component's registers, and that you create masks for each of the bits represented in
those macros. These steps result in a driver that is much easier to understand;
therefore, it is easier to verify the correctness of the device driver.
The following sections describe the existing my_uart_driver source code,
particularly the device access descriptors used to manipulate the peripheral.
my_uart_driver is based on the Altera Avalon UART device
driver, with all of the names changed to represent the “my” flavored device, as an
illustration of how you can incorporate your own device driver. All of the function
and macro names (except for the *_INIT() and
*_INSTANCE() macros) in the Altera Avalon UART device driver
have had the “altera_avalon” portion of the name replaced with “my”. For example,
ALTERA_AVALON_UART_STATUS_REG() has become
The two macros for _INSTANCE() and _INIT() are exceptions, because their names must match
the hardware component name. As a result, the my_uart_driver device driver has definitions for ALTERA_AVALON_UART_INIT() and ALTERA_AVALON_UART_INSTANCE(). These _INIT() and _INSTANCE() macros must
be defined in a header file that also matches the device name, which in this case is
altera_avalon_uart.h. This restriction is necessary for
the automatic construction of alt_sys_init.c, a generated C
source file that handles of component instance initialization.
This example shows you how to write a software device driver that fits the HAL
structure, either for manipulation of your own new device, or to override the
functionality of the provided software device driver for an Altera component or
other third party device.
The file bit_bang_uart.c demonstrates how to write source code. The source
code development progresses toward a complete device driver. Source code development
starts from direct access of the peripheral's registers and goes on to validating
the proper functioning of the Altera_Avalon_UART hardware.
bit_bang_uart.c is the first piece of software to communicate with the
Altera Avalon UART hardware.
To develop the source code that accesses a new device, perform the following
Use IOWR() macros with hard-coded address
values in main() to write values directly to
the memory-mapped UART registers. IOWR() macros
are the most direct way to access the UART hardware. Direct hardware access is
useful for validating proper functioning of the component instance, while
minimizing the potential for any software coding errors to interfere with
For more information about HAL device
driver access macros, refer to the "Accessing Hardware" section of the
"Developing Device Drivers for the Hardware Abstraction Layer" chapter in
the Nios II Software Developer's Handbook.
After developing some direct peripheral manipulation code for your custom
component, modeled after bit_bang_uart.c, write custom device access
Using the custom device access macros from the previous step, develop and test
polled routines for the init(), read(), and
Write the ISRs for interrupt driven mode. An ISR is an interrupt-driven software
routine, responding to a hardware interrupt that the peripheral generates when
it requires servicing. An interrupt-driven device driver is much more efficient
than a polled device driver, which wastes processor clock cycles by repeatedly
querying the peripheral to determine if there is work to be performed. An ISR
allows the Nios II processor to do other work while the peripheral is idle, or
while it is operating autonomously and does not require servicing by the Nios II
processor. Call alt_ic_isr_install() from
main() to install the ISRs.
After you have tested the ISR and polled routines from main(),
create and test the INIT and INSTANCE macros.
alt_sys_init.c invokes these initialization macros to initialize both
the software device driver and the hardware driver. The INIT
macro needs to initialize an alt_dev structure for the software device driver
with the tested functions for reading and writing to the UART hardware device.
The INSTANCE macro declares a structure for each component
instance to hold component instance-specific information, such as the baud rate
and the transmit and receive memory buffers. At this point, you move the
alt_ic_isr_install() calls from function
main() to the device’s initialization code, as described in
the next section.
For more information about the alt_dev
structure, refer to "Character-Mode Device Drivers" in the "Developing Device
Drivers for the Hardware Abstraction Layer" chapter of the Nios
II Software Developer's Handbook.
Understanding the Hardware-Specific INSTANCE and INIT Macros
The INSTANCE macro creates the alt_dev structure, which
represents the component instance. This macro creates unique component
instance-specific data structures.
The INIT macro must perform the following tasks:
Create mutual exclusion resources
Install the component's ISR with alt_ic_isr_register()
The my_uart_driver example driver supports both the enhanced and
the legacy HAL interrupt APIs. Supporting both APIs ensures that the driver
can be used in combination with legacy drivers supporting only the legacy
API. For information about supporting both APIs, refer to "Interrupt Service
Routines" in the "Exception Handling" chapter in the Nios
II Software Developer's Handbook.
When you integrate a HAL device driver into a BSP, the following services are enabled:
Automatic initialization with the alt_sys_init() function for
the HAL device drivers.
alt_sys_init() is an automatically
generated function. alt_sys_init() calls the
INIT and INSTANCE macros for each
component instance found in the hardware design that has a specific source
code directory structure and set of file names. The directory structure for
hardware components provided by Altera conforms to:
easiest option for a directory structure for your custom components conforms
the device driver source code files in a folder structure under
<component_folder>. The file names conform to the
This document uses the variable <Altera
installation> to represent the location where the Altera
Complete Design Suite is installed. On a Windows system, by default, that
location is c:/altera/<version number>.
HAL device-class services can access any specific HAL device in that class. For
example, Altera_Avalon_UART is a character-mode device, and so has access
to higher level services such as buffer management. HAL software device drivers
become available to the UNIX-style POSIX API for device functions such as
open() and read().
For more information about adding device drivers using the Nios II SBT, refer
to the "Integrating a Device Driver in the HAL" section of the "Developing Device
Drivers for the Hardware Abstraction Layer" chapter of the Nios
II Software Developer's Handbook.
For more information about how to integrate your own VHDL or Verilog HDL
source code as a new HAL-compatible Qsys component, and for details about the
Component Editor tool, refer to the "Creating Qsys Components" chapter of Volume 1: Design and Synthesis in the Quartus II
Software device drivers can use mutual exclusion resources to control access to any data
structure or peripheral register. Event flags and semaphores provide synchronization
and mutual exclusion services. These resources allow only one task to access a
shared piece of data at a time in a multi-threaded environment.
If the MicroC/OS-II operating system is present, its resources are used. Otherwise,
the HAL provides its own set of event flags and semaphores. The HAL event flags and
semaphores support device driver source code portability. The event flags and
semaphores do nothing in this example.
The my_uart_driver device driver creates two semaphores and one
event flag. The two semaphores are called read_lock and write_lock.
my_uart_driver uses them to control access to the transmit and
receive circular buffers. The event flag, called events, indicates to the
software device driver when data is ready to be transmitted or received.
Overview of Debugging Tools for HAL Device Drivers
The Nios II EDS and Quartus II software tools provide a variety of mechanisms for debugging
You can monitor individual component instance signals for activity with the SignalTap™ II logic analyzer. For example, you can hook up
the SignalTap II logic analyzer to the UART hardware transmit line to watch for
any activity while you write characters to the Altera_Avalon_UART
component instance through the my_uart_driver device
For information about using SignalTap II with Nios II systems, refer to AN446: Debugging Nios II Systems with the SignalTap II Embedded
You can step into the fprintf() function, stepping through the
various layers of abstraction until you reach the HAL’s invocation of
my_uart_write() function in the
my_uart_driver device driver.
You can set breakpoints in the driver’s ISRs, or set watchpoints on UART
memory-mapped registers to pause the processor when a character is
Note: Use caution when setting a breakpoint in an ISR. When you
resume, there might be problems with other devices, if they generated
interrupts that were not handled. However, sometimes the best way to debug a
specific device driver is to set a breakpoint in it. When this technique is
required, you can reset or download the software containing the device
driver again when you are done with a particular debugging
These mechanisms can help you diagnose an incorrectly configured system. For example,
if the interrupt controller receives a spurious interrupt signal, the interrupt
might not be properly handled. When interrupts are enabled after low-level system
initialization, there is no way to clear the interrupt source. As a result, the
application does not work correctly. The Nios II SBT for Eclipse debugger might even
stop communicating with the processor.
For the next set of debugging examples, you must create a new application. For
these examples, create the hello_world_my_uart application and
import it to the Nios II SBT for Eclipse. Next, regenerate the files which make up
the hal_my_uart BSP. This time, instead of commenting out the
invocation of the macros ALTERA_AVALON_UART_INSTANCE() and ALTERA_AVALON_UART_INIT(), let the alt_sys_init() function install the Altera Avalon UART
HAL device driver, after which you can inspect its operation.
The following sections show examples of placing breakpoints and watchpoints in HAL
device driver source code to analyze component instance behavior.
To create and import the hello_world_my_uart project, perform the following
Delete the generated file public.mk from the hal_my_uart BSP.
Enter the following command in the Nios II Command Shell:
Sets the software device descriptors stdout,
stderr, and stdin to
script sets up these software device descriptors by calling
Creates the software device driver, my_uart_driver, in
the <my_design>/ip/my_uart directory, and associates it
with the Altera_Avalon_UART device. The
creates the driver and associates it with its device.
Sets the software device driver called my_uart_driver
to the component instance named uart1. The
hal_my_uart.tcl script, passed to nios2-bsp, sets the
driver to uart1.
Regenerates alt_sys_init.c by invoking of
Note: You can invoke
nios2-bsp with the --debug parameter,
which displays verbose information about the construction steps in
this section. The --debug parameter can be very
useful for finding errors in the construction of the relevant Tcl
scripts and command shell scripts.
Import the hello_world_my_uart application to the Nios II SBT for Eclipse
as described in the section Importing Projects in Steps 2 through 8,
substituting the hello_world_my_uart application for the
bit_bang_uart application. It is not necessary to re-import the
Place a breakpoint at the top of the function named
my_uart_irq(), as shown in the figure below.
Restart Tera Term if it is not already running.
Create a debug configuration for hello_world_my_uart by following the
steps 3 through 8 in Debugging the bit_bang_uart Project and substituting
the hello_world_my_uart application for the bit_bang_uart
Start debugging the hello_world_my_uart application. The processor pauses
at the top of function main().
Click the Resume button. The Nios II processor pauses
at the my_uart_irq() invocation.
Figure 20. Setting a Breakpoint on my_uart_irq()
Step up to and over the following assignment of the status
In the Variables view, set the format of the status variable to hex. The status register now holds the value 0x60.
This value indicates bits 5 and 6 are set. According to the my_uart_driver register description in drivers/inc/my_uart_regs.h, these two bits indicate
transmit ready and transmit. The UART driver is in an interrupt context, ready
to transmit the first character of the string "Hello
from Nios II!".
Continue stepping through the procedure. The my_uart_irq()
function invokes my_uart_txirq() in response to a transmit
Press Resume after each character is transmitted. Stop
after the entire string “Hello from Nios II!” is
Remove the breakpoint.
After you pause the debugger in an ISR, the rest of the system is in an unknown
state, because it could not respond to other interrupt requests while paused in the
driver. Therefore, you need to start a new debugging session to perform further
In this section, you intercept the Nios II processor by placing a watchpoint on a UART
peripheral register. A watchpoint is a special breakpoint that pauses the execution
of an application whenever the value of a given expression changes. To watch for any
writes to the transmit register on the UART, you can set up a write-access
watchpoint on the register.
To set a watchpoint, perform the following steps:
Start the debugging session for the hello_world_my_uart project.
Open the Breakpoints view. If the Breakpoints view is not visible, open it
through the Window menu, by pointing to Show View and clicking
Open the menu by clicking the drop-down arrow in the upper right corner of the
view, and click Add Watchpoint (C/C++).
In the Add Watchpoint dialog box, type a value in the Expression to
watch field that equals the uart1 base value plus an
offset of one long word. This value accesses the transmit register. In the case
of the design example accompanying this application note, this value is
In the Access section, turn on Write and turn off Read.
Click OK. The Add Watchpoint dialog box closes.
Click the Resume button.
The Nios II processor executes until it writes
the first character, H. The processor writes this character
when the my_uart_txirq() function invokes the macro
IOWR_MY_UART_TXDATA(), as shown in the figure
View the transmit register value in the Eclipse Memory view.
Notice that the value changes when the debugging stops at the
Look at the call stack in the upper left corner of the
Nios II Debug perspective. The call stack records each call leading up to
this point, including each function invoked to process the transmit
function calls my_uart_irq().
Figure 21. Setting Watchpoints in the UART's Transmit Register
Reducing Driver Code and Memory Footprint
The Nios II SBT provides BSP settings to configure the HAL. You manipulate these settings
through the BSP Editor, or with the --set parameter to
The Nios II SBT settings are described in "Settings" in the "Nios II Software
Build Tools Reference" chapter in the Nios II Software Developer's
The Reduced device drivers and Lightweight device driver API options
are of particular interest, because they reduce the code and data footprint at the
expense of device driver functionality. Additionally, they set
#define parameters that the my_uart_driver
device driver needs to examine and handle. The setting values are documented in the
summary.html file generated by nios2-bsp, in
The Reduced device drivers option generates a #define statement for
ALT_USE_SMALL_DRIVERS. To turn on this option, set
hal.enable_reduced_device_drivers to true. Setting this option has the
following effects on correctly-written UART device drivers:
Sets #define ALT_USE_SMALL_DRIVERS
Activates polled mode only for the UART device
Disables floating-point support in printf() and
Configures drivers to ignore flow control.
The figure below shows excerpts from the summary.html file generated by the
SBT for a BSP with reduced device drivers enabled.
Figure 22. hal.enable_reduced_device_drivers in summary.html
Note: For more information about the
Reduced device drivers option, refer to "Reducing Code Footprint" in the "Developing
Programs Using the Hardware Abstraction Layer" chapter and to "Reducing Code
Footprint" in the "Developing Device Drivers for the Hardware Abstraction Layer"
chapter of the Nios II Software Developer's Handbook.
The Lightweight device driver API option generates a #define statement
for ALT_USE_DIRECT_DRIVERS. To turn on this option, set
hal.enable_lightweight_device_driver_api to true. Setting this option has
the following effects on correctly-written UART device drivers:
Sets #define ALT_USE_DIRECT_DRIVERS.
Eliminates the option of using a file system. File descriptors cannot be
Disables the open() and close() functions. Attempting to call these
functions generates a link time error.
Causes direct calls to your UART device driver using macros, bypassing the
device manipulation function invocations normally accessed through the file
descriptor structure. The macros are defined in alt_driver.h, which is in
the BSP folder, in HAL/inc/sys.
For example, a call to alt_putstr() is normally treated as a call to
the run-time library function fputs(). With lightweight device
drivers enabled, alt_putstr() is translated to
ALT_DRIVER_WRITE() (defined in alt_driver.h) and
state-obtaining macros. The ALT_DRIVER_WRITE() macro in turn calls
the ALT_DRIVER_FUNC_NAME() macro (also defined in
alt_driver.h), and eventually
ALTERA_AVALON_UART_WRITE(), which is defined in the
altera_avalon_uart_write.c driver file for the UART, where the UART is
defined for stdout. Calling
write) returns ALTERA_AVALON_UART_WRITE.
ALT_USE_DIRECT_DRIVERS is dual-purposed in the
my_uart_driver device driver. It provides a convenient way to
map the names of the ALTERA_AVALON_UART_INIT() and
ALTERA_AVALON_UART_INSTANCE() macros, which are tied to the
component name, to names that are specific to the my_uart_driver
device driver. This setting of ALT_USE_DIRECT_DRIVERS already maps
ALTERA_AVALON_UART_INSTANCE() to macros that change based on
the setting of ALT_USE_DIRECT_DRIVERS in
altera_avalon_uart.h. At the same time, the
ALTERA_AVALON_UART_INSTANCE() macros have the
ALTERA_AVALON portion of their names change to
MY_UART. The resulting four macro name mappings are
The figure below shows excerpts from the summary.html file generated by the
SBT for a BSP with lightweight device drivers enabled.
Figure 23. hal.enable_lightweight_device_driver_api in
Note: For more information about the
Lightweight device driver API option, refer to "Reducing Code Footprint" in the
"Developing Programs Using the Hardware Abstraction Layer" chapter and to "Reducing
Code Footprint" in the "Developing Device Drivers for the Hardware Abstraction
Layer" chapter of the Nios II Software Developer's
This section discusses the crucial topics of interrupt latency and determinism. For the
purposes of this discussion, you need to be familiar with the following
Interrupt latency—The difference between the time that a component instance
asserts an interrupt and the execution of the first instruction at the interrupt
vector address. This instruction is typically part of the interrupt funnel,
rather than the interrupt handler itself.
Interrupt response time—The time elapsed between the event that causes the
interrupt and the execution of the handler.
Determinism—An attribute of a piece of source code that is guaranteed to execute
within a fixed amount of time. Overall interrupt latency impacts the
deterministic behavior for all source code in the system for which interrupts
are not disabled.
For more information, refer to "Nios II Exception Handling Overview" section in
the "Exception Handling" chapter of the Nios II Software
To minimize interrupt latency, thus directly improving system determinism, follow
In the software ISR, perform the minimum processing necessary to clear the
Complete noncritical-section interrupt processing outside of the interrupt
context. If your software is based on an operating system, a high priority task
can be pending on an event flag. The ISR posts to the event flag, notifying the
task to complete interrupt processing.
Use an External Interrupt Controller (EIC), such as the VIC used in the hardware
design for this application note.
If it is not possible to use an EIC, you can improve the performance of the internal
interrupt controller (IIC) by using the interrupt vector custom instruction.
The interrupt vector custom instruction is not compatible with the EIC interface. The
performance of the IIC with the interrupt vector custom instruction is generally
inferior to the performance of the VIC.
For information about using the EIC and shadow register sets, or the interrupt
vector custom instruction, refer to "Improving ISR Performance" in the "Exception
Handling" chapter in the Nios II Software Developer's
Handbook and "Exception and Interrupt Controllers" section in the "Processor
Architecture" chapter in the Nios II Processor Reference
Handbook. For information about tightly coupled memory, refer to the Using Tightly Coupled Memory with the Nios II Processor
For details of the interrupt vector custom instruction implementation, refer to the
Exception and Interrupt Controllers section in the Processor
Architecture chapter of the Nios II Processor Reference Handbook.
For more information about tightly-coupled memories, refer to the Tightly-Coupled
Memory section in the Processor Architecture chapter of the Nios
II Processor Reference Handbook.
Restrict the use of synchronization resources to post-function calls. Do not call the
following types of functions from within an ISR:
Functions and macros, such as ALT_SEM_PEND(), that explicitly
wait for a resource
Library functions, such as printf(), that might wait for a
Other functions that wait for resources
Calling these types of functions from an ISR can have serious consequences, from the
destruction of overall system latency to complete system deadlock.
Avoid using alt_irq_interruptible(), which can enable ISR nesting,
but is likely to worsen interrupt latency (unless the ISR is abnormally long)
because of the interrupt context switch overhead. If the ISR is lengthy, instead of
making it interruptible, consider moving much of the less time-critical processing
of the interrupt outside of the ISR to a task. Write the ISR to do only as much as
is required to clear the interrupt and capture state so that the hardware can
proceed, and then signal a task to complete processing of the interrupt request.
The Altera logging functions are a very useful mechanism for debugging device drivers. Altera
logging uses macros to bypass the HAL driver and access the peripheral directly. As
a result, software can print debugging messages during the boot process before the
devices are initialized.
You do not need to regenerate the .sopcinfo file in Qsys or recompile the .sof image
in the Quartus II software.
Another advantage of Altera logging is that you can disable it without modifying your
source code. You simply change a BSP setting and recompile, leaving zero residual
impact in the compiled and linked application .elf file. All the Altera logging
mechanisms are macros, and so the compiler eliminates them when they are not
enabled. As a result, you can leave these calls to obtain debugging information in
the source code for your released final product, with no loss of speed or code
memory space. Compiling with Altera logging disabled creates a .elf file
identical to a .elf compiled from source code without the Altera logging
When Altera logging is enabled, the behavior of the application might be less
deterministic, due to the collection and output of Altera logging messages.
Altera Logging Usage
You enable Altera logging with the hal.log_port
BSP setting by setting the logging level with ALT_LOG_FLAGS. This setting causes the SBT to define ALT_LOG_ENABLE in public.mk.
You can add Altera logging diagnostic messages to your code by invoking
ALT_LOG_PRINTF(), a macro that handles most
printf() formatting options.
Writes to the Altera logging device are blocking. Therefore, when
hal.log_port is set to a component instance of type
altera_avalon_jtag_uart, you must run an application to accept the Altera
logging output in order for the Nios II application to complete initialization.
Otherwise, the application pends on an ALT_LOG_PRINTF() statement
until the Altera logging device's output buffer can be drained.
You can handle the JTAG UART logging output in either of the following ways:
Run your application in the Nios II SBT for Eclipse. JTAG UART logging output
appears in the Nios II Console view.
Run your application from the Nios II Command Shell, with the nios2-download
command. From another Nios II Command Shell, run nios2-terminal to accept the
JTAG UART logging output.
You can disable the Altera logging feature by setting the
hal.log_port setting in the BSP to none. This is the default
setting. In the example BSP, hal_my_uart, the
create-this-bsp script initially sets
hal.log_port to jtag_uart. You can enable and
disable it in the BSP Editor. After regenerating a BSP, you can check the value of
hal.log_port in summary.html, located in the
Disabling Altera logging has the effect of leaving ALT_LOG_ENABLE
undefined in bsp/hal_my_uart/public.mk. When this feature is disabled, the
application does not pend on ALT_LOG_PRINTF() statements, even when
no terminal capable of receiving Altera logging output is connected.
You can leave your ALT_LOG_PRINTF() debugging statements in the
final source code version intended for production release, provided you set
hal.log_port to none. With this setting, the definition of
ALT_LOG_PRINTF() is empty, and so the compiler effectively
removes these macro invocations. They have no impact on code footprint or
performance unless you re-enable Altera logging.
For complete information about using the Altera logging functions, refer to
"Using Character-Mode Devices" section in the "Developing Programs Using the
Hardware Abstraction Layer" chapter in the Nios II Software
The example applications, bit_bang_uart and hello_world_my_uart, and the example
BSP, hal_my_uart, use the Altera logging functions.
To see an example of Altera logging, run or debug hello_world_my_uart. You can
see the logging messages in the Nios II Console view, as shown in the figure
You enable Altera logging by setting the hal.alt_log_flags BSP
setting to 3. Refer to the create-this-bsp script in the
bsp/hal_my_uart directory for an example.
Figure 24. Altera Logging Output for hello_world_my_uart.c with
By dissecting the Altera_Avalon_UART component and the
my_uart_driver HAL software device driver, and examining the UART status register
bit manipulation in detail, you gain insight into the HAL device driver development
process. You now have the tools necessary to develop and debug at this low level of
the system, close to the hardware. Your set of tools includes analysis and debugging
techniques for tackling even the most elusive and deterministic embedded software
With your new knowledge about the HAL's facilities, and with the array of techniques
for debugging and development described in this document, you are now better
prepared to write HAL software device drivers for your own embedded system's
components. You can also apply these tools at higher levels in the software
Document Revision History
Table 1. Document Revision History
NEEK changed to Nios II Cyclone V E FPGA Development
nios2-terminal changed to Tera Term
Updated Software Requirements for the Driver Example
Updated Debugging the bit_bang_uart Project section
Updated Asterisk Transmitted from Memory-Mapped Register
Updated Characters Transmitted by Manipulating UART Register
Updated Stepping Over the BitBangUartTransmit() Function
Updated Transmitting BIT BANGBASH by Stepping Through the
Updated Transmitter Overrun image
Updated Waiting to Receive Character on UART image
and design example for Qsys
Update for the Nios II Software Build Tools for Eclipse
Update the examples to run on the NEEK
Update the design example to use the EIC and VIC
Update the software examples to use the HAL enhanced
Nios II version 8.0 upgrade, adaptation of the
Altera_Avalon_UART device driver to become the
my_uart_driver device driver
Nios II Software Build Tools conversion for my_uart
IP, hal_my_uart BSP, and bit_bang_uart and