asm: Swapping adjacent file registers, PIC16F

Discussion in 'Programmer's Corner' started by Robin66, Jun 21, 2016.

  1. Robin66

    Thread Starter Member

    Jan 5, 2016
    Hi. I'm writing a bubble sort in assembly. I have adjacent bytes containing values and I'm looping through them swapping adjacent values when they're out of sequence. My subfunc to swap adjacent bytes is below, but I can't believe this is the most efficient way of doing it. vara is an intermediary variable and FSR points at the upper byte. Is there a trick I'm missing? I've seen the xor trick that allows swapping defined registers thru W only (no intermediary var required), but I don't see how I can do so with relative addressing.

    Code (Microchip Assembler):
    1. swap
    2.     movf INDF,w   ; set aside upper byte
    3.     movwf vara
    4.     decf FSR     ; move lower byte into w, then into upper byte
    5.     movf INDF,w
    6.     incf FSR
    7.     movwf INDF
    8.     decf FSR     ; put vara into lower byte
    9.     movf vara,w
    10.     movwf INDF
    11.     return
    Mod edit: code tags
    Last edited by a moderator: Jun 21, 2016
  2. JohnInTX


    Jun 26, 2012
    I think this does what you want. Note the two adjacent registers are referenced using INDF and FSR is incremented/decremented to select the bytes as shown above. I like the fact that it leaves FSR pointing to the next byte..

    Code (Microchip Assembler):
    2.   ; *********** SWAP BYTES AT *FSR  *************
    3.   ; Swaps byte at *FSR with byte at *(FSR+1)
    4.   ; Leaves FSR at +1
    5. SwapByte_FSR:
    6.                         ;-- INDEX --
    7.     movf    INDF,W      ; +0
    8.     incf    FSR,F
    9.     xorwf   INDF,F      ; +1
    11.     movf    INDF,W      ; +1
    12.     decf    FSR,F
    13.     xorwf   INDF,F      ; +0
    15.     movf    INDF,W      ; +0
    16.     incf    FSR,F
    17.     xorwf   INDF,F      ; +1
    19.     return
    I did a quick check and it seems to work correctly. Let us know if it works for you.

    Some observations on your original code:
    Use a ':' after a label - it makes it easier to identify both on screen and when searching the source. If you ever use macros, you'll appreciate the visual, too. Without the colon, a label looks just like a macro invocation and can be confusing to look at and debug. MPASM will appreciate it as well.
    You are using the default destination (F) for your moves. A better technique is to always explicitly specify the destination i.e. ,F or ,W. One day you'll forget the ,W somewhere and it will be much harder to find the error. If you always explicitly specify the destination, the lack of one on the instruction will jump out at you.

    Have fun!

    Edit: removed original example for clarity
    Last edited: Jun 21, 2016
    MaxHeadRoom likes this.
  3. dannyf

    Well-Known Member

    Sep 13, 2015
    What if you throw this to a C compiler and see if it can beat your code?
  4. jpanhalt


    Jan 18, 2008
    Have you learned about PicList? Lots of good toolbox routines there.

    Here is the swap routine:
    Code (ASM):
    2.      movf  rega,w      
    3.      xorwf regb        
    4.      xorwf regb,w      
    5.      movwf rega        
    6.      xorwf regb
    Curious how may instructions C takes.

  5. JohnInTX


    Jun 26, 2012
    But it's assembler, not C, and direct addressing isn't suitable for bubble sorting a list in RAM. I do like saving the one logical operation, though.
    Last edited: Jun 21, 2016
  6. Robin66

    Thread Starter Member

    Jan 5, 2016
    Thx for the tips JohnIn. I had noticed colons being used by some contributors but I wasn't sure which was more correct. And thanks other John, I'll check out PicList.
  7. jpanhalt


    Jan 18, 2008
    Agree on the direct addressing. My intent was only to show what was available on PicList with very minimal change. Changing it to indirect addressing would not be hard to do.

    As for the colon after labels, I know some people get emotional about that. I don't and don't suspect you do either. Almost all current Microchip examples use white space rather than a colon, however (Source: MPASM User's Guide, DS33014J, Example 1-1, page 25) :

    Since MPLab 8.92 puts the label in magenta by default,


    I do not find it difficult to distinguish a label from the rest of the code. I do use a lot of spacing while working on code, but that is mainly because I find it hard to read something on a monitor.

  8. Robin66

    Thread Starter Member

    Jan 5, 2016
    It's a shame I don't have 2 FSRs. Then I could swap using 2 fixed addresses, circumventing the FSR hopping. I guess this is one of those examples which shows that some of the additional features are really useful.
  9. jpanhalt


    Jan 18, 2008
    If you move to a different chip, like a 12F1xxx or 16F1xxx you will have two FSR's plus automatic increment in the enhabced midrange instruction set. I use the 16F1829 or 12F1840 as a cheap goto chips.

  10. JohnInTX


    Jun 26, 2012
    @jpanhalt John, your points are valid and well considered, I didn't mean to sound overly pedantic with my recommendations to @Robin66 and certainly didn't intend to fire the opening salvo in the colon wars :D. And you're right, I don't get emotional about things like that unless my name is on the front of the checks. Then, yeah..

    But at the risk of sounding overly defensive, I can attest that these and other recommendations I've been known to make here and sometimes even for actual money, are virtually all from lessons learned the hard way. Colons on a label help avoid confusion with macro names and invocations. MPASM is kind of slack in that area so the visual helps me. I didn't always use the colon. After a potentially expensive typo in a source that would have burned a batch of OTPs with bad code, now I do.

    I like always specifying the destination rather than letting MPASM take the default. For one, I don't have to remember what the default is nor expect another reader to know but mostly it's that if you always specify the destination, including the default F, then even a cursory review of the source will reveal the lack of that specific destination and identify a potential problem. Relying on the default leads to things like
    movf register
    Did I intend the destination to be F or did I just forget the ,W? Hard to tell without a detailed reading. MPASM is no help. In this case, you get a warning about the default, even when its what you intended, making it worthless. OTOH, if your personal rule is to always specify the destination, then lack of same jumps out in the source and the warning from MPASM becomes relevant again.

    Like you've indicated, I also like my source to pop off the page (even monochrome printouts) and so take pains to make the formatting style as consistent as I can. Sometimes, I get tired and cheat. I usually come to regret that. As for the Microchip code snippet, it illustrates a concept without a lot of baggage. I don't know if it is intended to be an example of good coding practice. If so, I'd have some issues with it, and not just the label..

    I guess the point is as programs get bigger and more complex some sort of convention becomes a necessity rather than ego. It doesn't have to be my convention. But as I write, I just wrapped up some revs to a little 18F4685 based CAN gizmo written in assembler. It builds to about 48Kwords and the list file runs 903 pages. The source is in 71 files, not counting the RTOS or math library. Even 4 years after the original release, I can look at the code and pretty much it reads itself. That's why I get, if not emotional, shall we say 'firm' about such things :cool:.

    Anyway, I appreciate the link and the faster swap code. Much appreciated. I took the liberty of converting it to indirect addressing and added a macro to make the direct one less error prone to use (after screwing up one of those destinations..). Here is the result along with a little test loop for MPSIM. Hopefully, Robin66 and others can make use of it. I've added it to my library.

    Thanks again.
    Edit: changed references to default as W. The default is of course the file register...

    Code (Microchip Assembler):
    1. ;*********** BYTE SWAPPING  ******************
    3.     #include
    5.     ;*********** EXCHANGE BYTES USING DIRECT ADDRESSING  **********
    6.     ; Generates code to exchange two bytes in the same bank using
    7.     ; direct addressing.
    8. xchg2bytes macro b1,b2
    9.     expand
    10.         movf  b1,W
    11.         xorwf b2,F    
    12.         xorwf b2,W
    13.         movwf b1  
    14.         xorwf b2,F
    15.     noexpand
    16.     endm
    18.     ;**************** DATA  ********************
    19.     cblock 20h      ; some data to play with
    20.     regA:   1
    21.     regB:   1
    22.     regC:   1
    23.     regD:   1
    25.     saveFSR:1       ; used to exchange pointers (FSR)
    26.     endc
    28.     ;**************** TEST PROGRAM  ************************
    29.     ; Step through this and observe the exchanging in the
    30.     ; File Registers window.
    32.     ORG 0
    33.     clrf    STATUS
    35.     ;------------- TEST CODE TO RUN IN MPSIM ------------------
    36.     ; Calls various xchg routines in a loop.  Observe the
    37.     ; xchgping of 20h and 21h in the File Registers window
    38. xchgLoop:
    39.     movlw   'A'                 ; preset some values
    40.     movwf   regA
    41.     movlw   'B'
    42.     movwf   regB
    43.     movlw   'C'
    44.     movwf   regC
    45.     movlw   'D'
    46.     movwf   regD
    48.     call    xchgAB_Direct     ; xchg using direct addressing
    50.     movlw   regA                ; xchg back using midrange FSR
    51.     movwf   FSR
    52.     call    xchgByte_FSR
    54.     call    xchg_regAregC      ; xchg A and C
    56.     movlw   regC                ; test pointer exchange
    57.     movwf   saveFSR
    58.     movlw   regA
    59.     movwf   FSR
    60.     call    xchgByte_FSR        ; exchange A and B using FSR
    61.     call    xchgFSR             ; use alternate FSR to..
    62.     call    xchgByte_FSR        ; C and D
    64.     goto    xchgLoop
    66.     ;************* SWAP regA and B - DIRECT ADDRESSING ************
    67.     ; xchgs regA and regB
    68. xchgAB_Direct:
    69.     movf  regA,W
    70.     xorwf regB,F    
    71.     xorwf regB,W
    72.     movwf regA  
    73.     xorwf regB,F
    74.     return
    76.     ; *********** SWAP BYTES AT *FSR  *************
    77.     ; xchgs byte at *FSR with byte at *(FSR+1)
    78.     ; Leaves FSR at +1
    79. xchgByte_FSR:           ;--INDEX--
    80.     movf    INDF,W      ; +0
    81.     incf    FSR,F
    82.     xorwf   INDF,F      ; +1
    83.     xorwf   INDF,W      ; +1
    84.     decf    FSR,F
    85.     movwf   INDF        ; +0
    86.     incf    FSR,F
    87.     xorwf   INDF,F      ; +1
    88.     return
    90.     ;************* MACRO TEST: MISC SWAPS ******************
    91.     ; These use the macro to generate xchg code
    92.     ; for hard-coded addresses in the same bank
    93.     ; Works for SFRs too
    94. xchgFSR:
    95.     xchg2bytes  FSR,saveFSR         ; xchg FSR and saveFSR
    96.     return
    98. xchg_regAregC:
    99.     xchg2bytes  regA,regC           ; xchg regs a and c
    100.     return
    102.     END
    Last edited: Jun 22, 2016
    jpanhalt likes this.
  11. MaxHeadRoom


    Jul 18, 2013
    Myself, I wouldn't say I get emotional about it, Using colon after label, just I find it neater and easier to read and is a left over from the old 8080/8086 assembly programming.
    Any code I down load I usually edit it accordingly, same with editing an instruction on the same line as a label.
    Both work, so it's whatever floats your boat.
    JohnInTX likes this.
  12. joeyd999

    AAC Fanatic!

    Jun 6, 2011
    I've been blessed with the ability to read and write assembly like an English Lit graduate reads/writes prose. It probably helps that I've been doing it since about 9 years old -- second nature, and all that.

    That said, I've never asked another programmer to review my work, and I've never had to -- or desired to -- collaborate with another programmer or software team.

    Sometimes, I wonder how my code looks to the the rest of the world...

    ....but then I remember that I don't care.
    ErnieM and JohnInTX like this.
  13. jpanhalt


    Jan 18, 2008
    I agree 100% with always showing the destination when its allowed. The older PicList code used "movfw". That was the only thing I fixed in the post. It was sloppy of me.

    And, thank you for the routines.

    JohnInTX likes this.
  14. Robin66

    Thread Starter Member

    Jan 5, 2016
    Ok, this is what I have. Rather than using xorlf I've decided to put aside a var that would only be used in this function. It seems to work nicely. I'm sure a few lines could be saved here or there, but it's readable. I've made the assumption that i'm sorting 5 adjacent bytes

    Code (Text):
    1. ; ***********  
    3. ; ***********  
    4. sort
    5.     ;  loop thru pointers comparing adjacent values and swapping if A>B
    6.     movlw v_lb
    7.     movwf FSR
    8.     clrf sortv1
    9. sort_inner
    10.     movf INDF,w
    11.     incf FSR
    12.     subwf INDF,w ; B-A
    13.     btfsc STATUS,C
    14.     goto $+2
    15.     call swapBytes; borrow has occured, ie. A>B
    16.     movf FSR,w
    17.     sublw v_lb+4 ; decide whether to stop
    18.     btfss STATUS,Z
    19.     goto sort_inner
    20.     btfsc sortv1,0 ; we've completed a pass, are we clear?
    21.     goto sort
    22.     return
    24. swapBytes
    25.     ; swap byte at FSR with FSR-1
    26.     bsf sortv1,0  ; flag that swapping has occured
    27.     movf INDF,w   ; set aside upper byte
    28.     movwf swapv1
    29.     decf FSR     ; move lower byte into w, then into upper byte
    30.     movf INDF,w
    31.     incf FSR
    32.     movwf INDF
    33.     decf FSR     ; put vara into lower byte
    34.     movf swapv1,w
    35.     movwf INDF
    36.     incf FSR
    37.     return