asm: Swapping adjacent file registers, PIC16F

Thread Starter

Robin66

Joined Jan 5, 2016
281
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:
swap
    movf INDF,w   ; set aside upper byte
    movwf vara
    decf FSR     ; move lower byte into w, then into upper byte
    movf INDF,w
    incf FSR
    movwf INDF
    decf FSR     ; put vara into lower byte
    movf vara,w
    movwf INDF
    return
Mod edit: code tags
 
Last edited by a moderator:

JohnInTX

Joined Jun 26, 2012
4,787
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:
  ; *********** SWAP BYTES AT *FSR  *************
  ; Swaps byte at *FSR with byte at *(FSR+1)
  ; Leaves FSR at +1
SwapByte_FSR:
                        ;-- INDEX --
    movf    INDF,W      ; +0
    incf    FSR,F
    xorwf   INDF,F      ; +1

    movf    INDF,W      ; +1
    decf    FSR,F
    xorwf   INDF,F      ; +0

    movf    INDF,W      ; +0
    incf    FSR,F
    xorwf   INDF,F      ; +1

    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:

jpanhalt

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

Here is the swap routine:
Code:
     movf  rega,w      
     xorwf regb        
     xorwf regb,w      
     movwf rega        
     xorwf regb
Curious how may instructions C takes.

John
 

JohnInTX

Joined Jun 26, 2012
4,787
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:

Thread Starter

Robin66

Joined Jan 5, 2016
281
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.
 

jpanhalt

Joined Jan 18, 2008
11,087
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.
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) :
upload_2016-6-22_4-48-29.png

Since MPLab 8.92 puts the label in magenta by default,

upload_2016-6-22_4-50-38.png

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.

John
 

Thread Starter

Robin66

Joined Jan 5, 2016
281
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.
 

jpanhalt

Joined Jan 18, 2008
11,087
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.

John
 

JohnInTX

Joined Jun 26, 2012
4,787
@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:
;*********** BYTE SWAPPING  ******************

    #include P16F77.inc

    ;*********** EXCHANGE BYTES USING DIRECT ADDRESSING  **********
    ; Generates code to exchange two bytes in the same bank using
    ; direct addressing.
xchg2bytes macro b1,b2
    expand
        movf  b1,W
        xorwf b2,F    
        xorwf b2,W
        movwf b1  
        xorwf b2,F
    noexpand
    endm

    ;**************** DATA  ********************
    cblock 20h      ; some data to play with
    regA:   1
    regB:   1
    regC:   1
    regD:   1

    saveFSR:1       ; used to exchange pointers (FSR)
    endc

    ;**************** TEST PROGRAM  ************************
    ; Step through this and observe the exchanging in the
    ; File Registers window.

    ORG 0
    clrf    STATUS

    ;------------- TEST CODE TO RUN IN MPSIM ------------------
    ; Calls various xchg routines in a loop.  Observe the
    ; xchgping of 20h and 21h in the File Registers window
xchgLoop:
    movlw   'A'                 ; preset some values
    movwf   regA
    movlw   'B'
    movwf   regB
    movlw   'C'
    movwf   regC
    movlw   'D'
    movwf   regD

    call    xchgAB_Direct     ; xchg using direct addressing

    movlw   regA                ; xchg back using midrange FSR
    movwf   FSR
    call    xchgByte_FSR

    call    xchg_regAregC      ; xchg A and C

    movlw   regC                ; test pointer exchange
    movwf   saveFSR
    movlw   regA
    movwf   FSR
    call    xchgByte_FSR        ; exchange A and B using FSR
    call    xchgFSR             ; use alternate FSR to..
    call    xchgByte_FSR        ; ..exchange C and D

    goto    xchgLoop

    ;************* SWAP regA and B - DIRECT ADDRESSING ************
    ; xchgs regA and regB
xchgAB_Direct:
    movf  regA,W
    xorwf regB,F    
    xorwf regB,W
    movwf regA  
    xorwf regB,F
    return

    ; *********** SWAP BYTES AT *FSR  *************
    ; xchgs byte at *FSR with byte at *(FSR+1)
    ; Leaves FSR at +1
xchgByte_FSR:           ;--INDEX--
    movf    INDF,W      ; +0
    incf    FSR,F
    xorwf   INDF,F      ; +1
    xorwf   INDF,W      ; +1
    decf    FSR,F
    movwf   INDF        ; +0
    incf    FSR,F
    xorwf   INDF,F      ; +1
    return

    ;************* MACRO TEST: MISC SWAPS ******************
    ; These use the macro to generate xchg code
    ; for hard-coded addresses in the same bank
    ; Works for SFRs too
xchgFSR:
    xchg2bytes  FSR,saveFSR         ; xchg FSR and saveFSR
    return

xchg_regAregC:
    xchg2bytes  regA,regC           ; xchg regs a and c
    return

    END
 
Last edited:

MaxHeadRoom

Joined Jul 18, 2013
30,616
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.
Max.
 

joeyd999

Joined Jun 6, 2011
6,254
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.
 

jpanhalt

Joined Jan 18, 2008
11,087
@JohnInTX
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.

John
 

Thread Starter

Robin66

Joined Jan 5, 2016
281
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:
; ***********  
;  BUBBLE SORT ALGO
; ***********  
sort
    ;  loop thru pointers comparing adjacent values and swapping if A>B
    movlw v_lb
    movwf FSR
    clrf sortv1
sort_inner
    movf INDF,w
    incf FSR
    subwf INDF,w ; B-A
    btfsc STATUS,C
    goto $+2
    call swapBytes; borrow has occured, ie. A>B
    movf FSR,w
    sublw v_lb+4 ; decide whether to stop
    btfss STATUS,Z
    goto sort_inner
    btfsc sortv1,0 ; we've completed a pass, are we clear?
    goto sort
    return
  
swapBytes
    ; swap byte at FSR with FSR-1
    bsf sortv1,0  ; flag that swapping has occured
    movf INDF,w   ; set aside upper byte
    movwf swapv1
    decf FSR     ; move lower byte into w, then into upper byte
    movf INDF,w
    incf FSR
    movwf INDF
    decf FSR     ; put vara into lower byte
    movf swapv1,w
    movwf INDF
    incf FSR
    return
 
Top