assembly6502c64

Kick Assembler question, manipulating variable in loop


I'm trying to study mnemonics logic, and tried this simple test with C64 asm, made with VSCode and Kick Asm. Basically i'm trying to print from upper left corner sequence chars from 0 to 9 and then backwards. This is working attempt:

.label SCREEN_RAM = $0400   // Start of C64 screen memory
// --- Variable ---
counter: .byte 0    
//backCounter: .byte 39       
animationDirection: .byte 1  // Start animation going forward (0=backward, 1=forward) 
.var backCounter = $39  

BasicUpstart2(main) 

main:
    lda #0              // Initialize a counter to 0
    sta counter 
    jmp checkloop

checkloop:
    ldy animationDirection  // Load the current direction flag
    cpy #1
    beq print_loop_forward 
    //.break
    //lda #10     // Load the desired value into the accumulator
    //sta counter      // Store the value from the accumulator into 'counter'
    ldy #backCounter //#$39 
    jsr print_loop_backwards
    jmp exit2

print_loop_backwards: 
    //.break
    //ldy #backCounter //#$39 no!
    ldx counter            // Load the counter into Y register (for offsetting)
    tya
    sta SCREEN_RAM, x     // Store the character on the screen with offset 
    dey 
    inc counter
    ldx counter
    cpx #20
    bne print_loop_backwards         // Compare with 10 (decimal) 
    rts

print_loop_backwards_not_working: 
    .break
    lda #backCounter //#$39 
    ldx counter            // Load the counter into Y register (for offsetting)
    sta SCREEN_RAM, x     // Store the character on the screen with offset

    dec backCounter
    //.break
    inc counter
    ldx counter
    cpx #20
    bne print_loop_backwards         // Compare with 10 (decimal) 
    rts
    
 
print_loop_forward:  
    //.break
    lda counter            // Load the counter value into the accumulator
    //cmp #0
    //beq exit
    clc
    adc #$30              // Add $30 to convert to ASCII 
    ldx counter            // Load the counter into Y register (for offsetting)
    sta SCREEN_RAM, x     // Store the character on the screen with offset
    
    inc counter
    ldx counter
    cpx #10
    bne print_loop_forward         // Compare with 10 (decimal)
    //.break
    lda #0
    sta animationDirection
    jmp checkloop 

exit:               // Label for the exit point
    rts

exit2:               // Label for the exit point
    lda #$21            // Load the counter into Y register (for offsetting)
    ldx #$99
    sta $0500, x
    rts

As you can see i've tried another way to do that in this loop:

print_loop_backwards_not_working

Firstly I've tried that attempt, modifying the variable "backCounter" during loop without success, but didn't understand why the variable doesn't reflect changements like the "counter", which i'm able to modify with INC. Tried with DEC as you see, but also with SEC\SBC #$1.

Obviously while tried this method without success, line:

ldy #backCounter //#$39 

was commented out.

Hope someone help me clarify this!


Solution

  • 6502/6510 opcodes have Immediate, Absolute and Indirect modes. If you want to use a memory address as a counter, you should use Absolute mode which can be represented as below.

    LDA $FFFF
    

    or it's Zeropage version;

    LDA $FF
    

    You are trying to use the Immediate mode which looks like;

    LDA #$FF
    

    So, you have three sensible options here.

    1 - Use Absolute mode like below;

    LDY backCounter
    ...
    DEC backCounter
    

    Absolute mode reads backCounter from memory address to Y register and when we need to decrease the counter, we directly decrease in memory. When we read the memory address next time, it's already decremented.

    2 - Use modifying code. I don't recommend it, i'm sharing this as an alternative option.

    label1:
        LDY #backCounter
        ...
        DEC label1+1
    

    Here we use Immediate mode and decrease the opcode parameter in memory. Don't forget if you don't reset the parameter, your code doesn't work with backCounter's initial value next time.

    3 - Use registers directly. This is the fastest method which you already use in your working example.

    LDY #backCounter
    ...
    DEY
    

    As long as you have free registers, you should go with registers. When you are out of registers, using Absolute mode like "LDY backCounter" is your second best option. Self modifying code can be very useful time to time. But it's not the safest option and can cause some side effects. In modern CPUs, self modifying code can be restricted or at least cause caching issues and make your code run much slower. But in good old 8-bit CPUs era, self modifying code was safe to use as long as you know what you are doing.