5.2 Psect Concatenation And Paging
All executable code must be placed in a psect. The grouping of psects is based on how and where the psects are defined, and this may affect how you write code that jumps to labels or calls other routines.
Notice that in file_1.S
there were two separate blocks of assembly code
placed into the code
psect (one defining readPort
, the
other containing main
). The content of psects with the same name are
concatenated by the linker prior to memory allocation (unless they use the
ovrld
flag), thus the instruction associated with the label
main:
will occur directly after the last instruction in the
readPort
routine, even though there is other code and objects
defined between these blocks in the source file.
Note that the content of the code
psect in
file_2.S
, however, will not concatenate with the already combined
content of the code
psect in file_1.S
. Concatenation
of psects only occurs for psects with the same name and defined within the same module.
In this case, the code
psect in file_2.S
will be
linked independently to the code
psect in
file_1.S
.
After building the example program, the map file will show two code
psects linked at different addresses. The code
psects are listed once
in the module-by-module listing under file_1.o
(the object file
produced from file_1.S
) and again under file_2.o
in
the map file extract below. They are also shown in the listing by class, but you cannot
see the source file in which they were defined there. Note that there is just the one
code
psect indicated for file_1.o
, as both
sections of code placed in that psect were concatenated.
Name Link Load Length Selector Space Scale
file_1.o resetVec 0 0 2 0 0
code 3E7 3E7 19 7CE 0
udata_shr 71 71 1 70 1
file_2.o code 3DD 3DD A 7BA 0
udata_shr 70 70 1 70 1
TOTAL Name Link Load Length Space
CLASS CODE
resetVec 0 0 2 0
code 3E7 3E7 19 0
code 3DD 3DD A 0
The program memory on Baseline and Mid-range devices is paged, and your device data sheet will indicate the page arrangements for your device. The way psects are concatenated has consequences for how code written for these devices must call or jump to labels. The program memory on PIC18 devices is not paged. All addresses in their program memory are reachable by call and jump instructions, so the following discussion does not apply to programs written for these devices.
On Baseline and Mid-range devices, there are three methods of handling flow-control instructions. One is to manually add page selection instructions or directives into your program, then mask the address by using either a predefined macro or by using an expression. Another method is to again manually insert page selection sequences, but to use a linker option to have the linker automatically truncate addresses to suite the instruction. This essentially suppresses the linker fixup overflow error that would normally occur if the address was not masked. The final method is to use psuedo instructions that handle both page selection and address masking for you. All these methods are discussed below.
Manual Address Masking
Before making a call or jump on Baseline and Mid-range devices, the PCLATH register
must contain the value (the upper bits of the address) to select the page of the
destination. The PAGESEL
directive can be used to initialize the
PCLATH register for you.
PAGESEL
directive being used before the call
storeLevel
instruction in the example code, repeated
here:loop:
;a call to a routine in the same psect
call readPort ;value returned in WREG
;a call to a routine in a different module
PAGESEL storeLevel
call PAGEMASK(storeLevel) ;expects argument in WREG
PAGESEL $
;wait for a few cycles
movlw 0xFF
delay:
decfsz WREG,f
goto delay
Note that it is used again after the call using the
current location counter, $
, as its argument to ensure that PCLATH
is again pointing to the page holding the code currently being executed. This allows
the goto delay
instructions following the call to work as
expected.As shown above, a PAGESEL
directive was not used before the
call to readPort
. The PCLATH register did not need to be updated in
this case because of two conditions. First, as mentioned earlier, the
code
psect that contains the readPort
routine
will concatenate with the code
psect that holds the
main
routine, so these two routines (the caller and callee)
will be in the same concatenated psect; and second, the CODE
linker
class associated with the code
psect is defined in such a way that
psects placed in its memory ranges can never cross a page boundary.
Linker command line:
-W-3 --edf=/Applications/microchip/xc8/v2.31/pic/dat/en_msgs.txt -cs \
-h+dist/default/debug/asm_curiosity_16F18446_linearMemory.X.debug.sym \
--cmf=dist/default/debug/asm_curiosity_16F18446_linearMemory.X.debug.cmf \
-z -Q16F18446 -o/tmp/xckFLd4UW -presetVec=0h -ver=XC8 PIC(R) Assembler \
-Mfile.map -E1 --acfsm=1493 -ACODE=00h-07FFhx8 -ASTRCODE=00h-03FFFh \ ...
The
CODE
class is defined by the linker option:
-ACODE=00h-07FFhx8
. This option indicates that the memory
associated with the CODE
class consists of 8 consecutive pages, the
first starting at address 0, and each being 0x800 words long. These ranges
correspond to the page addresses on the 16F18446 device.The linker will never allow a psect placed into the memory associated with a class to
cross boundaries in that class's memory ranges, which implies in this example that a
psect placed in the CODE
class will be wholly contained in a device
page. You will receive a 'can't find space' error if a psect linked into this class
exceeds the size of a page. If the class had instead been defined using
-ACODE=0-01FFFh
, it would cover exactly the same memory, but
the boundaries in that memory would not exist. Psects placed in a class such as this
could be linked anywhere, potentially straddling a device page boundary.
It is common to restrict where the linker can place psects so that assumptions can
then be made in the source code that improve efficiency. In this case, the page
boundaries in the CODE
class, has meant that calls or jumps to a
label that is defined in the same psect and in the same module do not need to first
select the destination page (assuming that PCLATH already points to that page).
Page selection must be considered for any Baseline and Mid-range non-relative control
instruction, those being the goto
, call
, and
callw
instructions. It is not needed prior to relative branch
instructions; however, as these instructions modify PC once the branch has been
taken, you will need to assess whether page selection is required for calls and
jumps made after the branch. Page selection may also be required should you use any
instructions that specify the PCL register as the destination, as these also use the
PCLATH register to form the destination address.
A PAGEMASK()
preprocessor macro has been used with the
call
instruction operand to remove the page bits contained in
the address of storelevel
. This directive ANDs the address with an
appropriate mask. If this page information is not removed, the linker might issue a
fixup overflow error, which occurs when the operand value determined by the linker
is too large for the relevant field in the instruction opcode. For example,
Mid-range call
instructions have an 11-bit wide field in their
op-code that specifies the offset into the currently selected page of the location
they are to call.
Automatic Address Masking
The other method of handling address masking is to have the linker automatic truncate the addresses for you. This is by far the easiest to use and does not clutter your assembly source with ancillary expressions in instruction operands.
As with manual masking, before making a call or jump on Baseline and Mid-range
devices, the PCLATH register must contain the value (the upper bits of the address)
to select the page of the destination. The PAGESEL
directive can be
used to initialize the PCLATH register for you.
To enable automatic masking, use the driver option
-Wl,--fixupoverflow=action
option, with
ignore
or warn
and/or lstwarn
as the action
argument. Such an option will have
the linker either totally ignore fixup overflow errors or only issue a warning
should it encounter such situations. The address that will be used by the
instruction is the full opernad address masked to the exact width expected by the
instruction. See the MPLAB® XC8 PIC®
Assembler User's Guide for more information on this option.
loop:
;a call to a routine in the same psect
call readPort ;value returned in WREG
;a call to a routine in a different module
PAGESEL storeLevel
call storeLevel ;expects argument in WREG
PAGESEL $
;wait for a few cycles
movlw 0xFF
delay:
decfsz WREG,f
goto delay
Notice that the PAGEMASK()
macro has
not been used; however, the code will still build and execute correctly if these are
present. Note that this option does not affect page selection in any way.
Instructions or the PAGESEL
directive must always be used to select
the page of a memory location, regardless of how you handle address masking.Using Psuedo Instructions
The third way to handle flow-control is by use of two pseudo instructions. These both select the page of the destination and perform masking of the called address.
The psuedo instructions are ljmp
and fcall
. These
expand to a goto
and call
, respectively, with the
necessary page selection before the goto
or call
instruction, then page selection of the current page after the instruction. As the
ljmp
and fcall
mnemonics can expand to more
than one PIC instruction, they should never be used immediately after any
instruction that skips, such as the btfsc
instruction. You can see
the opcodes generated for the ljmp
or fcall
pseudo
instructions in the assembly list file.
loop:
;a call to a routine in the same psect
call readPort ;value returned in WREG
;a call to a routine in a different module
fcall storeLevel ;expects argument in WREG
;wait for a few cycles
movlw 0xFF
delay:
decfsz WREG,f
goto delay
If you are not sure whether page selection is required before a call or jump, the
safest approach is to use the PAGESEL
directive or use the
fcall
and ljmp
instructions, but just remember
that doing so might unnecessarily increase the size of your code and slow its
execution.
Paging Considerations
The above considerations might tempt you to place most of your code in the same psect
in the one module so that you can avoid page selection instructions, but remember
that once a psect grows larger than the size of a page, it will no longer fit in the
CODE
class and you'll receive 'can't find space' error message
from the linker. Consider having frequently called routines and the routines that
call them in psects with the same name and in the same module. Move other routines
to other modules, or place them in psects with different names so that they are
linked separately and can fill other device pages.
If you decide to create your own psects and linker classes to position code, keep in mind that how you define the linker classes might affect how your code needs to be written. If routines can straddle a bank boundary, they are more likely to fit in the program memory, but your program will require more page selection instructions. If you manually position psects to control where page selection instructions are needed (rather than have them linked anywhere in a linker class), this will require a lot of maintenance as the code is debugged and developed.