7 LINKER SCRIPT LANGUAGE

This chapter contains the following sections:

Introduction

Structure of a Linker Script File

Syntax of the Linker Script Language
Identifiers
Expressions
Built-in Functions
LSL Definitions in the Linker Script File
Memory and Bus Descriptions
Architecture Definition
Derivative Definition
Processor Definition and Board Specification
Section Placement Definition

Expression Evaluation

Semantics of the Architecture Definition
Defining an Architecture
Defining Internal Busses
Defining Address Spaces
Mappings

Semantics of the Derivative Definition
Defining a Derivative
Instantiating Core Architectures
Defining Internal Memory and Busses

Semantics of the Board Specification
Defining a Processor
Instantiating Installed Processors
Defining External Memory and Busses

Semantics of the Section Layout Definition
Defining a Section Layout
Creating and Locating Groups of Sections
Creating or Modifying Special Sections
Conditional Group Statements

7.1 Introduction

To make full use of the linker, you can write a script with information about the architecture of the target processor and locating information. The language for the script is called the Linker Script Language (LSL). This chapter first describes the structure of an LSL file. The next section contains a summary of the LSL syntax. Finally, in the remaining sections, the semantics of the Linker Script Language is explained.

The TASKING linker is a target independent linker/locator that can simultaneously link and locate all programs for all cores available on a target board. The target board may be of arbitrary complexity. A simple target board may contain one standard processor with some external memory that executes one task. A complex target board may contain multiple standard processors and DSPs combined with configurable IP-cores loaded in an FPGA. Each core may execute a different program, and external memory may be shared by multiple cores.

LSL serves two purposes. First it enables you to specify the characteristics (that are of interest to the linker) of your specific target board and of the cores installed on the board. Second it enables you to specify how sections should be located in memory.

7.2 Structure of a Linker Script File

A script file generally consists of the following parts:

The architecture definition (required)

In essence an architecture definition describes how the linker should convert virtual addresses into physical addresses for a given type of core. If the core supports multiple address spaces, then for each space the linker must know how to perform this conversion. In this context a physical address is an offset on a given internal or external bus. Additionally the architecture definition contains information about items such as the (hardware) stack and the interrupt vector table.

Typically an architecture definition is written by Altium and should not be changed by you unless you also modify a core's hardware architecture. If the LSL file describes a multi-core system an architecture definition must be available for each different type of core.

See section 7.5 , Semantics of the Architecture Definition for detailed descriptions of LSL in the architecture definition.

The derivative definition (required)

The derivative definition describes the configuration of the internal (on-chip) bus and memory system. Basically it tells the linker how to convert offsets on the busses specified in the architecture definition into offsets in internal memory. A derivative definition must be present in an LSL file. Microcontrollers and DSPs often have internal memory and I/O sub-systems apart from one or more cores. The design of such a chip is called a derivative. Altium provides LSL descriptions of supported derivatives, along with "SFR files", which provide easy access to registers in I/O sub-systems from C and assembly programs. When you build an ASIC or use a derivative that is not (yet) supported by the TASKING tools, you may have to write a derivative definition.

See section 7.6 , Semantics of the Derivative Definition for a detailed description of LSL in the derivative definition.

The processor definition

The processor definition describes an instance of a derivative. Typically the processor definition instantiates one derivative only (single-core processor). A processor that contains multiple cores having the same (homogeneous) or different (heterogeneous) architecture can also be described by instantiating multiple derivatives of the same or different types in separate processor definitions.

See section 7.7 , Semantics of the Board Specification for a detailed description of LSL in the processor definition.

The memory and bus definitions (optional)

Memory and bus definition are used within the context of a derivative definition to specify internal memory and on-chip busses. In the context of a board specification the memory and bus definitions are used to define external (off-chip) memory and busses. Given the above definitions the linker can convert a logical address into an offset into an on-chip or off-chip memory device.

See section 7.7.3 , Defining External Memory and Busses, for more information on how to specify the external physical memory layout. Internal memory for a processor should be defined in the derivative definition for that processor.

The board specification

The processor definition and memory and bus definitions together form a board specification. LSL provides language constructs to easily describe single-core and heterogeneous or homogeneous multi-core systems. The board specification describes all characteristics of your target board's system busses, memory devices, I/O sub-systems, and cores that are of interest to the linker. Based on the information provided in the board specification the linker can for each core:

The section layout definition (optional)

The optional section layout definition enables you to exactly control where input sections are located. Features are provided such as: the ability to place sections at a given load-address or run-time address, to place sections in a given order, and to overlay code and/or data sections.

Which object files (sections) constitute the task that will run on a given core is specified on the command line when you invoke the linker. The linker will link and locate all sections of all tasks simultaneously. From the section layout definition the linker can deduce where a given section may be located in memory, form the board specification the linker can deduce which physical memory is (still) available while locating the section.

See section 7.8 , Semantics of the Section Layout Definition,, for more information on how to locate a section at a specific place in memory.

Skeleton of a Linker Script File

The skeleton of a linker script file now looks as follows:

7.3 Syntax of the Linker Script Language

The following lexicon is used to describe the syntax of the Linker Script Language:

A ::= B       = A is defined as B
A ::= B C     = A is defined as B and C; B is followed by C
A ::= B | C   = A is defined as B or C
<B>0|1        = zero or one occurrence of B
<B>>=0        = zero of more occurrences of B
<B>>=1        = one of more occurrences of B
IDENTIFIER = a character sequence starting with 'a'-'z', 'A'-'Z', _ , . or @
STRING     = sequence of characters not starting with \n, \r or \t
DQSTRING   = " STRING "           (double quoted string)
OCT_NUM    = octal number, starting with a zero (06, 045)
DEC_NUM    = decimal number, not starting with a zero (14, 1024)
HEX_NUM    = hexadecimal number, starting with '0x' (0x0023, 0xFF00)

OCT_NUM, DEC_NUM and HEX_NUM can be followed by a k (kilo), M (mega), or G (giga).

Characters in bold are characters that occur literally. Words in italics are higher order terms that are defined in the same or in one of the other sections.

7.3.1 Identifiers

arch_name         ::= IDENTIFIER
bus_name          ::= IDENTIFIER
core_name         ::= IDENTIFIER
derivative_name   ::= IDENTIFIER
file_name         ::= DQSTRING
group_name        ::= IDENTIFIER
mem_name          ::= IDENTIFIER
proc_name         ::= IDENTIFIER
section_name      ::= DQSTRING
space_name        ::= IDENTIFIER
stack_name        ::= section_name
symbol_name       ::= DQSTRING

7.3.2 Expressions

The expressions and operators in this section work the same as in ANSI C.

number            ::= OCT_NUM
                    | DEC_NUM
                    | HEX_NUM
assignment        ::= symbol_name assign_op expr ;
assign_op         ::= =    
                    | :=   
expr              ::= number
                    | symbol_name
                    | unary_op expr
                    | expr binary_op expr
                    | expr ? expr : expr
                    | ( expr )
                    | function_call
unary_op          ::= !    // logical NOT
                    | ~    // bitwise complement
                    | -    // negative value
binary_op         ::= ^    // exclusive OR
                    | *    // multiplication
                    | /    // division
                    | %    // modulus
                    | +    // addition
                    | -    // subtraction
                    | >>   // right shift
                    | <<   // left shift
                    | ==   // equal to
                    | !=   // not equal to
                    | >    // greater than
                    | <    // less than
                    | >=   // greater than or equal to
                    | <=   // less than or equal to
                    | &    // bitwise AND
                    | |    // bitwise OR
                    | &&   // logical AND
                    | ||   // logical OR

7.3.3 Built-in Functions

function_call     ::= absolute ( expr )
                    | addressof ( addr_id )
                    | max ( expr , expr )
                    | min ( expr , expr )
                    | sizeof ( size_id )
addr_id           ::= sect : section_name
                    | mem : mem_name
                    | group : group_name
size_id           ::= sect : section_name
                    | group : group_name

You can use the following built-in functions in expressions. All functions return a numerical value. This value is a 64-bit signed integer.

absolute()

Converts the value of expr to a positive integer.

addressof()

Returns the address of addr_id, which is a named section or memory. To get the offset of the section with the name asect:

This function only works in assignments.

max()

Returns the value of the expression that has the largest value. To get the highest value of two symbols:

min()

Returns the value of the expression hat has the smallest value. To get the lowest value of two symbols:

sizeof()

Returns the size of a section or group in an object file. To get the size of the section "asection":

This function only works in assignments.

7.3.4 LSL Definitions in the Linker Script File

description       ::= <definition>>=1
definition         ::= architecture_definition
                    | derivative_definition
                    | board_spec
                    | section_definition

7.3.5 Memory and Bus Definitions

mem_def           ::= memory mem_name { <mem_descr ;>>=0 }
mem_descr         ::= type = mem_type
                    | mau = expr
                    | size = expr
                    | speed = number
                    | mapping
mem_type          ::= rom        // attrs = rx
                    | ram        // attrs = rw
                    | nvram      // attrs = rwx
bus_def           ::= bus bus_name { <bus_descr ;>>=0 }
bus_descr         ::= mau = expr
                    | width = expr  // bus width, nr
                    |               // of data bits 
                    | mapping       // legal destination
                                    // 'bus' only
mapping           ::= map ( map_descr <, map_descr>>=0 )
map_descr         ::= dest = destination
                    | dest_dbits = range
                    | dest_offset = expr
                    | size = expr
                    | src_dbits = range
                    | src_offset = expr
destination       ::= space : space_name
                    | bus : <proc_name |
                             core_name :>0|1 bus_name

- space => space

- space => bus

- bus => bus

- memory => bus

range             ::= number .. number

7.3.6 Architecture Definition

architecture_definition
                  ::= architecture arch_name
                      <extends arch_name>0|1 
                      { arch_spec>=0 }
arch_spec         ::= bus_def
                    | space_def
                    | endianness_def
space_def         ::= space space_name { <space_descr;>>=0 }
space_descr       ::= space_property ; 
                    | section_definition  //no space ref
space_property    ::= id = number // as used in object
                    | mau = expr
                    | align = expr
                    | page_size = expr
                    | stack_def
                    | heap_def
                    | copy_table_def
                    | start_address
                    | mapping
stack_def         ::= stack stack_name ( stack_heap_descr
                            <, stack_heap_descr >>=0 )
heap_def          ::= heap heap_name ( stack_heap_descr
                            <, stack_heap_descr >>=0 )
copy_table_def    ::= copytable ( copy_table_descr 
                            <, copy_table_descr>>=0 )
stack_heap_descr  ::= min_size = expr
                    | grows = direction
                    | align = expr
direction         ::= low_to_high
                    | high_to_low
copy_table_descr  ::= align = expr
                    | copy_unit = expr
                    | dest = space_name
start_addr        ::= start_address ( start_addr_descr
                               <, start_addr_descr>>=0 )
start_addr_descr  ::= run_addr = expr
                    | section = section_name
endianness_def    ::= endianness { <endianness_type;>>=1 }
endianness_type   ::= big
                    | little

7.3.7 Derivative Definition

derivative_definition
                  ::= derivative derivative_name
                      <extends derivative_name>0|1 
                      { <derivative_spec>>=0 }
derivative_spec   ::= core_def
                    | bus_def
                    | mem_def
core_def          ::= core core_name { <core_descr ;>>=0 }
core_descr        ::= architecture = arch_name
                    | endianness = ( endianness_type
                               <, endianness_type>>=0 )

7.3.8 Processor Definition and Board Specification

board_spec        ::= proc_def
                    | bus_def
                    | mem_def
proc_def          ::= processor proc_name 
                      { proc_descr ; }
proc_descr        ::= derivative = derivative_name

7.3.9 Section Placement Definition

section_definition ::= section_layout <space_ref>0|1 
                       <( space_props )>0|1 
                       { <section_statement>>=0 }
space_ref         ::= <proc_name>0|1 : <core_name>0|1
                             : space_name
space_props       ::= space_property <, space_property>>=0
space_property    ::= locate_direction
locate_direction  ::= direction = direction
direction         ::= low_to_high
                    | high_to_low
section_statement
                  ::= simple_section_statement ;
                    | aggregate_section_statement
simple_section_statement
                  ::= assignment
                    | if_statement
                    | select_section_statement
                    | special_section_statement
aggregate_section_statement
                  ::= { <section_statement>>=0 }
                    | group_descr
select_section_statement
                  ::= select <section_name>0|1 
                      <section_selections>0|1
section_selections
                  ::= ( section_selection 
                        <, section_selection>>=0 )
section_selection
                  ::= attributes = < <+|-> attribute>>0
if_statement      ::= if ( expr ) section_statement
                      <else section_statement>0|1
special_section_statement
                  ::= heap stack_name <size_spec>0|1
                    | stack stack_name <size_spec>0|1
                    | copytable
                    | reserved <section_name>0|1 
                           <size_spec>0|1
size_spec         ::= ( size = expr )
group_descr       ::= group <group_name>0|1                                          <( group_specs )>0|1
                            section_statement
group_specs       ::= group_spec <, group_spec >>=0
group_spec        ::= group_alignment
                    | attributes
                    | group_load_address
                    | group_page
                    | group_run_address
                    | group_type
                    | allow_cross_references
group_alignment   ::= align = expr
attributes        ::= attributes = <attribute>>=1
group_load_address
                  ::= load_addr load_or_run_addr_assignment
group_page        ::= page <= expr>0|1
group_run_address ::= run_addr load_or_run_addr_assignment
group_type        ::= clustered
                    | contiguous
                    | ordered
                    | overlay
attribute         ::= r    // read-only sections
                    | w    // read/write sections
                    | x    // executable code sections
                    | i    // initialized sections
                    | s    // scratch sections
                    | b    // blanked (cleared) sections
load_or_run_addr_assignment
                  ::= <= load_or_run_addr>0|1
load_or_run_addr  ::= expr
                    | memory_reference 
                      < | memory_reference >>=0
memory_reference  ::= mem : <proc_name :>0|1
                                   <core_name :>0|1 mem_name

7.4 Expression Evaluation

Only constant expressions are allowed, including sizes, but not addresses, of sections in object files.

All expressions are evaluated with 64-bit precision integer arithmetic. The result of an expression can be absolute or relocatable. A symbol you assign is created as an absolute symbol.

7.5 Semantics of the Architecture Definition

Keywords in the architecture definition

7.5.1 Defining an Architecture

With the keyword architecture you define an architecture and assign a unique name to it. The name is used to refer to it at other places in the LSL file:

If you are defining multiple core architectures that show great resemblance, you can define the common features in a parent core architecture and extend this with a child core architecture that contains specific features. The child inherits all features of the parent. With the keyword extends you create a child core architecture:

7.5.2 Defining Internal Busses

With the bus keyword you define a bus (the combination of data and corresponding address bus). The bus name is used to identify a bus and does not conflict with other identifiers. Bus descriptions in an architecture definition or derivative definition define internal busses. Some internal busses are used to communicate with the components outside the core or processor. Such busses on a processor have physical pins reserved for the number of bits specified with the width statements.

7.5.3 Defining Address Spaces

With the space keyword you define a logical address space. The space name is used to identify the address space and does not conflict with other identifiers.

See section 7.8 , Semantics of the Section Layout Definition for information on creating and placing stack sections.

7.5.4 Map pings

You can use a mapping when you define a space, bus or memory. With the map field you specify how addresses from the source (space, bus or memory) are translated to addresses of a destination (space, bus). The following mappings are possible:

With a mapping you specify a range of source addresses you want to map (specified by a source offset and a size), the destination to which you want to map them (a bus or another address space), and the offset address in the destination.

If you are mapping a bus to another bus, the number of data lines of each bus may differ. In this case you have to specify a range of source data lines you want to map (src_dbits = begin..end) and the range of destination data lines you want to map them to (dest_dbits = first..last).

From space to bus

All spaces that are not mapped to another space must map to a bus in the architecture:

From space to space

If you map an address space to another address space (nesting), you can do this by mapping the subspace to the containing larger space. In this example a small space of 64k is mapped on a large space of 16M.

From bus to bus

The next example maps an external bus called e_bus to an internal bus called i_bus. This internal bus resides on a core called mycore. The source bus has 16 data lines whereas the destination bus has only 8 data lines. Therefore, the keywords src_dbits and dest_dbits specify which source data lines are mapped on which destination data lines.

It is not possible to map an internal bus to an external bus.

7.6 Semantics of the Derivative Definition

Keywords in the derivative definition

7.6.1 Defining a Derivative

With the keyword derivative you define a derivative and assign a unique name to it. The name is used to refer to it at other places in the LSL file:

If you are defining multiple derivatives that show great resemblance, you can define the common features in a parent derivative and extend this with a child derivative that contains specific features. The child inherits all features of the parent (cores and memories). With the keyword extends you create a child derivative:

7.6.2 Instantiating Core Architectures

With the keyword core you instantiate a core architecture in a derivative.

7.6.3 Defining Internal Memory and Busses

With the memory keyword you define physical memory that is present on the target board. The memory name is used to identify the memory and does not conflict with other identifiers. It is common to define internal memory (on-chip) in the derivative definition. External memory (off-chip memory) is usually defined in the board specification (See section 7.7.3, Defining External Memory and Busses).

- rom: read only memory

- ram: random access memory

- nvram: non volatile ram

With the bus keyword you define a bus in a derivative definition. Busses are described in section 7.5.2 , Defining Internal Busses.

7.7 Semantics of the Board Specification

Keywords in the board specification

7.7.1 Defining a Processor

If you have a target board with multiple processors that have the same derivative, you need to instantiate each individual processor in a processor definition. This information tells the linker which processor has which derivative and enables the linker to distinguish between the present processors.

If you use processors that all have a unique derivative, you may omit the processor definitions. In this case the linker assumes that for each derivative definition in the LSL file there is one processor. The linker uses the derivative name also for the processor.

With the keyword processor you define a processor. You can freely choose the processor name. The name is used to refer to it at other places in the LSL file:

7.7.2 Instantiating Derivatives

With the keyword derivative you tell the linker that the given processor has a certain derivative. The derivative name refers to an existing derivative definition in the same LSL file.

For examples, if you have two processors on your target board (called myproc_1 and myproc_2) that have the same derivative (called myderiv), you must instantiate both processors as follows:

7.7.3 Defining External Memory and Busses

It is common to define external memory (off-chip) and external busses at the global scope (outside any enclosing definition). Internal memory (on-chip memory) is usually defined in the scope of a derivative definition.

With the keyword memory you define physical memory that is present on the target board. The memory name is used to identify the memory and does not conflict with other identifiers. If you define memory parts in the LSL file, only the memory defined in these parts is used for placing sections.

If no external memory is defined in the LSL file and if the linker option to allocate memory on demand is set then the linker will assume that all virtual addresses are mapped on physical memory. You can override this behavior by specifying one or more memory definitions.

For a description of the keywords, see section 7.6.3, Defining Internal Memory and Busses.

With the keyword bus you define a bus (the combination of data and corresponding address bus). The bus name is used to identify a bus and does not conflict with other identifiers. Bus descriptions at the global scope (outside any definition) define external busses. These are busses that are present on the target board.

For a description of the keywords, see section 7.5.2, Defining Internal Busses.

You can connect off-chip memory to any derivative: you need to map the off-chip memory to a bus and map that bus on the internal bus of the derivative you want to connect it to.

7.8 Semantics of the Section Layout Definition

Keywords in the section layout definition

7.8.1 Defining a Section Layout

With the keyword section_layout you define a section layout for exactly one address space. In the section layout you can specify how input sections are placed in the address space, relative to each other, and what the absolute run and load addresses of each section will be.

You can define one or more section definitions. Each section definition arranges the sections in one address space. You can precede the address space name with a processor name and/or core name, separated by colons. You can omit the processor name and/or the core name if only one processor is defined and/or only one core is present in the processor. A reference to a space in the only core of the only processor in the system would look like "::my_space". A reference to a space of the only core on a specific processor in the system could be "my_chip::my_space". The next example shows a section definition for sections in the my_space address space of the processor called my_chip:

With the optional keyword direction you specify whether the linker starts locating sections from low_to_high (default) or from high_to_low. In the second case the linker starts locating sections at the highest addresses in the address space but preserves the order of sections when necessary (one processor and core in this example).

If you do not explicitly tell the linker how to locate a section, the linker decides on the basis of the section attributes in the object file and the information in the architecture definition and memory parts where to locate the section.

7.8.2 Creating and Locating Groups of Sections

Sections are located per group. A group can contain one or more (sets of) input sections as well as other groups. Per group you can assign a mutual order to the sets of sections and locate them into a specific memory part.

With the section_statements you generally select sets of sections to form the group. This is described in subsection Selecting sections for a group.

Instead of selecting sections, you can also modify special sections like stack and heap or create a reserved section. This is described in section 7.8.3, Creating or Modifying Special Sections.

With the group_specifications you actually locate the sections in the group. This is described in subsection Locating a group.

Selecting sections for a group

With the select keyword you can select one or more sections for the group. You can select a section by name or by attributes. If you select a section by name, you can use a wildcard pattern:

The first select statement selects the section with the name ".mysection". The second select statement selects all sections that were not selected yet.

A section is selected by the first select statement that matches, in the union of all section layouts for the address space. Global section layouts are processed in the order in which they appear in the LSL file. Internal core architecture section layouts always take precendence over global section layouts.

- r readable sections

- w readable/writable sections

- x executable sections

- i initialized sections

- b sections that should be cleared at program startup (BSS)

- s scratch sections (not cleared and not initialized)

To select all read-only sections:

Keep in mind that all section selections are restricted to the address space of the section layout in which this group definition occurs.

Locating a group

With the group_specifications you actually define how the locator must locate the group. You can roughly define three things: 1) assign properties to the group like alignment and read/write attributes, 2) define the mutual order in the address space for sections in the group and 3) assign a load-time address or run-time address to the group.

The linker creates labels that allow you to refer to the begin and end address of a group from within the application software. Labels _lc_gb_group_name and _lc_ge_group_name mark the begin and end of the group respectively, where the begin is the lowest address used within this group and the end is the highest address used. Notice that a group not necessarily occupies all memory between begin and end address. The given label refers to where the section is located at run-time (versus load-time).

1. Assign properties to the group like alignment and read/write attributes. These properties are assigned to all sections in the group (and subgroups) and override the attributes of the input sections.

2. Define the mutual order of the sections in the group. By default, a group is unrestricted which means that the linker has total freedom to place the sections of the group in the address space.

It may be possible that one of the sections in the overlay group already has been defined in another group where it received a load-time address. In this case the linker does not overrule this load-time address and excludes the section from the overlay group.

3. Assign a load-time address or run-time address to the group.
By default, the position and properties of a group in an LSL file specify the run-time addresses of its sections. The linker uses the addresses defined by the run-time addresses when it patches symbol references in the object code. It may be possible that you want to assign a different load-time and run-time address to a group. The load-time address specifies where the group's elements are loaded in at download time. The run-time address specifies where sections are located at run-time, that is when the program is executing. The program is responsible for copying overlay sections at appropriate moment from its load-time location to its run-time location.

The load-time and run-time addresses of a group cannot be set at the same time. If the load-time property is set for a group, the group (only) restricts the positioning at load-time of the group's sections. It is not allowed to set the address of a group that has a not-unrestricted parent group.

The properties of the load-time and run-time start address are:

For overlays, the linker reserves memory at the run-time start address as large as the largest element in the overlay group.

7.8.3 Creating or Modifying Special Sections

Instead of selecting sections, you can also create a reserved section or modify special sections like a stack or a heap, a reserved section. Because you cannot define these sections in the input files, you must use the linker to create them.

7.8.4 Conditional Group Statements

Within a group, you can conditionally select sections or create special sections.


Copyright © 2003 Altium BV