PIC 16c5x Introduction

Microchip offers a wide range of PICmicro RISC controllers. This page describes the SB-Assembler's behaviour for the Base Line PIC16C5x family.

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.

The RISC architecture of the devices requires some getting used to. All instructions occupy only one instruction word. These instruction words are 12 to 16 bits wide, which reflects on the memory layout of the target file.

The maximum ROM capacity for this series of controllers is 2 k words of 12-bits each. This address range is divided into 4 memory pages of 512 words each.

Data memory is only 8-bits wide and is organized as the so-called register file F, which is described shortly.

SB-Assembler Version Differences

This page describes the PIC Base-line cross overlay for SB-Assembler Version 2 only. Microchip has made quite a little mess of the instruction sets throughout the entire family that it has become impractical to handle both versions on one page. After all Version 2 of the SB-Assembler is no longer maintained, therefore new instructions are not added to that version any more.
If you want the latest and greatest cross overlay for the PIC Base-line family please go to the Base-line page for SB-Assembler V3.

Programming Model

I only include a little summary about the features of the PIC16C5x programming model here. It is not my intention to make the original documentation obsolete, so please refer to the original documentation for further details.

PIC16C5x 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. 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 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 Indirect Data Addressing

This is not a physical register but is used to indirectly address any of the register file's data. If you refer to address f$00, you actually address the file register pointed to by the contents in FSR (f$04).

f$01 RTCC

The Real Time Clock Counter is just a free running 8-bit counter that can be used for timing operations. The contents of the register can be incremented by every pulse on the RTCC pin, or by the internal instruction clock CLKOUT (fosc/4).

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 PIC16C5x 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 7PA2For future use
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 the reason why the device was restarted and are read-only bits. The bits PA0 and PA1 are used to select the program memory bank. These bits may be used as general purpose flags on devices where the affected banks are not implemented. The PA2 bit is reserved for future devices and may be used as general purpose flag.

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.

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.
The I/O register may be used as a general purpose register if a port is not implemented in a particular device.


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 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.

Reserved Words

The SB-Assembler 16C5x 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 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.

Every processor allows you to write 4 ID words at the end of the ROM capacity of the device. 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.
You can include the ID words in your target file at the end of the program 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 address $0FFF. Please refer to the new directive .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

The memory organization of the PIC devices forced me to change the behaviour of all data generating directives, like: .AS, .AT, .AZ, .BI, .DA, .DL, .DR, .HS and .RF
All data generating directives will now generate RETLW instructions instead of normal data bytes when the destination address is within the program memory range. If the destination address is outside the program area all data will be saved as words, with the low byte filled with the data and the high byte of the word being $00. This last feature is not of much use because none of the current 16C5x devices is equipped with memory beyond the program memory area. All data stored outside the program area will get lost, except when you define a Config Word this way.

Disabled Directives

I had to disable a few standard directives due to the way the PIC cross overlay handles the target files. The disabled directives are: .DU, .ED, .PH and .EP
These directives were not very useful to the PIC crosses anyway, so we can do very well without them.

You'll get a fatal *** Illegal directive error if you do try to use any of the above directives.

.CR directive

The .CR directive hasn't changed actually. But you better don't rely on the SB-Assembler's multi-processor capabilities for the PIC family because of the way the target files are generated. This means that you better not use more than one .CR directive in your PIC programs.

File Register addressing

File registers are numbered from $00 to $7F, while the actual addressing range runs from $00 to $1F. The SB-Assembler accepts addresses from $00 to $7F 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.

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.


        RETLW  $12,$34,$56

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 PIC16C5x 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 *** Range error if you try to CALL to a location in the second half of any particular program memory space.

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.

.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 bytes here and not 512 bytes when referring to memory mapping pages for the 16C5x family.


Tables 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 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. But 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 some code in front of the table.


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  [flags]
        .CW  #expression


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


The Config Word of the 16C5x family of devices selects one of four oscillator types, whether the code is protected or not, and whether the watchdog is active or not.
The Config word is usually located at address $0FFF and will end up on addresses $1FFE and $1FFF in the target file. 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.

You may use the named flags if you like. The table below shows all possible flags. The oscillator names select 2 bits at a time, and may only be entered once in the flag list. You'll get an *** Illegal Config bit error if you try to use more than one oscillator option or if you use an unrecognized flag name. Multiple flags must be separated from each other by commas.

FlagConfig word bit relationship
CPbit 3 is 0 if CP is listed
WDTEbit 2 is 1 if WDTE is listed
RCbits 1 and 0 are both set if RC is listed
HSbit 1 is set, bit 0 is cleared if HS is listed
XTbit 1 is cleared, bit 0 is set if XT is listed
LPbits 1 and 0 are both cleared if LP is listed

You may also specify a bit mask yourself, in which case the first character of the operand must be a # symbol. The value you enter will be truncated to 12 bits, even though only the least significant 4 bits are actually implemented as Config word flags in the hardware devices. You may not mix named flags and the immediate bit mask in one operand though.

You may also leave the operand field completely blank. The SB-Assembler will then assume a bit mask of %0000.0000.1011, disabling code protect and watchdog timer and selecting an RC oscillator.

The .CW directive is only allowed outside the program memory space, which is set-up by the .MS directive. You'll get a *** Data memory allocation error if you do try to use the .CW directive within program memory space.


          .OR    $0FFF       Put CW in recommended address
          .CW    XT,WDTE     Select crystal and watchdog enable

          .OR    $0FFF       2nd example (not in same source file)
          .CW    #%0100      Select LP oscillator and code protect

.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 fatal *** Data memory allocation 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.


0123-                   .OR    $01FF       Last program address for
                        ;                   $200 word device
01FF-00 0A              GOTO   START       Start executing the program

0200-01 00 02 00
0202-03 00 04 00        .ID    $1234

.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.

There are several reasons why you are required to specify the program memory size (in words, not 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. 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 *** Range error will be reported if the expression evaluates to a value above $0800. The expression may not contain forward referenced labels.
Any other address is accepted, even silly addresses like $0000. Please feel free to make your programming job a nightmare by selecting silly program memory sizes.

The default memory size is $0200.

.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 error

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.

*** Illegal config bit error

This error is given because one of the flag names behind the .CW directive could not be recognized or because an oscillator was selected for the 2nd time.

*** Illegal directive error

This is a fatal error, triggered by the use of any of the illegal directives for the PIC cross overlay. The illegal directives are: .DU, .ED, .PH and .EP

*** Program memory allocation error

This fatal error is reported 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.

*** Data memory allocation error

This fatal 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

Three things are set while initializing the 16C5x overlay every time it is loaded by the .CR directive.

  • Little endian model is selected for the .DA and .DL directives. This means that words or long words are stored with their low byte first.
  • Default memory size is set to $0200.
  • Control of storing bytes in the target file is taken over from the main assembler to enable the saving of words instead of bytes.

Differences Between Other Assemblers

There are some differences between the SB-Assembler and other assemblers for the 16C5x 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 following one RETLW mnemonic.
  • The obvious differences in notation of directives and radixes common to all SB-Assembler crosses.
  • Don't forget that the SB-Assembler does not allow spaces in or between operands.