4.3 Working with Data Banks

The Mid-range code in this chapter's example shows how to handle data memory banking. Check your device data sheet to ensure you understand how banking works on your chosen device.

The equivalent PIC18 example code, shown in section A Basic Example For PIC18 Devices, largely avoided banked data memory complications by using the movff instruction, which can access the entire data memory without the need for the bank selection register to be set. The movffl instruction can also access the entire data memory on PIC18 devices that have an expanded amount of data RAM, such as the 18F47K42. Additionally, the max and tmp objects were placed into a psect that was destined for Access bank memory, which can also be accessed using file register instructions without using the bank selection register.

Mid-range and Baseline PIC devices do not implement the movff instruction, so all movement of data is performed by banked instructions. So too, all other instructions that access file registers (e.g. clrf, addwf) are banked. Baseline and Mid-range devices also have only very limited amounts of common memory, which can be accessed independently of the currently selected bank, so most programs on Baseline and Mid-range devices will need to consider the memory banks used by objects.

To illustrate how banked instructions should be used, the code in this Mid-range example links the tmp and max objects into banked rather than common memory, by placing them in the assembler-defined psect udata_bank0. This means that the code that accesses these objects must deal with bank selection and address masking.

Bank selection (typically performed using the BANKSEL directive) must always be handled by the addition of instructions into your program, but there are two methods of handling the masking of addresses used by instruction operands. One is to manually mask the address by using either a predefined macro or by using an expression. The other method is 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. Both these methods are discussed below.

Attention: The actions of selecting the bank of an object before that object is accessed and masking the address of an object when it is used as the operand to a file register instruction are quite independent. Regardless of whether you chose to manually mask addresses (e.g. using the BANKMASK() macro) or have the linker automatically truncate addresses, you must always include the appropriate bank selection sequences before objects are accessed. This is typically performed using the BANKSEL directive.

Manual Address Masking

The example program in this chapter shows bank selection and the addresses being manually masked. The main part of the example program begins as follows.
    BANKSEL    max                 ;starting point
    clrf       BANKMASK(max)
    BANKSEL    ANSELC
    clrf       BANKMASK(ANSELC)    ;select digital input for port C
loop:
    BANKSEL    PORTC               ;read and store port value
    movf       BANKMASK(PORTC),w
    BANKSEL    tmp
    movwf      BANKMASK(tmp)
    subwf      max^(tmp&0ff80h),w  ;is this value larger than max?
  ...
The BANKSEL max directive has been used to select the bank of the object max before it is accessed by the clrf instruction following. Although you can manually write the instruction or instructions needed to set the bank selection bits for the device you are using, the BANKSEL directive is more portable and is easier to interpret. On devices other than PIC18s and Enhanced Mid-range devices, this directive can sometimes expand into more than one instruction, so you should never use it immediately after an instruction that can skip, such as the btfsc instruction.

The clrf instruction clears the object max. Here, the BANKMASK() preprocessor macro has been used with the instruction operand to remove the bank bits contained in the address of max. This directive ANDs the address with an appropriate mask.

If this bank 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 file register instructions have a 7-bit wide field in their op-code that specifies the offset into the currently selected bank of the location they are to access.

In the last few instructions of the code snippet shown above, the programmer has assumed that max and tmp are defined in the same psect, and hence will be located in the same data bank. The example code selects the bank of tmp, writes the W register into that object using a movwf instruction, and then max is accessed by the subwf instruction. Based on the programmer's assumption, a BANKSEL directive to select the bank of max before the subwf instruction is redundant, and it has been omitted to reduce the size of the program.

While the above assumption is valid and the code will execute correctly, it could fail if the definitions for max and tmp were to change and the objects ended up in different banks. Such a failure would be difficult to track down. Fortunately, there are other ways to remove the bank information from an address that can be used to your advantage.

The last line of code in the above example:
subwf   max^(tmp&0ff80h),w
contains a check to ensure that the bank of tmp and max are the same. Rather than ANDing out the bank information in max by using the BANKMASK() macro, the operand expression XORs ( ^ operator) the full address of max with the bank bits contained in the address of tmp (which is obtained by masking out only the bank offset from tmp using the AND operator, &). If the bank bits in the address of max and the bank bits in the address of tmp are the same, they will XOR to zero in this expression. If they are not the same, they will produce a non-zero component in the upper bits of the address and trigger a fixup overflow error from the linker. This is much more desirable than the code silently failing at runtime.

The operand expression to the subwf instruction checks to ensure that two objects are in the same bank, but you can also use this style of check to ensure that an object is in a particular bank. If, for example, the code required that max must be in bank 2, then using the address expression (max ^ 100h) on a Mid-range device would trigger a fixup error if that was not the case. Here the value 0x100 consists of the bit sequence that represents bank 2, with all seven bits of the bank offset (the least significant bits) zeroed.

The section relating to file register address masking in the MPASM to MPLAB® XC8 PIC® Assembler Migration Guide has more information on the format of addresses and the appropriate masks to use, should you decide to use the AND or XOR operators and you not use the BANKMASK() macro.

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. To enable this, 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 operand 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 only code associated with bank selection is required if you are using this linker option.
    BANKSEL    max                 ;starting point
    clrf       max
    BANKSEL    ANSELC
    clrf       ANSELC              ;select digital input for port C
loop:
    BANKSEL    PORTC               ;read and store port value
    movf       PORTC,w
    BANKSEL    tmp
    movwf      tmp
    subwf      tmp,w               ;is this value larger than max?
  ...
Notice that the BANKMASK() macros have not been used, nor any other expressions to remove the bank information from the instruction operands; however, the code will still build and execute correctly if these are present. Note that this option does not affect bank selection in any way. The BANKSEL directive (or equivalent instructions) must always be used to select the bank of a memory location, regardless of how you handle address masking.

Throughout this document, source code examples will show instructions where their operand addresses have been manually masked, but the macros and expressions that perform this task do not need to be used if you prefer to use the -Wl,--fixupoverflow option and have the linker truncate addresses for you. As noted earlier, bank selection directives or instructions must always be present in the source when required.