PIC Paging and PCLATH

Contents:

see also related pages:

Do I need to worry ?

High level languages ( languages.htm ) like BASIC and C and JAL just take care of this stuff. So C coders do not need to worry about it unless they inline ASM.

If you are lucky enough that your program fits in the first 2 Kwords or less, then you do not have to worry about PCLATH for any CALL or GOTO. CALL and GOTO have enough bits to directly call every destination in your program without having to modify PCLATH from its power-up value of 0.

If you are writing a simple subroutine (one that does not call other subroutines, does not do any "computed gotos", and does not cross any 2 Kword "page boundary"), you do not need to worry about PCLATH. When this subroutine was called, PCLATH must have already been set appropriately, so local GOTOs (used to implement loops and if-then-else branches) inside the subroutine work correctly. You do not need to set PCLATH on return, because

If you write any code that does "computed GOTOs" ( for example, for reading from a constant array or table tables.htm ) then you will have to worry about PCLATH, even if the entire program plus all the tables fit in the first 2 Kwords.

If you're writing the interrupt service routine, there are some ISR considerations .

Details of the interaction of PC and PCLATH

[FIXME: which PICs does this apply to ? which PICs does this *not* apply to ?] This applies to the 14-bit PICs (the 16Cxx and 16Fxx series except 16C5x), and the 16F87x, and...

Core Size   Page Size

12-bit           512
14-bit           2048
16-bit           8192

The "flow control" instructions ( CALL, GOTO, RETLW, RETFIE, RETURN, and any instruction that directly modifies the PC like ADDWF PCL ) modify all 16 bits of the PC at once.

This can be confusing, because none of the flow control instructions have enough address bits to specify the destination address completely. Where do the remaining bits come from ?

Note that the PCLATH register is *not* the Hi byte of the program counter PC.

[FIXME: Should I make a distinction between the 8 bit ``PCL'' vs. the 16 bit ``PC'', or should I just talk about a 16 bit ``PCL'' ?]

PCLATH and ISR considerations

No matter which paging strategy you pick, if you write an Interrupt Service Routine, it must save the original PCLATH on entry (typically in a register named ISR_PCLATH) and restore it on exit. See "Saving register state in an Interrupt Service Routine" for more information.

Roger Froud of Amytech Ltd. noted:

BEWARE!!! you must avoid GOTO INTERRUPT at the interrupt vector as this inherits the page bits from the page that's interrupted! Just put a NOP at the interrupt vector and drop through to an interrupt routine in bank 0 at address h'0005' I use a LONGCALL after saving the registers to place the bulky interrupt main code in page 2.

[``LONGCALL'' is the same as the ``fcall'' macro below]

Paging Strategies

If your program makes a far call (sets PCLATH and then does a CALL), and the subroutine it calls changes PCLATH again (so it could jump or call), that change remains after the routine has returned. So you can't assume that PCLATH is where you put it after a call unless you are very consistent about your handling of this issue.

Be very consistent in setting PCLATH before GOTOs, CALLs, Table lookups, and any other instructions that modify the PC. Pick a method and use it every time. There are several methods (paging strategies) in common use.

I [David Cary] currently use the caller fixes PCLATH paging strategy. It's not original with me; I think I got it from Olin Lathrop of Cognivis with very little modification.

I also discuss some other paging strategies that have some advantages (and disadvantages) compared to the caller fixes PCLATH paging strategy.

Unfortunately, there's a lot of code that uses manual bit flipping, even when

compared to the relatively simple paging strategy.

simple paging strategy

Olin Lathrop of Cognivis recommends:

1 - Individual modules are not allowed to cross page boundaries. I make sure this happens automatically by creating a separate linker section for each page. There are occasional exceptions which so far have all been large data module (tables, etc.).

2 - always use "far" calls (fcall) and far gotos (lgoto). Use ``lgoto'' (built into MPASM) and ``fcall'' (defined above) for each and every GOTO, CALL, etc., then PCLATCH will always be set correctly and your program will run.

Use a macro to conditionally compile the appropriate instructions even including the jump, call or whatever. This is nice as it

The *only* reason not to use this strategy is that it generates slightly larger and slower than some of the strategies listed below. (But some of the strategies below have obscure ``gotchas'', causing minor changes in the code to make the code jump into hyperspace ). Also, since ``lgoto'' and ``fcall'' expand into multi-instruction sequences, the ``skip'' instructions (BTFSS and BTFSC) don't work right immediately before a "lgoto" or "fcall" macro call.

If you are working with a Scenix SX, there is a PAGE instruction (also supported) that loads a literal 3 bit value into the top of the PCLATH. The SXKey will automatically insert it if you put a @ before the target label in your CALL or JMP.

The SX has a RETP instruction that restores PCLATH on subroutine returns, so you don't have to.

MPASM recognizes the PAGESEL so it allows you to write relocatable modules that can be linked with code from other languages (C for example) after assembly. PAGESEL and BANKSEL and BANKISEL assemble to a set of 0 to 3 BCF/BSF instructions.

WARNING: old versions of MPASM sometimes assemble PAGESEL and BANKSEL to a MOVLW followed by a MOVWF PCLATH. That trashes W.

I'm told that future versions of MPASM will settle on BCF/BSF sequences for PAGESEL and add a new PAGESELW that always does the MOVLW thing.

caller fixes PCLATH paging strategy

(RECOMMENDED) (David thinks you should write code this way unless you *know* what you are doing).

Olin Lathrop of Cognivis recommends:

1 - Individual modules aren't allowed to cross page boundaries. I make sure this happens automatically by creating a separate linker section for each page. There are occasional exceptions which so far have all been large data module (tables, etc.).

2 - The high bits of PCLATH are always set to the page of the current module. This means CALLs and GOTOs within a module can always be done in one instruction without setting PCLATH first.

3 - PCLATH is always set before a CALL or GOTO to a label outside the current module.

4 - PCLATH is always reset to the current address after return from an external subroutine.

Use ``fcall'' and ``lgoto'' if the destination is outside the module. Use plain "call" and "goto" instructions if the destination is within the same module. (If you use ``fcall'' where you only needed to use ``call'', the compiled program will be a few bytes longer and a few cycles slower, but it will still run. But be very carefull you do not use ``call'' where you needed to use ``fcall'' -- your program will jump to a completely unexpected location at run time).

This method will produce relocatable, linkable code.

Disadvantage: *no* warnings if CALL or GOTO use the name of a subroutine on another page, but (when actually executing) don't really go there.

The GCALL macro by Olin Lathrop in STD.INS.ASPIC at http://www.embedinc.com/pic is slightly more sophisticated than the ``fcall'' macro below, but it does basically the same thing and has exactly the same disadvantage.

; example program

; the fcall macro
; by Roger Froud of Amytech Ltd.
fcall	macro subroutine_name
	local here
	lcall subroutine_name
	pagesel here
here:
	endm

interrupt_handler CODE 0x0005
	...
	RETFIE

another_code_section CODE

main_loop:
	fcall test1
	fcall test2
	call test4
	fcall test3
	goto main_loop

test4:
	return

yet_another_code_section CODE

test1:
	call test2
	call test3
	fcall test4
	return

test2:
	return

test3
	return

END

jump tables

One interesting paging strategy is to use a "jump table". This gives the more compact code than any other paging strategy (but not the fastest code). Compilers using the ``optimize space'' option should use jump tables.

; sample code
page 0 : 
	..
	call p0do_something_useful
	clrf PCLATH
	...
	call p0do_something_useful
	clrf PCLATH
	....
	p0do_something_useful:
	bsf pclath,page1bit
	errorlevel -306 ; supress warning, you know what you're doing...
	goto really_do_something_useful
	errorlevel +306 ; re-enable warning to catch real errors.

page 1 :
really_do_something_useful:
	....
	return

... ; other subroutines

The SX has a RETP instruction that makes this the most compact paging strategy I've seen:

; sample code
page 0 : 
	..
	call p0do_something_useful
	...
	call p0do_something_useful
	....
	p0do_something_useful:
	bsf pclath,page1bit
	errorlevel -306 ; supress warning, you know what you're doing...
	goto really_do_something_useful
	errorlevel +306 ; re-enable warning to catch real errors.

page 1 :
really_do_something_useful:
	....
	RETP

... ; other subroutines

If one is unable to use the SX has a RETP instruction, you can get almost as compact as the above by combining ``jump tables'' with ``globals return to page 0'' .

Mike Harrison [mike at WHITEWING.CO.UK] combines ``jump tables'' with ``globals return to page 0'':

This particular problem, of subroutines called from lots of places, is easily solved efficiently - for each page-1 routine have a page-select and goto to it in page zero. adds a minor time penalty but space is usually more important.

After the first call of a subroutine, this adds no page-swap code overhead for subsequent calls, so is 'profitable' for any routines called more then once.

It can also be useful for all page 1 subroutines to exit via a common point that clears the page 1 bit first. This avoids the page 0 call having to worry about whether the routine is in page 0 or 1 - things tend to move about as code grows & changes...!

; sample code
page 0 : 
	..
	call p0do_something_useful
	...
	call p0do_something_useful
	....
	p0do_something_useful:
	bsf pclath,page1bit
	errorlevel -306 ; supress warning, you know what you're doing...
	goto really_do_something_useful
	errorlevel +306 ; re-enable warning to catch real errors.

page 1 :
	really_do_something_useful:
	....
	goto pexit

... ; other subroutines

pexit:
	bcf pclath,page1bit ; or: clrf pclath
	return

Having a consistent naming convention is important to avoid major debug headaches.

Obviously it helps a lot to group subroutines by page to reduce the number of page-bit swaps, e.g. all subs called by p1sub1 above live in page 1.

For routines called only once, returning via the 'clear page bit' exit point is still useful as the calling code doesn't need to clear it.

global callee clears PCLATH

1 - Individual modules aren't allowed to cross page boundaries.

2 - The high bits of PCLATH are always set to the page of the current module. This means CALLs and GOTOs within a module can always be done in one instruction without setting PCLATH first.

3 - Every module that makes "external" calls to some other page is placed on page zero. Those calls (from page 0 to some other page) set PCLATH (possibly using a jump table) before going to that routine.

4 - "global" routines on pages other than zero are responsible for clearing PLATH to zero before returning. (They must be returning to a CALL from page zero). (typically with CLRF PCLATH; RETURN)

5 - (optional) - "local" routines don't modify PCLATH.

Advantages:

If this is combined with "jump table", then all calls are ``short'' -- no need to worry about whether this is a ``long call'' or a ``short call''. Also, all GOTOs (except in the jump table) are short.

Takes less ROM space, because adjusting PCLATH on return is done once in the subroutine, rather than at every call of the subroutine.

disadvantages: all other page strategies have only 1 kind of subroutine. This requires 2 kinds of subroutines: "local" subroutines that never touch PCLATH. "global" subroutines that clear PCLATH before return. Brittle: no warning if you accidentally call a global routine from somewhere other than page0.

; example
page_0_stuff CODE 0x0005
	;...
	; ISR stuff here
	;...
mainloop:
	; ...
	lcall do_something_useful
	goto mainloop

page_1_subs CODE 0x0800

;...

; this is a local routine -- it must *only* be called
; from this page,
; never from page0 or any other page.
help_do_something_useful:
	nop
	return

; this is a global routine -- it must *only* be called
; from page0,
; never from this page or any other page.
	GLOBAL do_something_useful
do_something_useful:
	call help_do_something_useful
	; ...
	CLRF PCLATH
	return

other paging strategies

Here are some of the more common paging strategies. Unfortunately, none of these will produce relocatable, linkable code.

[1] OK, technically, the PC isn't really 16 bits. It is just barely big enough to address all the ROM space: 13 bits on PICs with 8 Kwords of program ROM (most PICs); 10 bits on the 16F84 and other PICs with 1 Kword of program ROM.

If I go on pretending it *is* 16 bits, will I ever write code that doesn't work ? Can you give an example that would give different results on the current PICs vs. a new, hypothetical PIC that has a 16 bit PC, and only the lower 10 or 13 bits are connected to the program ROM ?

2001-05-08:DAV: David Cary took ownership of this page. I'm trying to clean up some of the redundant information and confusing errors here.

Archive:

Questions:

David A Cary of Motorguide Pinpoint Says:

link to http://piclist.com/techref/microchip/instr/call.htm which discusses jump tables, and CALL on 12-bit-core PIC.

Questions:

Comments:

Questions:

Interested: