6502commodore

6502 Loops and Macros with Kick Assembler


I'm starting to get into 6502 assembly and working through the 64 Bites seasons. I'm on Season 2 episode 17 loops but kinda stuck on the second exercise question. The question is...

Modify program below using add_words_imm macro so that:

  1. The first row is filled with the letter X.
  2. The first column is filled with the letter X.
:BasicUpstart2(main)

main:
  ldx #0
  lda #24
loop:
  sta $0400
  
  // TODO: modify program here
  
  inx
  cpx #40
  bne loop
  
  rts

.macro add_words_imm(a, b, result) {
    clc
  .for(var byte = 0; byte < 2; byte++) {
    lda a + byte
    adc #extract_byte(b, byte)
    sta result + byte
  }
}

.function extract_byte(value, byte_id) {
  .var bits = byte_id * 8
  .eval value = value >> bits
  .return value & 255
}

For the first part of the question, my first thought would be to just do the following.

:BasicUpstart2(main)

main:
  ldx #0
  lda #24
loop:
  sta $0400,x
  inx
  cpx #40
  bne loop
  
  rts

Maybe I'm thinking too literally about the code snippet in the question but my assumption is that the existing code stays and I just add my code where it says "// TODO: modify program here".

That being said I'm struggling to see how to use the macro to fill the top row with X's.

Also, I'm using Kick Assembler and its scripting language to compile and build the .prg/.sym files.


Solution

  • To fill the first row with X's, you already have a loop; as you noted in your question you just need to change the memory address into which you're writing:

    :BasicUpstart2(main)
    
    main:
      ldx #0
      lda #24
    loop:
      sta $0400,X
      inx
      cpx #40
      bne loop
    
      rts
    

    The add_words_imm macro is useful (-ish) for filling in column one because we need to increment a value by 40 (the number of screen columns) a number of times, and the value would overflow a simple 8 bit register.

    There's probably a more elegant way of writing this, but this works:

    :BasicUpstart2(main)
    
    .pc = $c000
    main:
      ldx #0
      lda #24
    fill_row_1:
      sta $0400,X
      inx
      cpx #40
      bne fill_row_1
    
      ldy #0
      ldx #24
    
      //  Load screen base address ($0400) into location $fb
      lda #$0
      sta $fb
      lda #$04
      sta $fc
    
      //  Load character "X" into A
      lda #24
    fill_column_1:
      //  Save A because add_words_imm changes it
      pha
    
      //  Increment target address by 40 characters
      add_words_imm($fb, 40, $fb)
    
      //  Restore A
      pla
    
      //  Write character to screen location
      sta ($fb),y
      dex
      bne fill_column_1
    
      rts
    
    .macro add_words_imm(a, b, result) {
        clc
      .for(var byte = 0;  byte < 2; byte++) {
        lda a + byte
        adc #extract_byte(b, byte)
        sta result + byte
      }
    }
    
    .function extract_byte(value, byte_id) {
      .var bits = byte_id * 8
      .eval value = value >> bits
      .return value & 255
    }
    
    

    We store the target address in a zero-page location ($fb) and then use add_words_imm to increment this value each time through the loop. We set Y to zero (and never change it) so we can use $fb as the address of our next character.

    We use X as the loop index and loop 24 times; once for each screen line.


    For the record and future readers, if I were writing this myself I wouldn't bother with those subroutines and I would instead write:

    :BasicUpstart2(main)
    
    .pc = $c000
    main:
      ldx #0
      lda #24
    fill_row_1:
      sta $0400,X
      inx
      cpx #40
      bne fill_row_1
    
      lda #24
      ldy #6
      ldx #0
      clc
    fill_column_1:
      sta $0400,X
      sta $04f0,X
      sta $05e0,X
      sta $06d0,X
      pha
      txa
      adc #40
      tax
      pla
      dey
      bne fill_column_1
    
      rts
    

    This divides the screen into four blocks of six lines each and fills the first column of these blocks together.