PIC Base-line Cores Introduction

        .cr     pic12   To load this cross overlay

This page describes the cross overlay for the PIC Base-line family of cores. These cores store all their instructions in 12-bit words in program memory. I have used the Code memory architecture to name the cross overlays. So the pic12 cross overlay is for the Base-line family, which uses 12-bits instruction words. Thus the pic12 cross doesn't necessarily apply to PIC12Fxxx devices only. The two names are not strictly related, unfortunately.

According to Microchip all these controllers are easy to learn because there are only some 30 to 40 instructions to learn. Unfortunately not everything is as easy as it may seem. I must admit that the base models are quite easy to comprehend. But things get complicated very quickly when larger controllers start working with memory paging and register bank switching.
Because every instruction had to fit into a 12-bit word, including addresses, makes the addressable ranges of these instructions quite low. This means that the PIC Base-line family has to rely on bank switching and other addressing limitations. Subroutines, for instance, can't start on odd numbered program pages. And because of the limited instruction length a few very important instructions are missing. E.g. ADD or SUB with an immediate operand is not possible.

SB-Assembler Version Differences

This page describes the PIC Base-line cross overlay for SB-Assembler Version 3 only. Microchip has made quite a little mess of the instruction sets and special function registers throughout the entire family that it has become impractical to handle both versions on one page.
If you insist on using Version 2 of the SB-Assembler you can use the description of the PIC16c5x cross overlay, which was Microchip's first Base-line controller. Please note that this is a very old version and it is no longer maintained.

Programming Model

I only include a little summary about the features of the PIC Base-line family. All these family members have a 12-bit instruction size in common. It is not my intention to make the original documentation obsolete, so please refer to the original documentation for further details.

PIC12 programming model

The register file can be addressed by a 5-bit pointer, giving a total addressing range of 32 bytes. The first 16 bytes of the register file are fixed, while the second half can be bank switched with up to 4 banks depending on the device you're using. Any additional banks can only be addressed indirectly.

The first 8 bytes of the register file are not physical RAM, but serve other purposes like control and I/O. All the other register file memory is general purpose RAM memory, as long as it is implemented in the device of your choice.


The Working register W is not part of the register file and is used as an operand in most instructions. On other micro processors this register would have been called the Accumulator.

f$00 INDF (Indirect Data Addressing)

This is not a physical register. It is used to indirectly address any of the file registers, even the ones which can't be addressed directly. Whenever this register is the source or destination of an operation the contents of the FSR register is used to point to the actual file register to be used for the operation.

f$01 TMR0

This is timer 0, which is usually a free running 8-bit counter, which is readable and writable. On older devices this register was called RTCC and was little more than a free running counter, driven by either an external clock source or the internal instruction clock. Read the documentation of your device to get the full details about this register.

f$02 PCL

This is the low byte of the program counter. Normally the entire program counter is incremented after fetching each instruction. The program counter can access up to 2 k words of memory. After reset the program counter is initialized to all 1's, effectively starting the program at the highest available address. The program counter can wrap around to address 0, but it is customary to put a GOTO instruction in that location which jumps to the first instruction of your program. This first instruction should be somewhere in the first program memory page, because the page select bits are cleared to 0 upon RESET.
Only the low byte of the program counter can be read or written to under program control. All other program counter bits (b10, b9 and b8) are not directly accessible.
Writing to the PCL register in any way effectively causes a computed jump. Bit b8 of the program counter will always be 0 when writing directly to the PCL register, while bits b10 and b9 will get the value of the Status register bits PA1 and PA0 respectively.
GOTO instructions can change the bits b8..b0 directly, while CALL instructions and all other instructions that change the contents of PCL can only change b7..b0 and always leave b8=0. The other bits of the program counter can only be changed by selecting the proper memory page and then issue a GOTO or CALL instruction. The memory page can be selected with bits b6 and b5 of the status word f$03.
It is the programmer's responsibility to set the page select bits properly before any GOTO or CALL instruction.

The PIC Base-line series employs a two level hardware stack for return addresses. This means that subroutines may be nested 2 levels deep before the stack overflows and looses the oldest return address. The stack can not be manipulated in any other way then by the CALL and RETLW instructions.

f$03 Status word register

This register contains 8 system flags.

Bit 7---Undefined
Bit 6PA1Copied to PC10 by GOTO and CALL
Bit 5PA0Copied to PC9 by GOTO and CALL
Bit 4TOTime Out Flag
Bit 3PDPower down Flag
Bit 2ZZero Flag
Bit 1DCBCD Digit Carry Flag
Bit 0CCarry Flag

The C, DC and Z flags reflect the status of previous instructions. The PD and TO bits can be used to find out why the device was restarted and are read-only bits. The bits PA0 and PA1 are used to select the program memory bank. Not all devices have these bits. These bits may be re-assigned to other purposes on some devices. Read the device documentation to find out what applies to your device. Bit-7 is not defined on all devices. Again, read the documentation of your device to find out what the purpose of bit-7 is.

The Status register may be written to, but this may have unexpected results because some flags reflect the result of the write instruction and others are read-only. Usually the writable bits are written one a time by some of the bit instructions.

f$04 FSR

The File Select Register is used as a data pointer for indirect addressing of any of the 80 file registers. Reading or writing to register f$00 will effectively read or write to the address pointed to by the FSR register. Bits 4..0 select the indirectly addressed register in the file.
Bits 6 and 5 select the required memory bank on devices with more than one register file bank.

f$05 PORT A, f$06 PORT B, f$07 PORT C

These are the actual I/O ports. Reading such a register will read the actual logic level on the I/O pin, even if those pins are set as outputs.
Reset will cause all I/O lines te become inputs. The I/O pins can be switched to outputs by writing a 0 in the appropriate bits of the I/O control registers TRIS A, TRIS B and TRIS C.

On some devices with less I/O ports some of these registers may be re-assigned to other functions. For instance fs05 can also double as oscillator calibration register. Read the manual of your device to work out the details.


These registers are not part of the register file. They can only be written to by the TRIS instruction which copies the contents of the W register to one of the three I/O control registers. A '1' in a bit of any of the 3 TRIS registers will select the corresponding I/O bit to be an input. A '0' will make the corresponding bit an output.


This Option word is not a part of the register file. It is used to set the timer, WDT options and clock pre-scaler by means of the OPTION instruction.

Please note that none of the above mentioned register names are pre-defined by the SB-Assembler. You can use normal labels in combination with the .EQ directive to declare names for all the file registers, including the special function registers.

Reserved Words

The SB-Assembler PIC12 Base-line cross overlay has only 2 reserved words, W and F. Avoid assigning labels with these two names, and you're safe.

Target Files

Storing 12-bit instruction words in an 8-bit oriented target file requires special treatment. You may choose any target file format you like. Most PIC programming devices require you to supply an Intel HEX file though, so I advise you to use only the Intel format for your target files when writing programs for the PICmicro families.

Microchip recommends 2 methods of storing your target files. The first method is to split the 12-bit instruction words and store the low bytes in one target file and the remaining bits in a second target file. The SB-Assembler doesn't support this method, which is not such a big problem because the second method is accepted by all PIC programming devices I have seen so far.
The second method that is recommended by Microchip is to store all instruction words in one target file, low byte first, and the remaining bits in the next byte. This method is supported by the SB-Assembler.

Please note that the PIC12 cross overlay of the SB-Assembler will always store pairs of bytes so that all source addresses are doubled in the target file. So if you write an instruction at address $0123 it will end up at address $0246 and $0247 in the target file.

In case a device has built in EEPROM memory, this memory is stored in the target file directly following the program memory. So if a device has 512 words of program memory, its EEPROM memory will start at byte address 1024 (double the program word size). EEPROM data bytes are stored as words, leaving the high byte 0.

Every processor allows you to write 4 ID words at the end of the program memory, or at the end of the EEPROM memory (if present). These ID words can be used to identify your software versions, or may contain any other information you like. The ID words can not be read under program control, they are only accessible by programming devices. Each ID word occupies 2 bytes, but can contain only 4 bits.
You can include the ID words in your target file at the end of the program or EEPROM memory. Please refer to the new .ID directive for more information on how to include the ID words in your program.

Every device also has a Config word, allowing you to select the type of oscillator and some other configuration options. This Config word can be included in the target file too and is usually written at word address $0FFF (which is byte address $1FFE and $1FFF). Please refer to the new .CW directive for more information.

Special Features

New Directives

I've added a few new directives specifically tailored to suit the PIC cross overlay. All new directives are explained later on this page.

Altered behaviour of Directives

Every data byte written to program memory has to be translated to the RETLW instruction. Therefore all data generating instructions will write every generated byte as a RETLW instruction, as long as we're still writing to program memory. Data bytes stored beyond the program memory are written as words to the target file. Usually these data bytes will end up in the device's EEPROM memory.
The .MS (Memory Size) directive is used to set the size of the program memory. This is important for the behaviour of assembler when it comes to writing data bytes.
The above only applies to Code memory. It does not apply to the RAM and EEPROM memory segments. Please note that the use of EEPROM segments will write the data as bytes to a separate file, not as words. This can still be useful if you are using an external EEPROM device.

File Register addressing

File registers are numbered from $00 to $7F, while the actual addressing range runs from $00 to $1F. The SB-Assembler doesn't check the address when addressing any file register, although the actual instruction keeps only the 5 lowest bits. This allows you to access the file registers by their absolute addresses, but you must manually take care of the proper addressing of the upper two bits of the file address.
Remember that only the top 16 addressable registers of the register file are physically mappable with up to 4 separate banks. The lower 16 registers always remain accessible, regardless of the selected register bank. So register addresses $02, $22, $42 and $62 all refer to PCL for instance.

Immediate prefix is optional

Immediate data is called literal data in Microchip's documentation. All instructions that can handle immediate data end with the letters LW, which indicates that a literal value is to be used. The immediate pre-fixes #, /, = or \ may be used to identify an immediate value. The assembler will assume the default # pre-fix if no pre-fix symbol precedes the operand value, effectively using the LSB of the 32-bit value as operand.


Data bytes are stored as RETLW instruction words in the program memory. If you want to read some data from program memory you should CALL to the desired location. The RETLW instruction at that location will immediately return to the calling routine with a literal value loaded in W.
You may add multiple expressions behind a single RETLW instruction, effectively generating multiple RETLW instructions, each with its own return value.

You may also omit the data byte completely after a RETLW instruction, effectively making it a RETURN instruction. Keep in mind that the PIC doesn't have a real RETURN instruction though, so the W register will get a value one way or the other. When you don't specify the value, a 0 will be written to the W register.
If you do want to omit the data value, make sure any comments will follow the RETLW instruction after at least 10 spaces, otherwise the comment will be interpreted as return value.


        RETLW  $12,$34,$56
        RETLW          Comment after at least 10 spaces

CALL and GOTO address range

Some restrictions apply to the destination addresses of CALL and GOTO instructions due to the nature of the memory organization of the PIC Base-line family of processors. Memory consists of up to 4 banks of 512 instruction words each.

The GOTO instruction may cause a jump to any location within the 2k word program memory, even though only 9 bits of the destination address can be supplied by the instruction itself. The upper two address bits are copied from the PA1 and PA0 bits in the Status register every time a GOTO instruction is executed. It's the programmer's responsibility to set these bits appropriately before executing the GOTO instruction.

CALL instructions almost follow the same rules as GOTO instructions, with one little difference. Bit b8 of the program counter will always become 0 after a CALL instruction, restricting the destination address to the first half of each of the 4 possible program memory banks. Bits b10 and b9 are copied from PA1 and PA0 in the Status register, just like they would with the GOTO instruction. You will get a Out of range error if you try to CALL to a location in the second half of any particular program memory space.

The assembler does not check the address of the CALL or GOTO even though only the lowest 9 bits are actually used. This allows you to treat the 4 program memory banks as a linear address space.
The only exception to this rule is that the assembler does check if bit-8 of the destination address is 0 for CALL instructions.

Page crossing

The PIC processor's address counter will wrap around to 0 when it reaches past word address 511. The SB-Assembler will not warn you if this happens.
Likewise, the SB-Assembler will not warn you if a data table spans both the first half and second half of a program memory page. The PIC can't CALL to the second half of the page, rendering those data bytes (RETLW instructions) unreachable.

You can use the .OT and .CT directives in both cases to warn you for page crossings if you like.

Destination flag

Some instructions allow you to store the result in the W register or in the register file. You tell that to the assembler by adding ,W or ,F behind the operand of the instruction.
If you don't specify the destination flag the default destination will be F.

You can also use an expression as destination flag. The W register will be the destination if the expression evaluates to 0. And it will use the register file if the expression evaluates to 1. Any other value will generate a Out of range error.

Pseudo instructions

Microchip recommends a set of pseudo instructions. I have implemented them all. Please refer to the opcode test file to see them all.

Two of the pseudo instructions require a bit more explanation though, LCALL and LGOTO. These two instructions help you to reach the entire program memory more easily. You can view the entire 2k word of program memory as a linear address space, even though the PIC divides that space into 4 banks. If you want to jump or call to the first bank, both PA1 and PA0 bits in the STATUS register have to be cleared. If you want to jump or call to the second bank, only PA0 has to be set. Jumps or calls to the third bank requires you to only set PA1. While both PA1 and PA0 have to be set if you want to jump to program memory bank 4.
That is exactly what the LCALL and LGOTO instructions will do for you. But they do it in a clever way. On devices with up to 512 words of program memory, there is no need te set or clear any bits, so the LCALL and LGOTO instructions won't. On devices with up to 1024 words of program memory, only PA0 needs to be set or cleared, and again that is exactly what the LCALL and LGOTO instructions will do. Finally on devices with more than 1024 words of program memory, both flags need to be set or cleared.
It goes without saying that the SB-Assembler needs to know the program memory size of your device in order to make the right decisions. You do that with the .MS directive.

Please keep in mind that some of the Pseudo instructions are composed of multiple real instructions. This means that you cannot use such Pseudo instructions immediately following one of the Skip instructions! Such a Skip instruction will only skip the first instruction of a multi-instruction Pseudo instruction. This is hardly ever what you really want.

Extra Directives

The PIC Base-line cross overlay requires some extra directives. These include directives to set some options and to check if a program memory page boundary has been crossed.

.CT     Close Table




This directive signals the end of your table in memory. It will present a Table crossed page boundary error if this directive is located on a different page than which was stored by the previous .OT directive.

Please note that a page refers to 256 contiguous words here and not 512 words when referring to memory mapping pages for the PIC family.


Tables of data will not allow bits b10..b8 of the program counter to change in between. That's why the .OT and .CT directives are used to signal the beginning and the end of a table, verifying that a page crossing hasn't occurred in between.

The .OT directive should be placed at the beginning of the table. It will memorize the memory page of the beginning of the table. At the end of the table the closing .CT directive should still be on the same memory page. There's only one exception to this rule, and that is when the .CT is at the first location of the next page, because then the table ended just in time on the previous page.
No error message is reported when both pages are equal. A Table crossed page boundary error is reported when they do differ during pass 2 of the assembly process. The error message is only reported during pass 2 to enable you to find out at what location the table crossed the page boundary in order to fix the problem by either moving the table or moving away some code in front of the table.
No error or warning is shown when there was no matching .OT directive found.


The example below shows a typical lookup table routine. It converts a decimal digit to a seven segment pattern. I think it clearly demonstrates the use of the .OT and .CT directives.

            .OT                   Mark the beginning
DEC2SEGM    MOVLW SEGMENTS        Add offset to digit number
            ADDWF DIGIT,W
            MOVWF PCL             Jump to location in table

SEGMENTS    RETLW #%0011.1111     0  Return with pattern for '0'
            RETLW #%0000.0110     1  Return with pattern for '1'
            RETLW #%0101.1011     2  Return with pattern for '2'
            RETLW #%0100.1111     3  Return with pattern for '3'
            RETLW #%0110.0110     4  Return with pattern for '4'
            RETLW #%0110.1101     5  Return with pattern for '5'
            RETLW #%0111.1101     6  Return with pattern for '6'
            RETLW #%0000.0111     7  Return with pattern for '7'
            RETLW #%0111.1111     8  Return with pattern for '8'
            RETLW #%0110.1111     9  Return with pattern for '9'
            .CT                   End of table, checking page

Please note that changing the PCL this way causes a jump to the address which is composed of PA1,PA0,0,PCL. PA1 and PA0 are bits in the Status register, which are copied to bits 10 and 9 of the program counter respectively. Bit 8 of the program counter will always be made 0.

.CW     Config Word


        .CW  #expression


This directive is used to set the Config Word flags of the device which is used to select the oscillator type and other settings.


The Config Word is used to select the oscillator type, code protection, watchdog settings and various other options, depending on the device type you're using. So please consult the data sheet of your device to find out what bits to set or clear for your required options.
The Config word is usually located at word address $0FFF (byte address $1FFE and $1FFF). You may put the .CW directive anywhere outside the program memory, but Microchip recommends you to put it at address $0FFF so it can be understood by all chip programmers

The .CW directive is # symbol tolerant. This means that you may precede your bit pattern by a # symbol if you like, but you don't have to.
The bit pattern you enter may not exceed 12 bits. Entering larger numbers, or negative numbers, will result in a Out of range error.

The .CW directive is only allowed outside the program memory space, which is set-up by the .MS directive. If you do try to use the .CW directive while in program memory space you'll get a Directive only allowed beyond program memory error.


          .OR    $0FFF            Put CW in recommended address
          .CW    %0000.100.0100   Just an arbitrary pattern of bits

.ID     ID words


        .ID  expression


This directive allows you to set-up the four 4-bit ID words of the device.


Every device contains four 4-bit ID words, which can be loaded with any value you like. They serve the purpose of identifying programmed parts. The ID words can only be read during program/verify mode and are not accessible during normal operation. Microchip recommends to place the 4 ID words directly behind program memory. The SB-Assembler will allow you to place the ID words anywhere outside the program memory.

You'll get a Directive only allowed beyond program memory error message if you try to use the .ID directive somewhere in program memory.
You should use the .ID directive only once in your program to set-up the ID words. No error is reported if you do have more occurrences of the .ID directive in your program though.

The expression will be split into 4 nibbles and these nibbles are stored each in its own 12-bit word. All other bits of the ID words remain 0. The range of the expression is not checked, the assembler will simply use the 4 least significant nibbles.

There is a small difference between SB-Assembler 2 and 3. In version 3 the ID word is written in Little Endian mode, which means that the least significant nibble is stored first. In version 2 the ID word was written in Big Endian mode.


0123-                   .OR    $0200        Start directly beyond code memory
0200-04 00 03 00
0202-02 00 01 00        .ID    $1234
0204-04 00 03 00
0206-02 00 01 00        .ID    #$1234       Directive is # tolerant

.MS     Memory Size


        .MS  expression


This directive is used to set the expected program memory size of your particular device.


The .MS directive is preferably used before any code is generated. Although the SB-Assembler is perfectly happy if you use the directive just in time before you actually run out of the default memory size of $0200 words.

There are several reasons why you are required to specify the program memory size (in words, not in generated bytes). The most important reason is to be able to determine where data bytes must be translated to RETLW instructions. Another reason is to verify if the .ID and .CW directives were not used in program memory space. It allows the SB-Assembler to determine the proper translation for LCALL and LGOTO pseudo instructions. Finally it gives the SB-Assembler the opportunity to warn you if you try to use more memory than is actually available.

The .MS directive will accept any expression resulting in a value up to 2048 or $0800 (i.e. 2k words). A Out of range error will be reported if the expression evaluates to a value above 2048 or below 256. The expression may not contain forward referenced labels.
Any other size is accepted, even silly ones.

The default memory size is $0200 words.

.OT     Open Table




This directive signals the beginning of your table in memory.


.OT saves the current memory page address internally. Later on it should match the memory page address of the next .CT directive.

The .OT directive will automatically call the .CT function if you previously started a table without closing it properly with a .CT directive. After closing the previous table this way the new table will start at the current location as if you had inserted a .CT directive yourself. This way you can concatenate several tables after each other.
Remember though that it becomes more difficult to find a piece of memory large enough to hold all your concatenated tables if you rely on this automatic closing of previously opened tables.

Please note that the .OT directive would never generate the Table crossed page boundary error by itself if it wasn't for the automatic calling of the .CT routine when a previous table wasn't closed.

Extra Error Messages

A few extra error messages are added to the standard repertoire of error messages.

Table crossed page boundary

This error is generated by the .CT directive and sometimes by the .OT directive.
The error is only generated if the .CT directive is on a different memory page than the previous .OT directive. If the error occurs it will be reported during pass 2 of the assembly process only. This is to simplify the search for a suitable new place for your table. You would have no way of telling at what address the page was crossed if the error was reported during pass 1 instead.

Directive only allowed within Code memory

This error is triggered because you tried to generate instruction words beyond the end of the program memory space. You're only allowed to save data, ID words and a Config word beyond the end of the program memory space.
The program space boundary can be set by the .MS directive.

Directive only allowed beyond program memory

This error is reported because you tried to use the .ID or .CW directive while still being somewhere in program memory space.
Remember to set the program memory size (.MS) correctly!

Overlay Initialization

Five things are set while initializing the PIC12 overlay every time it is loaded by the .CR directive.

  • Little endian mode is selected for the data generating directives. This means that words or long words are stored with their low byte first.
  • Default memory size is set to $0200.
  • The target factor is set to 2, meaning that the target address will always be twice as high as the program counter.
  • An internal pointer is changed so that generated data bytes will be stored as RETLW instructions.
  • Some extra error messages are added to the list of standard error messages.

Differences Between Other Assemblers

There are some differences between the SB-Assembler and other assemblers for the PIC12 family processor. These differences require you to adapt existing source files before they can be assembled by the SB-Assembler. This is not too difficult though, and is the (small) price you have to pay for having a very universal cross assembler.

  • The handling of ID words and the Config word may differ from other assemblers.
  • Not all assemblers translate data bytes into RETLW instructions automatically.
  • Not all assemblers will allow multiple literal values, or none at all, following one RETLW mnemonic.
  • The obvious differences in notation of directives and radixes common to all SB-Assembler crosses.
  • None of the SFR registers and bit names are pre-defined. They can be declared using normal labels though.