Bubble Sort, Insertion Sort, Quick Sort assembly code

I’ve been on a bit of a sorting kick lately. Maybe sorting garbage on the screen is a proxy for finding order in my own life. Or maybe it’s just fun. I’m going with the latter.

The following code runs bubble sort, insertion sort, and quick sort, and displays the timing using TIMVAL from the BASIC ROM. In between each phase, hit a key on the keyboard to continue.

The code is split into two files, with the first one INCLUDE’ing the second one via the LWASM pseudo-op.

An interesting extension to try… sort garbage in other display modes. High-res display modes take up more space, and thus might help increase the quick sort time enough that you can actually see it do something. 😃

QS.ASM
; Quick Sort and friends
 
        ORG     $4000

SortScrnBufStart        EQU     $E00    ; Garbage / sort screen start address
TimingScrnBufStart      EQU     $400    ; Timing info screen start address
ScrnBufSize             EQU     $200    ; Num bytes in either screen

POLCAT  EQU     $A000
RND     EQU     $BF1F   ; ROM routine to generate random number
INTCNV2 EQU     $B3FE   ; ROM routine to convert FPA0 to int, without the overflow check
VALTYP  EQU     $6      ; Flag: 0=numeric, $FF=string
TIMVAL  EQU     $112    ; Location of BASIC's TIMER (2 bytes)

; Location of first byte of floating point accumulator
FP0EXP  EQU     $4F

; Values to fill floating point accumulator as parameter to
; pass to RND as the max random integer (65536)
EXPVAL  EQU     $91
MANVAL  EQU     $80
SGNVAL  EQU     $7F

; ---------------------------------------------------------------
; main
; ---------------------------------------------------------------
        LBSR    PrepareStrings          ; Run TweakLowerCaseLetters on label strings
MainLoop:
        LBSR    ScreenBufE00            ; Show sorting screen
        ; Bubble Sort
        LBSR    InitBufferAndSortScreen ; Generate random garbage, store & display
        BSR     WaitForKeystroke
        LDD     #0                      ; Reset timer
        STD     TIMVAL                  
        LBSR    BubbleSort
        LDD     TIMVAL                  ; Add elapsed time to timing info
        LDX     #TimingInfo
        ADDD    ,X
        STD     ,X
        BSR     WaitForKeystroke
        ; Insertion sort        
        LBSR    InitSortScreen          ; Replace random garbage
        BSR     WaitForKeystroke
        LDD     #0                      ; Reset timer
        STD     TIMVAL                  
        LBSR    InsertionSort
        LDD     TIMVAL                  ; Add elapsed time to timing info
        LDX     #TimingInfo+2
        ADDD    ,X
        STD     ,X
        BSR     WaitForKeystroke
        ; Quick sort
        LBSR    InitSortScreen          ; Replace random garbage
        BSR     WaitForKeystroke
        LDD     #0                      ; Reset timer
        STD     TIMVAL                  
        LDD     #SortScrnBufStart       ; Param 1: Start of list
        PSHS    D
        LDD     #SortScrnBufStart+ScrnBufSize-1  ; Param 2: End of list
        PSHS    D
        LBSR    QuickSort
        LEAS    4,S
        LDD     TIMVAL                  ; Add elapsed time to timing info
        LDX     #TimingInfo+4
        ADDD    ,X
        STD     ,X
        ; End of trial
        INC     NumTrials               ; Just finished a new trial
        LBSR    UpdateTimingsScreen     ; Fill timing screen with new times
        BSR     WaitForKeystroke
        LBSR    ScreenBuf400            ; Show timings screen
        BSR     WaitForKeystroke

        BRA     MainLoop                ; Repeat for another trial


; ---------------------------------------------------------------
; WaitForKeystroke sub
; ---------------------------------------------------------------
WaitForKeystroke:
        JSR     [POLCAT]                ; Any keystoke?
        BEQ     WaitForKeystroke        ; If not, loop
        RTS


; ---------------------------------------------------------------
; QuickSort(lo, hi) sub
; Performs a recursive 3-way partitioning quick sort.
; Middle partition contains all elements equal to pivot
; First and third partitions contain elements less than
; and greater than the pivot, respectively.
; Pivot value initially at *lo.  It slides right during the
; partitioning phase.  Then recurse on left side (before
; pivot) and then on right side (after pivot)
; Parameters: lo & hi are addrs of beginning and end of list to sort
; Entry: Stack: lo (2 bytes), hi (2 bytes), PC <-- S
; Register usage:
; X = lt (beginning of middle partition--earlier elements LessThan pivot)
; Y = gt (end of middle partition--later elements GreaterThan pivot)
; U = i (current element addr / "cursor"),
; B = *i (current element value)
; A = scratch
; ---------------------------------------------------------------
QuickSort:
        LDX     4,S     ; X = lt = lo
        CMPX    2,S     ; lo < hi ?
        BHS     QSDone  ; no, done (base case)
        LDY     2,S     ; Y = gt = hi
        LDA     ,X      ; A = pivot = *lo
        STA     Pivot   ; Store pivot value for use in comparisons
        LEAU    ,X      ; i = lo
PartitionLoop:
        STU     i       ; Temporarily store i so we can use it in a comparison
        CMPY    i       ; gt >= i ?
        BLO     PartitionLoopDone ; no, exit loop
        LDB     ,U      ; B = *i (current element to compare with pivot)
        CMPB    Pivot   ; compare current element (B) with pivot
        BLO     LessThanPivot
        BHI     GreaterThanPivot
; case: B == pivot
; Just advance i
        LEAU    1,U     ; i++
        BRA     PartitionLoop
LessThanPivot:
; case: B < pivot
; Exchange *lt & *i, advance both lt & i
        LDA     ,X      ; A = *lt (B is already *i)
        STA     ,U+     ; Exchange part 1: *lt -> *i; i += 1
        STB     ,X+     ; Exchange part 2: *i -> *lt; lt += 1
        BRA     PartitionLoop
GreaterThanPivot:
; case: B > pivot
; Exchange *i & *gt, decrement gt
        LDA     ,Y      ; A = *gt (B is already *i)
        STB     ,Y      ; Exchange part 1: *i -> *gt
        LEAY    -1,Y    ; gt -= 1; see note (*) below
        STA     ,U      ; Exchange part 2: *gt -> *i
        BRA     PartitionLoop
PartitionLoopDone:
; All elements between lt and gt are the same
; Recurse on the remaining two portions (<lt and >gt)
        LEAX    -1,X    ; lt -= 1
        LDD     4,S     ; D = lo
        PSHS    Y       ; Save gt for use after first recursive call
        ; First recursive call: QS(lo, lt-1)
        PSHS    D       ; Push param 1 (new lo = old lo)
        PSHS    X       ; Push param 2 (new hi = old lt - 1)
        BSR     QuickSort  ; Recurse on region [lo;lt-1]
        LEAS    4,S     ; PULS the parameters
        PULS    Y       ; Restore gt; S now restored to same addr as on entry
        LEAY    1,Y     ; gt += 1
        LDD     2,S     ; D = hi
        ; Second recursive call: QS(gt+1, hi)
        PSHS    Y       ; Push param 1 (new lo = old gt + 1)
        PSHS    D       ; Push param 2 (new hi = old hi)
        ; (above pushes can be combined into single PSHS but isn't this confusing enough?)
        BSR     QuickSort       ; Recurse on region [gt+1;hi]
        LEAS    4,S     ; PULS the parameters
QSDone:
        RTS
i       RMB     2       ; Addr of current element to compare with pivot
Pivot   RMB     1       ; Value of pivot.  Its addr starts at lo==lt, and as pivot moves up, lt updates to point to it
; (*) Above we did two separate ops:
;       STB     ,Y
;       LEAY    -1,Y
; This stores B into Y and then decrements Y.
; A shortcut might be:
;       STB     ,Y-
; where the decrement happens AFTER the store.
; But all auto-decrements happen BEFORE the store / load
; and all auto-increments happen AFTER the store / load.
; No post-decrement or pre-increment exists.
; As an alternative, one might try:
;       STB     1,-Y
; which does a pre-decrement but then adjusts for
; it by using 1 as the index when storing B.  However,
; you cannot mix auto-inc/dec with an index.  So it seems
; I'm stuck having to do two separate ops.


; ---------------------------------------------------------------
; BubbleSort sub
; Adapted from TRS-80 Color Computer Assembly Language Programming
; by William Barden, Jr.
; ---------------------------------------------------------------
BubbleSort:
        LDX     #SortScrnBufStart
        LDU     #0              ; Reset change flag to 0
ProcessPair:
        LDA     ,X+             ; Get first entry
        CMPA    ,X              ; Compare with second
        BLS     EndProcessPair  ; No need to swap
        LDB     ,X              ; Get second entry
        STB     -1,X            ; Swap second to first
        STA     ,X              ; Swap first to second
        LDU     #1              ; Set change flag to 1
EndProcessPair:
        CMPX    #SortScrnBufStart+ScrnBufSize-1 ; Test for end of pass
        BNE     ProcessPair     ; Continue pass
        CMPU    #0              ; Pass complete; test change flag
        BNE     BubbleSort      ; There was a change; do another pass
        RTS


; ---------------------------------------------------------------
; InsertionSort sub
; X: outer loop variable, starts at second element, continues
;    forward through the list.  Points to current item to
;    consider shifting left
; U: inner loop variable, starts at X, and shifts left
; ---------------------------------------------------------------
InsertionSort:
        LDX     #SortScrnBufStart+1             ; First item to consider is SECOND item
ISOuterLoop:
        CMPX    #SortScrnBufStart+ScrnBufSize   ; Has X reached the end?
        BHS     ISDone          ; Yes, done with entire sort
        TFR     X,U             ; Init inner loop: U starts at X
ISInnerLoop:
        CMPU    #SortScrnBufStart ; Have we shifted all the way to the beginning?
        BEQ     ISInnerLoopDone ; Yes, inner loop is done
        LDA     ,U              ; A is value we are shifting left
        CMPA    -1,U            ; Compare A with its left neighbor
        BHS     ISInnerLoopDone ; A > left neighbor, no need to shift
        LDB     -1,U            ; B is left-neighbor of A
        STA     -1,U            ; Swap right to left
        STB     ,U              ; Swap left to right
        LEAU    -1,U            ; Move U left to continue the shifting
        BRA     ISInnerLoop
ISInnerLoopDone:
        LEAX    1,X             ; Advance X to next item to consider for shifting
        BRA     ISOuterLoop
ISDone:
        RTS


; ---------------------------------------------------------------
; InitSortScreen sub
; Copies random garbage from GbgBuffer to the text screen
; buffer starting at $E00
; ---------------------------------------------------------------
InitSortScreen:
        LDU     #GbgBuffer                 ; Source
        LDX     #SortScrnBufStart          ; Destination
InitSortScreenLoop:
        CMPX    #SortScrnBufStart+ScrnBufSize
        BHS     InitSortScreenLoopDone
        LDD     ,U++
        STD     ,X++
        BRA     InitSortScreenLoop          ; Loop up
InitSortScreenLoopDone:
        RTS


; ---------------------------------------------------------------
; TwoRandomBytes sub
; ---------------------------------------------------------------
TwoRandomBytes:
; Initialize floating point accumulator
        LDX     #FP0EXP
        LDA     #EXPVAL
        STA     ,X+     ; Set FP0EXP
        LDA     #MANVAL
        STA     ,X+     ; Set byte 1 of mantissa
        LDD     #0
        STD     ,X++    ; Clear bytes 2-3 of mantissa
        STA     ,X+     ; Clear byte 4 of mantissa
        LDA     #SGNVAL
        STA     ,X+     ; Set FP0SGN
        ; Clear rest of FPA0 (7 bytes: COEFCT, STRDES, FPCARY)
        LDD     #0
        STD     ,X++ 
        STD     ,X++ 
        STD     ,X++ 
        STA     ,X

        JSR     RND             ; Call RND(FPA0), answer in FPA0
        LDA     FP0EXP          ; Check to see if RND returned 65536
        CMPA    #$91
        BNE     FPA0ToInt       ; No.  Do INTCNV2 with no fear of overflow
        LDD     #0              ; Yes.  Wraparound to 0 and we're done
        RTS

FPA0ToInt:
        CLR     VALTYP          ; Set value type to numeric
        JSR     INTCNV2         ; FPA0 -> D
        RTS

; ---------------------------------------------------------------
; InitBufferAndSortScreen sub
; Fills screen buffer starting at $E00 with random garbage,
; and keeps a copy of the same garbage in GbgBuffer for
; use in successive sorts
; ---------------------------------------------------------------
InitBufferAndSortScreen:
        BSR     ActivateDispMode        ; Activate alphanumeric mode
        LDX     #SortScrnBufStart
        LDU     #GbgBuffer
GarbageScreenLoop:
        CMPX    #SortScrnBufStart+ScrnBufSize
        BHS     GarbageScreenLoopDone
        PSHS    X,U                     ; Fill another 2 bytes of garbage
        BSR     TwoRandomBytes
        PULS    X,U
        STD     ,X++
        STD     ,U++
        BRA     GarbageScreenLoop       ; Loop up
GarbageScreenLoopDone:
        RTS

; ---------------------------------------------------------------
; ActivateDispMode sub
; Configures VDG for alphanumeric / semigraphic mode
; Start address configured elsewhere
; ---------------------------------------------------------------
ActivateDispMode:
; Set SAM control bits (to determine screen buffer size)
; $FFC0,$FFC2,$FFC4
        LDY     #$FFC0
        STA     ,Y++
        STA     ,Y++
        STA     ,Y
; Set VDG operating mode ($0 for AI)
        CLRA
        STA     $FF22
        RTS

; ---------------------------------------------------------------
; ScreenBufE00 sub
; Set screen buffer start address to $E00
; This displays the sorting screen
; ---------------------------------------------------------------
ScreenBufE00:
        STA     $FFCB
        STA     $FFC9
        STA     $FFC7
        RTS

; ---------------------------------------------------------------
; ScreenBuf400 sub
; Set screen buffer start address to $400
; This displays the timings screen
; ---------------------------------------------------------------
ScreenBuf400:
        STA     $FFCA
        STA     $FFC9
        STA     $FFC6
        RTS


; ---------------------------------------------------------------
; PrepareStrings sub
; Call TweakLowerCaseLetters for all static strings
; ---------------------------------------------------------------
PrepareStrings:
        LDX     #NumTrialsLbl
        BSR     TweakLowerCaseLetters
        LDX     #BubLbl
        BSR     TweakLowerCaseLetters
        LDX     #InsLbl
        BSR     TweakLowerCaseLetters
        LDX     #QuickLbl
        BSR     TweakLowerCaseLetters
        RTS

; ---------------------------------------------------------------
; UpdateTimingsScreen sub
; Redraw timings screen with latest timing info
; ---------------------------------------------------------------
UpdateTimingsScreen:
        LDX     #TimingScrnBufStart     ; Clear
        BSR     ClearScreen
        LDX     #NumTrialsLbl           ; Num trials label
        LDU     #TimingScrnBufStart
        BSR     PrintString
        LDB     NumTrials               ; Num trials value (only 1 byte!)
        CLRA
        LDU     #TimingScrnBufStart+(1*32)
        BSR     PrintInt
        LDX     #BubLbl                 ; Bubble sort label
        LDU     #TimingScrnBufStart+(4*32)
        BSR     PrintString
        LDD     TimingInfo              ; Print Bubble Sort timing
        LDU     #TimingScrnBufStart+(5*32)
        BSR     PrintInt
        LDX     #InsLbl                 ; Insertion sort label
        LDU     #TimingScrnBufStart+(8*32)
        BSR     PrintString
        LDD     TimingInfo+2            ; Print Insertion Sort timing
        LDU     #TimingScrnBufStart+(9*32)
        BSR     PrintInt
        LDX     #QuickLbl               ; Quick Sort label
        LDU     #TimingScrnBufStart+(12*32)
        BSR     PrintString
        LDD     TimingInfo+4            ; Print Quick  Sort timing
        LDU     #TimingScrnBufStart+(13*32)
        BSR     PrintInt
        RTS


; ---------------------------------------------------------------
; General routines for printing strings and numbers
        INCLUDE PrintUtilities.asm
; ---------------------------------------------------------------

; ---------------------------------------------------------------
; Symbols / data
; ---------------------------------------------------------------
GbgBuffer               RMB     ScrnBufSize    ; Random garbage storage
NumTrials               FCB     0
NumTrialsLbl            FCN     "number of trials:"
BubLbl                  FCN     "bubble sort:"
InsLbl                  FCN     "insertion sort:"
QuickLbl                FCN     "quick sort:"


; Array of timing sums
TimingInfo              FDB     0,0,0   ; Two bytes for each of 3 sorting algorithms


        END     $4000
PrintUtilities.asm
INTCNV  EQU     $B3ED   ; ROM routine to convert int in FPA0 to D
FPSCNV  EQU     $BDD9   ; ROM routine to convert FPA0 to ASCII string
GIVABF  EQU     $B4F4   ; ROM routine to convert D to FPA0
STRBUF  EQU     $03D7   ; Start of BASIC string buffer

; ---------------------------------------------------------------
; TweakLowerCaseLetters sub
; Adjusts all bytes representing lower-case letters in the
; specified string in the same manner as BASIC ROM's PUTCHR.
; Running this once on a string allows later code to directly
; store the resulting string's bytes in the screen buffer
; and have the lower-case letters show up properly.  All
; non-lower-case-letter characters are left alone, so
; ensure they're adjusted if necessary elsewhere
; Entry: X -> null-terminated string to tweak
; Uses: A
; ---------------------------------------------------------------
TweakLowerCaseLetters:
        LDA     ,X+
; At the null-teminator?
        TSTA
        BEQ     TweakLowerCaseLettersDone
; In lower-case letter range?
        CMPA    #'a'
        BLO     TweakLowerCaseLetters
        CMPA    #'z'
        BHI     TweakLowerCaseLetters
; We have a lower-case letter, so perform both tweaks
        ANDA    #$DF
        EORA    #$40
        STA     -1,X    ; Store tweaked version
        BRA     TweakLowerCaseLetters
TweakLowerCaseLettersDone:
        RTS


; ---------------------------------------------------------------
; ClearScreen sub
; Clears screen to the super-cool green-on-black
; Assumes screen buffer size is $200
; Entry: X->beginning of screen buffer
; Also uses D
; ---------------------------------------------------------------
ClearScreen:
        STX     SCRNEND         ; Calculate and store SCRNEND
        LDD     #$200
        ADDD    SCRNEND
        STD     SCRNEND
        LDD     #"  "           ; Double-space
ClearScreenLoop:
        CMPX    SCRNEND
        BHS     ClearScreenDone
        STD     ,X++
        BRA     ClearScreenLoop
ClearScreenDone:
        RTS
SCRNEND RMB     2


; ---------------------------------------------------------------
; PrintInt sub
; D = int to print
; U = location in screen buffer to print int
; ---------------------------------------------------------------
PrintInt:
        PSHS    U
        JSR     GIVABF          ; D -> FPA0
        JSR     FPSCNV          ; Convert FPA0 to ASCII string in STRBUF
        LDX     #STRBUF+3
        PULS    U
        BSR     PrintString
        RTS



; ---------------------------------------------------------------
; PrintString
; Prints null-terminated string
; Entry: X -> string
; Entry: U -> Screen buffer location to print to
; Also uses: A
; ---------------------------------------------------------------
PrintString:
        LDA     ,X+
        TSTA
        BEQ     PrintStringEnd      ; Break out of loop on terminating null
        STA     ,U+                 ; Print character
        BRA     PrintString
PrintStringEnd:
        RTS

Leave a comment

Design a site like this with WordPress.com
Get started