DEV Community

Avelyn Hyunjeong Choi
Avelyn Hyunjeong Choi

Posted on • Updated on

Adding Calculator in 6502 Emulator

Features

  • Simple adding calculator program for a computer system using 6502 assembly language.
  • It includes routines for initializing the screen, obtaining user input, displaying the result on the character screen, and presenting a green colour on the graphics screen.

Source Code

; Adding Calculator

; ROM routine entry points
define      SCINIT      $ff81 ; initialize/clear screen
define      CHRIN       $ffcf ; input character from keyboard
define      CHROUT      $ffd2 ; output character to screen

; zeropage variables
define      PRINT_PTR   $10
define      PRINT_PTR_H $11
define      firstInput  $14
define      secInput    $15

; absolute variables
define      GETNUM_1    $0080
define      GETNUM_2    $0081
define      ENTER       $0d ; for the ENTER key
define      BACKSPACE   $08 ; for the BACKSPACE key

; --------------------------------------------------------
; Main loop
        jsr SCINIT  ; initialize/clean screen

main:       
; get first input'
        ldy #$00        
        jsr firstInputQs
; '
        jsr GETNUM  ; get firstInput
        sta firstInput  ; store firstInput      

; get second input'
        ldy #$00
        jsr secondInputQs
; '
        jsr GETNUM  ; get secondInput

        sed     ; sets decimal model flag in processor status register  
        clc     ; clear carry flag in status register
        adc firstInput  ; add with carry
        cld     ; clear decimal mode flag

        sta firstInput  ; store result
        bcc result  ; if carry flag=0 -> branch to result
        inc secInput    ; increment

; get sum result
result:     
        pha     ; push current value onto the stack'
        ldy #$00
        jsr showResultMsg       
; '
        lda secInput    
        beq low_digits  ; if result=0 -> branch to low-digits
        lda #$31    
        jsr CHROUT  
        jmp draw_100s

low_digits: 
        lda firstInput  
        and #$f0    ; keep high nibble, masking out low nibble
        beq ones_digit  ; if high nibble=0 -> branch to ones_digit

draw_100s:  
        lda firstInput  
        lsr     ; logical shift right
        lsr
        lsr
        lsr
        ora #$30    ; add ASCII value for 0 for conversion to char
        jsr CHROUT


ones_digit: 
        lda firstInput
        and #$0f
        ora #$30
        jsr CHROUT
; initialize green pointer'
        ldx #$00
         stx $10  
        stx $11
; ' 
        jsr display_green
        jsr main

; --------------------------------------------------------
; Print a message

PRINT:      
        pla         ; pull the value from stack -> accumulator 
        clc         ; clear carry flag 
        adc #$01        ; add with carry
        sta PRINT_PTR   
        pla         ; retrieves a byte from stack -> accum
        sta PRINT_PTR_H

        tya         ; transfer y to accumulator
        pha         ; store value in accumulator -> stack

        ldy #$00        ; load y register with 0

print_next: 
        lda (PRINT_PTR),y   ; load PRINT_PTR, Y into accumulator
        beq print_done      

        jsr CHROUT      
        iny         
        jmp print_next

print_done: 
        tya         
        clc         
        adc PRINT_PTR       
        sta PRINT_PTR       ; store the result in PRINT_PTR

        lda PRINT_PTR_H     ; load value at PRINT_PTR_H -> accumulator
        adc #$00        ; add zero
        sta PRINT_PTR_H     ; store the result in PRINT_PTR_H

        pla         
        tay         ; transfer accumulator to Y

        lda PRINT_PTR_H     
        pha         ; push accumulator to stack
        lda PRINT_PTR       
        pha         

        rts         ; return

; ---------------------------------------------------
; GETNUM - get a 2-digit decimal number
;
; Returns A containing 2-digit BCD value

GETNUM:     
        txa         ; transfer x register -> accumulator
        pha         ; push accumulator value -> stack
        tya         ; transfer y register -> accumulator
        pha         

        ldx #$00        ; count of digits received
        stx GETNUM_1        ; store value of x into $0080
        stx GETNUM_2        ; store value of x into $0081


getnum_cursor:  
        lda #$a0        ; black square
        jsr CHROUT
        lda #$83        ; left cursor
        jsr CHROUT

key_check:  
                jsr CHRIN
        cmp #$00
        beq key_check       ; if 0 -> loops back

        cmp #BACKSPACE      ; comps value w/BACKSPACE
        beq getnum_bs

        cmp #ENTER      ; comps value w/ENTER
        beq getnum_enter

        cmp #$30        ; "0"
        bmi key_check       ; if result of comps=negative 

        cmp #$3a        ; "9" + 1
        bmi getnum_digit

        jmp key_check

getnum_enter:   
        cpx #$00        ; compare x with 0
        beq key_check       ; if 0 -> branch

        lda #$20        
        jsr CHROUT
        lda #$0d
        jsr CHROUT

        lda GETNUM_1

        cpx #$01        ; comps x register with 1
        beq getnum_done      

        asl         ; multiples value * 2
        asl
        asl
        asl
        ora GETNUM_2        ; logical OR ops

getnum_done:    
        sta GETNUM_1        
        pla         ; pull top value from stack -> accumulator
        tay         ; transfer accumulator -> y
        pla
        tax         ; transfer accum -> x
        lda GETNUM_1

        rts         ; return

getnum_digit:   
        cpx #$02        ; comps x value with 2
        bpl key_check       ; if positive -> branch
        pha         ; push accu -> stack    
        jsr CHROUT
        pla         
        and #$0f        ; bitwise AND ops
        sta GETNUM_1,x  
        inx
        jmp getnum_cursor

getnum_bs:  
        cpx #$00
        beq key_check
        lda #$20
        jsr CHROUT
        lda #$83
        jsr CHROUT
        jsr CHROUT
        lda #$20
        jsr CHROUT
        lda #$83
        jsr CHROUT
        dex         
        lda #$00
        sta GETNUM_1,x
        jmp getnum_cursor

; ---------------------------------------------------
; print questions'

fistInput:
dcb $0d,$0d,"E","n","t","e","r",32,"a",32,"F","I","R","S","T",32,"N","U","M","B","E","R"
dcb "(","0","-","9","9",")",":"
dcb 32,32,32,32,32,32,32,00

secondInput:
dcb "E","N","T","E","R",32,"S","E","C","O","N","D",32,"N","U","M","B","E","R"
dcb "(","0","-","9","9",")",":"
dcb 32,32,32,32,32,32,32,32,00

sumResult:
dcb "S","u","m",32,"R","e","s","u","l","t",":",32,32
dcb 32,32,32,32,32,32,32
dcb 32,32,32,32,32,32,32
dcb 32,32,32,32,32,32,32
dcb 00

firstInputQs:
    lda fistInput,y
        beq backToMain
        jsr CHROUT
        iny
        bne firstInputQs

secondInputQs:
    lda secondInput,y
        beq backToMain
        jsr CHROUT
        iny
        bne secondInputQs

showResultMsg:
    lda sumResult,y
        beq backToMain
        jsr CHROUT
        iny
        bne showResultMsg

backToMain:
    rts

; ---------------------------------------------------
; display green color when result is available

display_green: 
    lda #$0D    ; Load a with colour code
    ldy #$00    ; load the Y register with 0

color_loop:
    sta ($10), y    ; add y to pointer at $10 and store the accumulator there
    iny             ; increment the y register y++
    bne color_loop      ; continue until page 2 is done

    inc $11     ; increment high byte (page) of pointer
    cpx $11     ; compare value in X register with page number 6
    bne color_loop  ; continue as long as we have not hit the page 6.
    rts         ;'
Enter fullscreen mode Exit fullscreen mode

Demo
Result on the screen and text

Main Sections

1.Definitions:

  • Various ROM routine entry points and memory addresses are defined.
  • Zeropage and absolute variables are allocated.

2.Initialization:

  • The program starts by initializing/clearing the screen and getting user input.
  • The PRINT subroutine is called to display the initial message.

3.Message Definitions:

  • Constant byte strings are defined and stored in memory.

4.Input and Addition:

  • The program prompts the user to enter two numbers.
  • The GETNUM subroutine is used to receive and process the input.
  • The numbers are added together using ADC arithmetic.

5.Result Display:

  • The result is displayed on the screen, handling cases where the result might be more than one digit.

6.Printing Subroutine:

  • The PRINT subroutine prints a null-terminated message immediately after it's called.

7.GETNUM Subroutine:

  • This subroutine reads a 2-digit decimal number from the user.

8.Keyboard Input Handling:

  • The program handles various keyboard inputs including backspace, enter, and numeric characters.

9.Printing Cursor and Feedback:

  • The program displays a cursor and updates the screen based on user input.

10.Backspace Handling:

  • Handles the backspace key to erase the previous input.

Reflection
It was a lot harder than I thought to write a new program from scratch using a 6502 emulator. Since I am a super beginner, I had to look up every assembly language instruction used in 6502 programming. So, I decided to use existing code and build upon it, which was not easy either. First of all, I needed to understand what was going on in the existing code, which took a lot of time. Secondly, it took even more time to implement the new code on top of that. I think I need to become more familiar with the 6502 emulator through lots of practice and revision, so that I can do better next time.

Ideas for further enhancement of the program

  • Subtraction Operation: Add functionality to perform subtraction in addition to addition.
  • Multiplication and Division: Extend the calculator to handle multiplication and division operations.
  • Error Messages: Provide clear error messages to the user in case of invalid input or operations.

Limitations or remaining minor bugs
Using various colour displays that change based on user input can enhance the program's user-friendliness and overall quality.


Reference: https://github.com/ctyler/6502js-code/blob/master/adding-calculator.6502

Top comments (0)