Tom Hartman says:

...I came up with a method for a "switch" statement, its a little faster than average because it doesn't reload the variable for each case. Each case is 3 clocks if not equal, 4 clocks if equal. Code can appear between cases, but be careful not to alter the contents of w.
; Switch, case macros:
; Typical usage:
; #define CONSTANT1 1
; #define CONSTANT2 2
; SWITCH_F     file_register
; CASE_W  CONSTANT1, do_case_1
; CASE_W  CONSTANT2, do_case_2
;
; --- Or where W is already the variable---
; SWITCH_W
; CASE_W  CONSTANT1, do_case_1
; CASE_W  CONSTANT2, do_case_2
;--------------
SWITCH_W  macro
_previous_case set  0         ; W already contains the switch variable
          endm
;--------------
SWITCH_F  macro     f_label
          movf f_label,w ; read the location into w
_previous_case set  0
          endm
;--------------
CASE_W         macro     case_const, case_label
          xorlw     case_const^_previous_case
          btfsc     STATUS,Z
          goto case_label
_previous_case set  case_const
          endm
;------------------------------------------------------------------

Rich Leggitt says:

...I thought of a way to cut 'switch' to one instruction, it ain't gonna get any smaller than that :)
switch  macro
        retlw $+1               ; return 'return'
        endm

context equ 0x20

        movlw task1             ; note task 1 will run first
        movwf context
        movlw task2

        ; task switcher
        call $+2                ; make a place for 'switch' to return to
        goto $-1                ; (i.e. here!)
        xorwf context,f         ; then exchange w and context
        xorwf context,w
        xorwf context,f
        movwf pcl               ; and jmp to w

task1   blah
        switch
        etc
        switch
        stuff
        goto task1

task2   asdf
        switch
        zxcz
        goto task2

to which Scott Dattalo commented:

Cool! Now that you write this, I recall someone else (I believe it was Payson) doing something similar. Now that you've got the 'context switching' all confined to one section, you've opened up the possibilities of extending its functionality (without having duplicate code snippets scattered throughout).
  1. ) You could easily add additional tasks. Cycling through each can be done in a 'round-robin' fashion. For example, after task A then run task B, C,..., N, and back to A.
  2. ) You could add priorities to the tasks.
  3. ) With an extra instruction in the 'switch' macro, you could handle multiple pages:
    switch   macro
             movlw  HIGH($+2)
             retlw  $+1
             endm
    
    -----------------------
    

If you're using the 12bit core, then you might consider populating the stack with the address of the context switcher. You could do the same with the 14bit core - but chances are you're more likely to need the stack for making calls. (on the 12bit core, the stack is only 2 levels ) Once the stack is filled, then the retlw's will take advantage of stack roll overs.

         movlw  task1
         movwf  context
         movlw  task2

         setc
         clrf   first_time

         goto   l2
l1       rlf    first_time,f
         btfss  first_time,1
l2       call   l1
l3       xorlw context,f         ; then exchange w and context
         xorlw context,w
         xorlw context,f
         movwf pcl               ; and jmp to w

for the 14bit core you could fill the stack:

         movlw  task1
         movwf  context
         movlw  task2

         setc
         clrf   first_time

         goto   l2
l1       rlf    first_time,f
         btfss  first_time,7
l2       call   l1
l3       xorlw context,f         ; then exchange w and context
         xorlw context,w
         xorlw context,f
         movwf pcl               ; and jmp to w

I haven't tried this, but once you get the stack 'primed', you can save 4 execution cycles.

Multitasking methods@

see also: