HAL Port — Implementation Details
This documentation explains how the eCos HAL specification has been mapped onto Nios II hardware and should be read in conjunction with that specification. It should be noted that the architectural HAL is usually complemented by a hardware design HAL and a platform HAL, and those may affect or redefine some parts of the implementation.
The architectural HAL provides header files
cyg/hal/arch.inc. These header files
export the functionality provided by all the Nios II HALs for a
given target, automatically including headers from the lower-level
HALs as appropriate. For example the platform HAL may provide
containing additional I/O functionality, but that header will be
automatically included by
cyg/hal/hal_io.h so there is no need to
include it directly.
One header file is worth a special mention:
pkgconf/nios2_hwconfig.h. This file
is provided by the hardware design HAL and contains definitions such
as base addresses and interrupt vectors. It is automatically
included and used by the architectural HAL headers, but its contents
may prove useful to application developers.
The architectural HAL assumes that the Nios II cpu in the hardware design uses 32-bit arithmetic, little-endian byte ordering, and software floating point.
The architectural HAL provides a default implementation of the
low-level startup code which will be appropriate in nearly all
scenarios. For a ROM startup this includes clearing the instruction
and data caches, if present, and copying initialized data from flash
to RAM. For all startup types it will involve zeroing bss regions and
setting up the stack and the general C environment. It may also
include installing the exception vector code at the desired location
as well as copying code and data to on-chip RAM or external SRAM as
required. The platform HAL can override or extend this as required.
The code assumes that all of flash is directly accessible. Platform
HALs may override this as required, for example when booting from a
serial flash rather more work is needed to copy data from flash to
RAM. More information on the low-level startup code can be found in
the source file
The architectural HAL also implements the next stage of the startup
code, including initializing the VSR and virtual vector tables,
setting up HAL diagnostics, and invoking C++ static constructors,
prior to calling the first application entry point
cyg_start. This code resides in
The current code assumes that there is no memory management or MMU and hence will not perform any MMU initialization.
Interrupts and Exceptions
The architectural HAL provides default implementations of
HAL_QUERY_INTERRUPTS. These just involve simple
manipulation of the
status control register.
Similarly there are default implementations of the interrupt
HAL_QUERY_INTERRUPT_MASKED macros. These are
slightly more complicated to cope with nested interrupt scenarios,
involving a shadow mask as well as the
HAL_INTERRUPT_ACKNOWLEDGE is a
no-op because the hardware has no need for clearing an interrupt
centrally. Instead interrupts must be acknowledged within each device
as appropriate, using device-specific code.
HAL_INTERRUPT_CONFIGURE is a no-op. This macro is
normally only relevant to GPIO interrupts, affecting level versus edge
triggering, and on a Nios II an entire GPIO unit generates a
single interrupt but the various inputs to that port can be controlled
individually. Instead it is up to application code to set the various
registers within each GPIO unit appropriately.
HAL_INTERRUPT_SET_LEVEL is also a no-op. However
the architectural HAL does support prioritized nested interrupts when
enabled. The implementation assumes that interrupt vector 0 has the
highest priority, down to interrupt vector 31 as the lowest. In other
words, assuming nested interrupt support is enabled, if the cpu is
busy processing an interrupt from the device attached to interrupt
vector 9 and an interrupt 0 occurs then the latter will be handled
immediately and the processing of vector 9 resumes later. Since the
assignment of interrupt vectors to devices in SOPC Builder is
arbitrary this gives full flexibility without the overheads of
managing priorities in software.
Interrupt handlers are managed by a simple table
cyg_hal_interrupt_handlers so the implementation of
HAL_INTERRUPT_IN_USE is straightforward.
By default interrupt handlers run on a separate interrupt stack. This
saves memory because there is no need to allow for interrupt
processing overhead on every thread stack. However switching to the
interrupt stack requires a number of extra instructions so increases
the interrupt latency. If the latter is more important than memory
should be disabled.
That leaves VSR management and exceptions. On the Nios II
interrupt and exception processing is somewhat simpler than on most
other architectures. Typically the cpu indirects through a table in
memory, the VSR table, with the table index depending on the interrupt
vector or the exception being thrown. The eCos macro
HAL_VSR_SET updates an entry in the table,
allowing applications to take over completely certain interrupt
sources and process them as quickly as possible, bypassing the
overheads of the default general-purpose VSR handler used by eCos. For
example a custom VSR written in assembler could save only a few
registers before manipulating the hardware, whereas the
general-purpose VSR handler needs to save much of the cpu state before
calling the application's interrupt handler written in C. The net
result is reduced interrupt latency for critical interrupts, at the
cost of more complicated application code.
The Nios II implementation is very different. When an interrupt or exception occurs the cpu jumps to a fixed location in memory defined in the hardware design, the exception vector. The code at that location needs to determine whether it was invoked as the result of an interrupt or a processor exception. There is no hardware equivalent of the VSR table. eCos needs to provide the implementation of the code at the exception vector and there is no simple way for applications to provide a customized version. That makes it difficult for an application to handle critical interrupts with a minimum latency. It also causes other complications, for example it makes it difficult for a RAM startup eCos application to handle interrupts while the gdb stubs inside RedBoot handle exceptions including breakpoints.
To avoid these problems, the Nios II architectural HAL implements
a VSR table in software. The code at the exception vector simply
indirects through slot 0 of the VSR table, which involves a three
instruction overhead compared with a more conventional implementation.
Applications needing very fast handling of critical interrupts can use
HAL_VSR_SET to install a custom handler in slot
0. That handler can check whether or not a critical interrupt is
pending and process it immediately, Otherwise it can chain to the
original VSR handler. The overall effect is to provide a mechanism for
very fast handling of certain interrupts, at the cost of an extra
three instructions for ordinary interrupts which are handled by
interrupt handlers written in C.
The default handler for VSR 0 checks whether or not any interrupts are
pending. If so then it saves the current cpu state, or the minimum
subset thereof if
is enabled, switches to the interrupt stack if necessary, and invokes
the appropriate interrupt handler installed via
HAL_INTERRUPT_ATTACH or the higher-level
If the default handler for VSR 0 determines that no interrupt is pending then it must have been invoked as the result of a processor exception. The cpu provides only minimal support for detecting the nature of the exception. A breakpoint trap can be detected by examining the instruction that caused the exception. Anything else is treated as an illegal instruction. Both exceptions are processed by indirecting through further slots in the VSR table, thus allowing the gdb stubs code inside RedBoot to handle exceptions inside an eCos application. It should be noted that this mechanism is critically dependent on reliable interrupt reporting. If it is possible for an interrupt line to glitch, causing an interrupt but leaving the ipending register clear before the VSR 0 handler reads it, then this will be interpreted as an illegal instruction exception. Conceivably this can also affect device drivers if a write to a hardware register may result in an interrupt being cleared in a couple of cycles just as that interrupt is about to happen.
The full implementation of the interrupt and exception handling code
can be found in
src/nios2asm.S, and that code can
be used as the starting point for custom application VSRs.
Stacks and Stack Sizes
values for minimal and recommended thread stack sizes,
CYGNUM_HAL_STACK_SIZE_TYPICAL. These values depend
on a number of configuration options. Specifically if the use of a
separate interrupt stack
disabled to reduce interrupt latency then thread stacks have to be
rather larger to cope with the interrupt processing overhead. If
CYGSEM_HAL_COMMON_INTERRUPTS_ALLOW_NESTING are also
enabled then thread stacks must be much larger.
The Nios II architectural HAL always provides a separate
stack to run the startup code and for exception processing. This stack
will also be used for interrupts if
enabled, and its size is determined by
Thread Contexts and setjmp/longjmp
cyg/hal/hal_arch.h defines a
thread context data structure, the context-related macros, and the
longjmp support. The
implementations can be found in
The context structure is straightforward, containing space for the
integer registers and the status, ienable and ipending registers. Any
floating point arithmetic is assumed to be implemented in software. A
single data structure is used for the thread context in all eCos
configurations. However some fields will only be used in certain
configurations, for example when
is disabled. The use of a single data structure avoids complications
when debugging a RAM startup application on top of a ROM RedBoot
because both need to use the same structure.
The architectural HAL provides an assembler implementation of
HAL_LSBIT_INDEX, optimized for faster context
switching to higher priority threads.
HAL_MSBIT_INDEX uses a straightforward C
implementation since it is not performance-sensitive.
Idle Thread Processing
The Nios II instruction set does not include an idle instruction so the idle thread always simply spins, rather than suspend the cpu until an interrupt occurs.
The architectural HAL provides a default implementation of the various
system clock macros such as
These macros assume that the hardware design implements the system
clock using a simple Avalon timer. A platform HAL can provide an
alternative implementation if necessary but that is unlikely ever to
be necessary because including an Avalon timer in the design is
generally straightforward. The timer may be designed with either a
fixed period, typically 10 milliseconds to give a 100Hz system clock,
or with a variable period in which case there will a configuration
CYGNUM_HAL_RTC_PERIOD to control the clock
frequency. Ideally the system clock should be designed with the
readable snapshot option enabled. Otherwise the HAL will not be able
to provide the
HAL_CLOCK_READ macro and there
will be no support for clock timings with a finer granularity than the
interval between interrupts.
The various I/O macros for accessing hardware registers such as
HAL_READ_UINT8 are implemented using the
ldbuio and related instructions. This ensures that
all I/O accesses bypass any data cache that may be included the
The hardware design may include instruction and data caches, and there
is some control over parameters such as the cache sizes and the line
defines those cache macros which are appropriate for the current
hardware design, typically the
The hardware does not allow the caches to be enabled or disabled
IS_ENABLED macro always returns a 1 and
macros are never defined. Note that this may confuse some existing
code which assumes that these macros are always available.
CYGARC_UNCACHED_ADDRESS which assume that the cpu
only addresses 31 bits worth of address space, and that addresses with
the top bit set access the same memory locations as those with the top
bit clear, but bypassing the cache. This is the default behaviour for
Nios II processors. The header file also provides a
HAL_MEMORY_BARRIER macro which issues a
sync instruction to cause pending memory operations
The architectural HAL will generate the linker script for eCos
applications. This involves the architectural file
src/nios2.ld and a
memory layout file, typically provided by the platform HAL but using
some definitions from the hardware design HAL. It is the
.ldi file which places code and data in
the appropriate places for the startup type, but most of the hard work
is done via macros in the
nios2.ld file. This
includes macros for placing code and data in on-chip RAM as well as
external flash and SDRAM, using linker sections
.iram_bss. Code should only be placed in on-chip
RAM if the hardware design includes a connection between the RAM and
the cpu's instruction master port.
By default the architectural HAL provides a diagnostics and debug
channel using the first uart in the hardware design. If the design
does not include any uarts then all diagnostics output will instead be
discarded. The configuration option
CYGIMP_HAL_NIOS2_DIAGNOSTICS_PORT can be used to
select discard mode even when a uart is available. If the hardware
includes an ethernet device then debugging is still possible over the
network. Alternatively when debugging via JTAG it is possible to
direct the diagnostics output to a gdb hwdebug file I/O channel. By
default this will also discard diagnostics output. However if the
application is running inside a gdb session and the gdb
set hwdebug command has been used then the
diagnostics will be output via gdb. Platform HALs may implement
alternative diagnostics facilities.
The Nios II architectural HAL does not provide any SMP support.
The architectural HAL provides basic support for gdb stubs. Due to
a conflict between jtag and stubs gdb support, breakpoints are always
implemented using a fixed-size list of breakpoints, as per the
CYGNUM_HAL_BREAKPOINT_LIST_SIZE. A hardware design
may include hardware breakpoint support but these are not accessible
to the gdb stubs code, only via jtag. Hence if a debug session
requires the use of hardware breakpoints, for example when debugging
code in flash, a jtag-based debug solution must be used instead of gdb
cyg/hal/hal_intr.h provides a
simple implementation of the
using a busy loop. It requires that the hardware design HAL provides a
HAL_NIOS2_DELAY_US_LOOPS appropriate to
the cpu speed and the absence or presence of the instruction cache.
If the hardware design includes a system id register then all RedBoot builds will include some extra initialization code checking the register's current value against the one specified by the hardware design HAL, reporting any mismatches. This helps to guard against accidentally running the wrong build of RedBoot or the wrong hardware design. Note that if there is a serious incompatibility between the two then system bootstrap may fail long before this check gets to run, or the diagnostics channel may be inoperable preventing the warning from reaching the user.
The architectural HAL provides the support needed by the gprof
profiling package. This includes the
needed for callgraph profiling and
hal_enable_profile_timer for timer-based
profiling. The latter can be implemented in two ways. If the hardware
design includes a dedicated Avalon timer labelled
“profiling” then this will be used.
Otherwise the sys_clk timer will be used for profiling as well as
for the main system clock. Overloading the system clock in this way is
less desirable because it means the profiling sampling is likely to
miss any code that runs after clock events.
Otherwise the Nios II architectural HAL only implements the functionality provided by the eCos HAL specification and does not export anything extra.
|2023-08-15||eCosPro Non-Commercial Public License|