Search code examples
assemblyfibonacci6502

Why is my Fibonacci 6502 malfunctioning for 144 and 233?


The following program:

PORTB = $6000
PORTA = $6001
DDRB = $6002
DDRA = $6003

E  = %10000000
RW = %01000000
RS = %00100000

VARX = $4000
VARY = $4001
VARZ = $4002

NUM = $4005
DIV = $4006
RES = $4007
MOD = $4008

BACK_TO_FRONT = $4020

  .org $8000

reset:
  lda #%11111111 ; Set all pins on port A to output
  sta DDRA

  lda #%11100000 ; Set top 3 pins on port B to output
  sta DDRB

  lda #%00011100 ; Set 8-bit mode; 2-line display; 5x8 font
  sta PORTA
  lda #0         ; Clear RS/RW/E bits
  sta PORTB
  lda #E         ; Set E bit to send instruction
  sta PORTB
  lda #0         ; Clear RS/RW/E bits
  sta PORTB

  lda #%01110000 ; Display on; cursor on; blink off
  sta PORTA
  lda #0         ; Clear RS/RW/E bits
  sta PORTB
  lda #E         ; Set E bit to send instruction
  sta PORTB
  lda #0         ; Clear RS/RW/E bits
  sta PORTB

  lda #%01100000 ; Increment and shift cursor; don't shift display
  sta PORTA
  lda #0         ; Clear RS/RW/E bits
  sta PORTB
  lda #E         ; Set E bit to send instruction
  sta PORTB
  lda #0         ; Clear RS/RW/E bits
  sta PORTB

  lda #$0
  sta VARX

  lda #$1
  sta VARY

  lda #$a
  sta DIV

loop:
  lda VARX

  clv
  clc
  adc VARY
  clc
  clv

  sta VARZ

  lda VARY
  sta VARX

  lda VARZ
  sta VARY

  lda VARY
  sta NUM


  ldx #$0

  store_digits:  
    jmp divide

    return_from_div:

    lda MOD
        sta BACK_TO_FRONT, x

        inx

        lda RES
        sta NUM

        lda #$0
        cmp NUM
        bcc store_digits

   dex

   print_digits:
    ldy BACK_TO_FRONT, x
    lda digit_table, y
    sta PORTA
    lda #RS         ; Set RS; Clear RW/E bits
    sta PORTB
    lda #(RS | E)   ; Set E bit to send instruction
    sta PORTB
    lda #RS         ; Clear E bits
    sta PORTB

    dex

    sec
    cpx #$1
    bcc print_digits

    ldx #$0
    delay:
            lda #%00000100
            sta PORTA
            lda #RS         ; Set RS; Clear RW/E bits
            sta PORTB
            lda #(RS | E)   ; Set E bit to send instruction
            sta PORTB
            lda #RS         ; Clear E bits
            sta PORTB
        cpx #$f
        beq next

        inx

        jmp delay 

    next:

        lda #%10000000
        sta PORTA

    lda #0         ; Clear RS/RW/E bits
    sta PORTB
    lda #E         ; Set E bit to send instruction
    sta PORTB
    lda #0         ; Clear RS/RW/E bits
    sta PORTB

  jmp loop

divide:
  lda #$0
  sta RES

  lda #$0
  sta MOD

  div_loop:
    lda NUM
    cmp DIV
    bcc answer

    lda NUM

    sec
    sbc DIV
    clc
    clv

    sta NUM

    inc RES
    jmp div_loop

  answer:
    lda NUM
    sta MOD
    jmp return_from_div

digit_table:
  .word $8c0c
  .word $cc4c
  .word $ac2c
  .word $ec6c
  .word $9c1c


  .org $fffc
  .word reset
  .word $0000

Runs on the 65C02 (WDC version). Connected to a ROM (starting with address 0x8000) and to a RAM chip (address 0x4000) and to the VIA (65C22), which is connected to an HD44780 (1602 standard LCD).

And the program works, but partially. It outputs 1 then 2 then 3 ... then 89 and instead of 144 prints 1, instead of 233 prints 2. Then 1 again then 98 ??? And then 61.


Solution

  • The way you decide to loop back to print_digits is incorrect:

    ; I've omitted the SEC since it's pointless
    cpx #$1
    bcc print_digits
    

    This will jump to print_digits only if X is unsigned lower than 1. The only possible value for which that could be true is 0, so the above can also be described as jumping to print_digits if X equals 0.

    If we look at the entire print_digits loop, it can therefore be described with the following pseudo-code:

    x = numDigits - 1;
    do
    {
        print(digits[x]);
        x--;
    } while (x == 0);
    

    The only scenarios in which this would give you the correct output is when numDigits is either 1 or 2. For anything larger than that you'll print one digit and then exit the loop.


    What you really want is something like this:

    x = numDigits - 1;
    do
    {
        print(digits[x]);
        x--;
    } while (x >= 0);
    

    That is, replace:

    dex
    sec
    cpx #$1
    bcc print_digits
    

    with:

    dex                ; Updates the N flag after decrementing X
    bpl print_digits   ; Jump if N is clear, i.e. if X >= 0