Search code examples
assemblytexas-instrumentsz80

How to write two bytes to a chunk of RAM repeatedly in Z80 asm


I'm trying to write two bytes (color values) to the VRAM of my TI-84 Plus CE-T calculator, which uses the Zilog eZ80 CPU. The VRAM starts at 0xD40000 and is 0x25800 bytes long. The calculator has a built in syscall called MemSet, which fills a chunk of memory with one byte, but I want it to alternate between two different values and store these in memory. I tried using the following code:

#include "includes\ti84pce.inc"

    .assume ADL=1
    .org userMem-2
    .db tExtTok,tAsm84CeCmp

    call  _homeup
    call  _ClrScrnFull
    ld    hl,13893632     ; = D40000, vram start
    ld    bc,153600       ; = 025800, count/vram length
j1:
    ld    (hl),31         ; set first byte
    inc   hl
    dec   bc
    jr    z,j2            ; jump to end if count==0
    ld    (hl),0          ; set second byte
    inc   hl
    dec   bc
    jr    z,j2            ; jump to end if count==0
    jp    j1              ; loop
j2:
    call  _GetKey
    call  _ClrScrnFull
    ret

I want it to output 31 00 31 00 31 00... into memory starting at 0xD40000, but instead it seems to change only the first byte and jump to the end after doing so. Any ideas on how to fix this?


Solution

  • The variation of tum_'s answer with faster-than-regular-dec bc zero test mechanism for looping.

        LD   SP,$D65800    ; <end of VRAM>: 0xD40000+0x25800
        LD   BC,$004B      ; 0x4B many times (in C) the 256x inner loop (B=0)
            ; that results into 0x4B00 repeats of loop, which when 8 bytes per loop
            ; are set makes the total 0x25800 bytes (VRAM size)
            ; (if you would unroll it for more than 8 bytes, it will be a bit more
            ; tricky to calculate the initial BC to get correct amount of looping)
            ; (not that much tricky, just a tiny bit)
        LD   HL,31         ; H <- 0, L <- 31
    .L1
        PUSH HL            ; (SP – 2) <- L, (SP – 1) <- H, SP <- SP - 2
        PUSH HL            ; set 8 bytes in each iteration
        PUSH HL
        PUSH HL
        DJNZ .L1           ; loop by B value (in this example it starts as 0 => 256x loop)
        DEC  C             ; loop by C ("outer" counter)
        JR   NZ,.L1        ; btw JP is faster than JR on original Z80, but not on eZ80
    .END
    

    (BTW I never did eZ80 programming, and I didn't verify this in debugger, so this is kinda full of assumptions... actually thinking about it, isn't push on eZ80 32 bit? The the init of hl should be ld hl,$001F001F to set four bytes with single push, and the inner body of loop should have only two push hl)

    (but I did ton of Z80 programming, so that's why I even bother with comment on this topic, even if I haven't seen eZ80 code ever before)

    Edit: turns out the eZ80 push is 24 bit, i.e. the code above will produce incorrect result. It can be of course easily fixed (as the issue is implementation detail, not principal), like:

        LD   SP,$D65800    ; <end of VRAM>: 0xD40000+0x25800
        LD   BC,$0014      ; 0x14 many times (in C) the 256x inner loop (B=0)
            ; that results into 0x1400 repeats of loop, which with 30 bytes per
            ; loop set makes the total 0x25800 bytes (VRAM size)
        LD   HL,$1F001F    ; will set bytes 31,  0, 31
        LD   DE,$001F00    ; will set bytes  0, 31,  0
    .L1
        PUSH DE
        PUSH HL
            ; here SP = SP-6, and 6 bytes 31, 0, 31, 0, 31, 0 were set
        PUSH DE
        PUSH HL
        PUSH DE
        PUSH HL
        PUSH DE
        PUSH HL
        PUSH DE
        PUSH HL            ; unrolled 5 times to set 30 bytes in total
        DJNZ .L1           ; loop by B value (in this example it starts as 0 => 256x loop)
        DEC  C             ; loop by C ("outer" counter)
        JR   NZ,.L1