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.

You can see a 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.

The linker options used when you build are shown in the map file. For the 16F18446 device used in this example, the top of the map file is as follows:
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.

The above code extract taken from this chapter's example has been repeated here, showing how it can be written if you are using this linker 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.

The above code snippet could be rewritten:
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.