问题
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?
回答1:
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
回答2:
First of all, if you're going to move SP, you need to save and restore it. Second, you need to disable interrupts or else you'll have a race condition bug: if an interrupt triggers near the end of the copy, the stack will grow down into whatever is below it, which happens to be the VAT.
; Index registers are actually fast on the eZ80
ld ix, 0
add ix, sp
di
; Do some hack using SP here
ld sp, ix
ei
@Ped7g The eZ80 will cache any -IR/-DR suffix instruction; unlike the Z80, it doesn't reread the opcode from memory on each iteration. Consequently, instructions like LDIR can execute each iteration in just 2 bus cycles, one read and one write. The SP hack is therefore not only needlessly complicated, but actually slower. The SP hack still best left to more experienced programmers.
The eZ80 is very well pipelined and its performance is limited by its lack of any cache and 1-byte-wide bus. The only instruction that runs slower than the bus is MLT, a 2-bus-cycle instruction that needs 5 clock cycles. For every other instruction, just count the number of bytes in the opcode, and the number of read and write cycles, and you've got its execution time. It's a huge pity that in the TI-84+CE series, TI decided to pair the fast eZ80 with an SRAM that somehow needs four clock cycles for each read and write (at 48 MHz)! Yes, TI, a world leader in semiconductor design, managed to design a slow SRAM. Getting on-die SRAM to perform poorly is an engineering feat.
@harold has the right answer, though I prefer optimizing for size instead of speed outside of inner loops.
#include "includes\ti84pce.inc"
.assume ADL=1
.org userMem-2
.db tExtTok,tAsm84CeCmp
call _homeup
call _ClrScrnFull
; Initialize registers
ld hl, vRam
ld bc, lcdWidth * lcdHeight * 2 - 2
push hl
pop de
; Write initial 2-byte value
ld (hl), 31
inc hl
ld (hl), 0
inc hl
ex de, hl
; Copy everything all at once. Interrupts may trigger while this instruction is processing.
ldir
call _GetKey
call _ClrScrnFull
ret
On EFnet, #ez80-dev is a good place to ask questions. cemetech.net is also a good place.
回答3:
This does not work:
dec bc
jr z,j2
Only 8 bit dec
and inc
modify the flags. It could be fixed by properly detecting whether bc
is zero.
Here is a different technique without manual looping:
ld hl,$D40000
ld (hl),31
inc hl
ld (hl),0
dec hl
ld de,$D40002
ld bc,$25800 - 2
ldir
回答4:
See Update 3 at the bottom.
In addition to @harold's answer: if there's a need for a faster alternative a well-known trick with PUSH can be used.
I'm not familiar with TI-84, the stack trick might be unacceptable on some systems or require interrupts to be disabled. And of course you are supposed to store/restore the SP before/after the above code.
Update 3: Removed my code snippet as it was incorrect for eZ80 anyway. However, thanks to the links provided by @DrDnar here is someone's -not mine! :)- attempt to push the performance to the limit (yes, I'm aware that filling with $55 is not the same as alternating between $31 and $00):
Code:
FastClr: ld de,$555555 ; will write byte 85 (= blue color) or a sbc hl,hl ld b,217 di add hl,sp ; saves SP in HL ld sp,vram+76818 ; for best optimisation , we'll write 18 extra bytes ClrLp: .fill 118,$d5 ; = 118 * "PUSH DE" djnz ClrLp ; during 217 times ld sp,hl ; restore SP ei
16+4+8+8+4+4+16+217*(118*10+13)-5+4+4=258944 States !!! ;D (the classic LDIR takes about 537600 states)
Cemetech source There are more (allegedly faster) examples there.
This at least raises certain doubts regarding the claim that LDIR is the fastest option, so i would be interested in @DrDnar's comments.
Note: I'm not saying the claim is wrong as I'm not in the position to test any of this and see for myself. I've noticed that the author of the above code, although they mention "TI83PCE/TI84+CE" in the original post, perform the actual measurements on a TI83PCE only - and this might be important.
Also, the addresses and the size used in the code are not the same as in the OP's code, and the "8bpp mode" is mentioned, which again tells me very little but the OP does not mention any particular mode.
Update 4: The link provided by @iPhoenix contains loads of info on the TI-84+CE, including the LCD Controller details. This page explains, among other things, why '8bpp mode' has been specifically mentioned by the author of that code above:
When the LCD is in 8bpp mode, data written to VRAM will act as an 8-bit index to the LCD's 256x16-bit Color Palette. Note that the colour palette must be initialized prior to setting this mode or you will receive unexpected results. (See LCDPalette register - 0x200 for information on the Color Palette). This will effectively halve the amount of VRAM required to store a full resolution 320x240 image (76800 bytes vs 153600 bytes). The extra 76800 bytes of VRAM could be used to double buffer or for temporary data storage. Note that the TIOS will not be usable in this mode, it expects 16bpp 5:6:5 mode at all times.
In other words - rather than filling the 153600 bytes of VRAM with 0x31,0x00 (16bpp colour, presumably), the OP could fill half of VRAM with a single byte value XY and configure (prior to the actual filling) the Color Palette so that the XY value maps to the desired 16bpp colour and thus achieve the same result.
With this approach any "inconveniences" of alternating between 31 and 00 just go away naturally.
来源:https://stackoverflow.com/questions/57483503/how-to-write-two-bytes-to-a-chunk-of-ram-repeatedly-in-z80-asm