AN-572
a
One Technology Way • P.O. Box 9106 • Norwood, MA 02062-9106 • 781/329-4700 • World Wide Web Site: http://www.analog.com
APPLICATION NOTE
Overlay Linking on the ADSP-219x
By David Starr
OVERVIEW
This applications note is for software designers starting
an ADSP-219x overlay design. Using this note and the
information in the Linker and Utilities Manual for ADSP21xx Family DSPs the following may be done:
Divide a program into overlays.
Write LDF files for simple and overlay links.
Write an overlay manager.
HARVARD VS. PRINCETON ARCHITECTURE AND THE
LINKER
Early in the history of computing, the modern design of
one memory to hold both data and program instructions
acquired the name “Princeton.” Such a machine is flexible because programs with lots of code and little data
work, as well as programs with little code and lots of
data. It’s simple in that address is the only memory
parameter of interest. Performance is adequate, even
though it takes two memory cycle times to fetch data
from memory: one cycle to fetch the instruction and the
second cycle for the instruction to fetch the data.
A faster architecture has separate program and data
memories. This is “Harvard” architecture, which improves
speed because the machine can fetch program instructions and data in parallel; a data fetch from memory can
thus be accomplished in a single memory cycle. All
Analog Devices Inc. DSPs are Harvard architecture.
They possess a separate program and data memory for
speed and, for extra speed, data can be kept in program
memory as well as data memory and there are instructions to fetch data from both memories simultaneously.
To use the full memory bandwidth, the ADSP-219x family possesses an instruction cache. Instructions come
from cache, freeing up the program memory bus for data
fetch from program memory. This permits multiply
accumulate instructions to fetch a multiplier and a multiplicand, compute a product, and add the new product
to the accumulator, all in a single instruction. In this
case, it is important to locate the multiplier data in program memory and the multiplicand data in data
memory. It is the programmer’s responsibility to assign
data buffers to memory. This is done with instructions to
the linker. Having a number of memories to deal with
makes the DSP linker somewhat more complex than
linkers for Princeton architecture machines.
In addition, each DSP project must fit into a different
memory arrangement. Different DSP boards have different amounts of memory, located at different addresses.
The ADSP-219x family supports an external memory
interface, allowing rather large amounts of memory, but
at a penalty in speed. Internal memory is ten times faster
than external memory, so it may be desirable to keep
large amounts of program code in external memory
swap parts of it to internal memory for speed in execution. Such a program is said to run in “overlays.”
For code reuse and portability, a program should not
require modification to run in different machines or in
different locations in memory. So the C or assembler
source code does not specify the addresses of either
code or data. Instead, the source code assigns names to
sections
time, leaving it up to the linker to assign physical
memory addresses to each section of code or data. The
goal is to make the source program’s position independent and let the linker assign all the addresses.
The address assignment task has become too much to
handle with just command line switches to the linker.
ADI has devised a “linker programming language” to
allow full control of “what goes where.” Each DSP software project consists of one or more source code
modules and a “linker description file” (.ldf file).
At link time, the linker follows directions in the .ldf file to
place code and data at the proper addresses. The “linker
programming language” is quite powerful and fairly
complex. The manual for it is as thick as the classic “C
Programming Language” by Kernighan and Ritchie. The
.ldf file can select which compiler libraries are searched,
which C run time start-up code is linked, and can supply
all command line arguments and override all defaults. A
default .ldf file is shipped with each release of the VDSP
tools, and kept in the VDSP directories. VDSP allows an
.ldf file as part of a project, as does a C source file. If a
project does not contain an .ldf file, the linker will use
of code and/or data at compile or assembly
REV. 0
© Analog Devices, Inc., 2001
AN-572
the default one. If an .ldf file is included with the project,
the project will link the same way each time, even if the
default .ldf is edited or replaced by a new release of
the VDSP tools, or the project is rebuilt on another
computer with another version of the default .ldf file.
PHYSICAL MEMORY BLOCKS VS. LOGICAL MEMORY
SECTIONS
What is the difference between a block and a section? A
block is a named piece of the target system’s memory.
Blocks have attributes of address, size, and width (16-bit
vs. 24-bit). Sections are named pieces of code or data.
.SECTION directives in the source file mark code/data as
belonging to one named section or another. Typical
.SECTION names are “program,” “dm_data,” and
“pm_data.” Locate code and data at the desired address
by putting code sections into memory blocks. The linker
programming language uses “>” as the “put into” operator, (not to be confused with C’s ‘greater than’ operator).
The linker programming language has statements to
define memory blocks, define code/data sections and
marry the two together. When absolute addressing is
needed (say to place an interrupt vector or to access
memory-mapped I/O devices) define a memory block at
the desired absolute address. Place the code or data
into a .SECTION in the source file, and put the .SECTION
into the memory block with the “put into” operator (“>”)
in the .ldf file. The C compiler automatically places
.SECTION directives into its output files. The compiler
uses a series of default section names described in the
compiler documentation.
This simple .ldf file defines one memory block (ram_blk)
starting at 0 and ending at 64K. It defines one section
named dontcare0 containing all the code in module
frodo.doj, and puts the section into ram_blk, which
starts at address 0. The section is named “dontcare0” to
indicate that this name does NOT link to anything important. The name shows up in the link map but does not
control which code goes where. This simple example
has only one code module (frodo.doj). If the program
had two modules, the INPUT_SECTIONS statement
would look like:
dontcare0 {INPUT_SECTIONS
(frodo.doj,bilbo.doj(program)} > ram_blk
A practical program would, of course, contain a significant number of modules, and listing them in the
INPUT_SECTIONS statement would be tiresome. The
linker programming language provides a macro facility
using syntax like the UNIX make program. A macro
$OBJ is defined as:
$OBJ =
main.doj,frodo.doj,bilbo.doj,merry.doj,pippin.doj,
sam.doj,… …. ….. gandalf.doj;
and the INPUT_SECTIONS statement might look like:
dontcare0 {INPUT_SECTIONS ($OBJ (program)}
> ram_blk
and, finally, to simplify matters, the linker has a
convenient and automatically defined macro
$COMMAND_LINE_OBJECTS, which is written:
THE LDF FILE
LDF files have two major parts (each delimited by curly
brackets), the MEMORY part, and the PROCESSOR part.
Statements inside the MEMORY part create memory
blocks. The PROCESSOR part contains at least a SECTIONS command. The SECTIONS command uses
more curly braces to enclose a number of statements.
For example
MEMORY
{
ram_blk {TYPE (PM RAM) START (0x000000)
END (0x00FFFF) WIDTH 24)}
}
PROCESSOR CORE1
{
SECTIONS
{
dontcare0 {INPUT_SECTIONS
(frodo.doj (program))} > ram_blk
}
}
dontcare0 {INPUT_SECTIONS
($COMMAND_LINE_OBJECTS(program)} > ram_blk
In this way, modules can be added to the project without
editing the .ldf file by hand each time a new module
is added.
What does that (program) name do? It refers back to
the source file. Assembly language module frodo.dsp
looks like:
.section/CODE program
ax0 = dm(this_and_that);
I0 = input_buffer;
….
….
rts;
.section/DATA dm_data;
.var this_and_that;
.var input_buffer[100];
….
–2–
REV. 0
AN-572
This (program) means take only the code in the source
module .SECTION-named program. In this example,
ram_blk will receive just the program code and NOT the
.var- defined data buffers. The linker matches the name
in () with the names in the .SECTION pseudo ops in the
source code. In this way code from one source file can
be split into different sections, which are put into different memory blocks.
A SIMPLE ASSEMBLER PROGRAM LINK
ARCHITECTURE(ADSP-219x)
// Libraries from the command line are included in COMMAND_LINE_OBJECTS.
$OBJECTS = $COMMAND_LINE_OBJECTS ;
MEMORY
{
vector_blk { TYPE(PM RAM) START(0x000000) END(0x0000ff) WIDTH(24) }
program_blk { TYPE(PM RAM) START(0x001000) END(0x06FFF) WIDTH(24) }
dmdata_blk { TYPE(DM RAM) START(0x008000) END(0x009fff) WIDTH(16) }
pmdata_blk { TYPE(PM RAM) START(0x07000) END(0x07FFF) WIDTH(16) }
}
PROCESSOR CORE1
{
LINK_AGAINST( $COMMAND_LINE_LINK_AGAINST)
OUTPUT( $COMMAND_LINE_OUTPUT_FILE )
One final note. The linker programming language syntax does NOT use ; as a statement terminator as does
the assembler. Most linker programming language statements have a variable number of arguments enclosed in
curly brackets. The closing curly bracket ends the statement. The syntax ignores white space (tab, space, CR,
LF) in the same way that C does. The $MACROs, however, require a terminating semicolon.
SECTIONS
{
dont_care_0 /* reset vector */
{
INPUT_SECTIONS( $OBJECTS(IVreset))
} > vector_blk
dont_care_1 /* code space */
{
INPUT_SECTIONS( $OBJECTS(program) )
} >program_blk
dont_care_2 /* data memory data */
{
INPUT_SECTIONS( $OBJECTS(dm_data) )
INPUT_SECTIONS( $OBJECTS(dmdata) )
} >dmdata_blk
dont_care_3 /* Program memory DATA */
{
INPUT_SECTIONS( $OBJECTS(pm_data) )
INPUT_SECTIONS( $OBJECTS(pmdata) )
} >pmdata_blk
} // end SECTIONS
} // end PROCESSOR
REV. 0
–3–