Omnimaga
Calculator Community => TI Calculators => ASM => Topic started by: ralphdspam on December 24, 2011, 12:27:13 am
-
The ASM board hasn't been as active as it should be, so I'm starting a weekly discussion called "ASM Command of the Week." Basically, we will discuss hacks, routines, optimizations, or alternatives to the command.
It's basically going to be like these topics, except this one is going to be weekly.
DJNZ:
http://ourl.ca/13569
RLD and RRD:
http://ourl.ca/12758
IX and IY:
http://ourl.ca/12633
We'll start with a really easy one. This week's command is:
XOR
-
Possibly one of the best uses of XOR is in combination with AND, to act as a bitwise multiplexer.
For example, this uses the bits of C to select bits from A and B: if the bit in C is 1, that bit is selected from A, and if the bit in C is 0, that bit is selected from B.
xor b
and c
xor b
-
xor a is probably the most common use, faster than "ld a, 0"
xor is also commonly used to "flip" bits in register A.
-
You can also use xor to test whether two bits within a byte are the same.
and MASK
jr z, bitsAreTheSame
xor MASK
jr z, bitsAreTheSame
;... otherwise, bits are different
where MASK is a mask of the two bits to compare
With this concept, you can use it to swap two bits within a byte:
ld a, (addr)
and MASK
jr z, doneSwapping
xor MASK
jr z, doneSwapping
ld a, (addr)
xor MASK
ld (addr), a
-
and MASK
jp pe,bitsAreTheSame
/me runs
-
Explain how that works please...>.< I've never understood how to use parity.
Edit: After thinking about it that makes sense, but that only allows you to test two bits, whereas my method could be used to test any number (I think). Of course the swapping would only work for two bits at a time...
-
AND mask
masks out all bits except the two in question. If the remaining two bits are the same, either there will be 8 zeros and no ones, or 2 ones and 6 zeros. Thus, the parity will be even. However, if the remaining two bits are different, 1 will be zero, and 1 will be one. Thus, the parity will be odd.
-
Possibly one of the best uses of XOR is in combination with AND, to act as a bitwise multiplexer.
For example, this uses the bits of C to select bits from A and B: if the bit in C is 1, that bit is selected from A, and if the bit in C is 0, that bit is selected from B.
xor b
and c
xor b
What could you/would you want to use this for?
I think this is an interesting topic and would like to see it continue :)
Like Hot_dog, i often use xor to flip bytes. I think i'm pretty unimaginative though when i code.
-
Possibly one of the best uses of XOR is in combination with AND, to act as a bitwise multiplexer.
For example, this uses the bits of C to select bits from A and B: if the bit in C is 1, that bit is selected from A, and if the bit in C is 0, that bit is selected from B.
xor b
and c
xor b
What could you/would you want to use this for?
You can use it for a lot of graphics stuff. ie: masking sprites, grayscale, etc.
I also use it for preserving certain flags in a register while changing others.
-
Ok, i'm not really too interested in grayscale, but i'd like to see how masking sprites might work out.
Here's some basic code to draw a masked byte to the gbuf:
;ix=masked byte to draw
;ix+1=byte mask
;hl=location in gbuf to draw to
ld a,(ix+1) ;load mask into a
and (hl) ;and the mask onto the byte in gbuf
or (ix) ;draw the byte
ld (hl),a ;update gbuf
So that would become:
ld a,(ix)
xor (hl)
and (ix+1)
xor (hl)
ld (hl),a
Which is an extra byte larger. I've always drawn my masks like this:
sprite:
%00011000
%00111100
%01111110
%11111111
mask:
%11100111
%11000011
%10000001
%00000000...a 1 being a byte to preserve in the gbuf (whereas i suppose with the xor method it'd be inverted).
If you want, i've got some code from a masked sprite routine i wrote a while back for an rpg i was working on that we could try to optimize :D I don't like how i went about it, first rotating/loading the mask onto the gbuf and updating the gbuf, then rotating/loading the sprite onto the gbuf (drawing twice to the gbuf).
drawPlayerLoop:
push bc
drawPlayerRow
ld d,(ix+32) ;sprite mask
ld e,(ix+33)
ld a,$ff ;a 1 means the gbuf bit will be preserved
dec b \ inc b \ jr z,skipMaskClip ;if b=0, no need to rotate
scf \ rr d \ rr e \ rra \ djnz $-6 ;rotate mask b bits
skipMaskClip:
and (hl) ;a = mask to the furthest right
ld (hl),a ;store masked gbuf
dec hl ;next byte...
ld a,(hl)
and e
ld (hl),a
dec hl ;last byte
ld a,(hl)
and d
ld (hl),a
pop bc ;recall b=x offset, how many bits to rotate
inc hl ;return hl to starting gbuf position
inc hl
push bc ;store bc again
ld d,(ix) ;sprite
ld e,(ix+1)
xor a ;empty a to receive flowover from shifting
cp b
jp z,skipSpriteClip
srl d \ rr e \ rra \ djnz $-5 ;rotate sprite
skipSpriteClip:
or (hl) ;load sprite into masked gbuf
ld (hl),a ;update gbuf
dec hl ;next byte, etc.
ld a,(hl)
or e
ld (hl),a
dec hl
ld a,(hl)
or d
ld (hl),a
inc ix ;next sprite row
inc ix
ld de,14 ;jump to next row in gbuf, +2 (draw rightmost byte first)
add hl,de
pop bc
dec c
jp nz, drawPlayerLoop
-
By the way, are you still working on that RPG any? I always thought it had a really nice look to it. =)
-
Well, my laptop busted (or rather, i busted my laptop) but i had made a fairely recent backup of it (the code on yaronet). Right now i'm a bit spread out but especially once i get Monopoly taken care of (a project that's been biting at my feet for maybe 8 years now) it's next on my hitlist. Actually, a lot of the work i've done with the Monopoly text/menu engine will probably be carried over into the RPG. One of the big things i need to do now is work on the story a bit. Thanks, btw :)
Does anyone have any suggestions for the next "Command of the Week"?
-
Okay, time for a new command, ay? Well here goes:
CPIR
CPIR can be an extremely useful and powerful command, especially if you are doing a search.
What it does:
It performs cp (hl) then it decrements BC and increments HL. If the byte was a match, the loop stops and PE is the flag state of the parity/overflow flag and the other compare flags are affected normally. If BC reaches 0, the parity flag is PE.
As an example, if you want to find and jump to a certain line in an executing program, you can do something like this:
;Inputs:
; DE is the line to jump to
; (965Bh) points to the start of the program
; (965Fh) points to the end of the program
;Notes:
; The two addresses above are set accordingly while a BASIC program is running
push de
ld de,(965Bh) ;This is the start of the currently running program
ld hl,(965Fh) ;This is the end of the currently running program
or a
sbc hl,de ;HL is now the size of the program
ex de,hl
ld b,d
ld c,e
pop de
dec hl
;HL points to the start of the program
;DE is the line to jump to
;BC are the size of the program
;A is the newline token
SearchLoop:
dec de
ld a,d \ or e
jr z,StoLine
ld a,3Fh
cpir
ret nz ;this means BC hit 0. In other words, the line does not exist.
;the byte is 3Fh, but we need to make sure that it is not part of a two-byte token
dec hl \ dec hl
ld a,(hl)
inc hl \ inc hl
cp 7Eh \ jr z,SearchLoop+5
cp $AA \ jr z,SearchLoop+5
cp $BB \ jr z,SearchLoop+5
cp $EF \ jr z,SearchLoop+5
cp 5Fh \ jr z,SearchLoop
sub 5Ch \ jr c,SearchLoop
sub 7 \ jr c,SearchLoop+5
jr SearchLoop
StoLine:
dec hl
ld (965Dh),hl ;this loads the address to the BASIC program counter
ret
It can probably be optimised a little, but I am sure you get the idea :)
-
Nice code, but I can't think of any other uses of the command. Can you think of any hacky type usage?
-
You can use cpir to determine the length of a string or name. For example, if you want the length of a program name in OP1 with a maximum limit of 9:
ld bc,9
ld hl,OP1
xor a
cpir
jr nz,$+3 ;This means the string ended at maximal length
ccf
ld a,9 \ sbc a,c
ld c,a
In fact, you can turn that into a general code where you can find the length of a zero-terminated string pointed to by HL with a maximum size:
push bc
xor a
cpir
pop hl
jr nz,$+3 ;This means the string ended at maximal length
ccf
sbc hl,bc ;the c flag was reset by 'xor a', so hl is the length
ret
There are other ways that I have used it, too, I just need a moment to think of some more uses XD I was actually hoping some other coders had neat hacks.
EDIT: Another use for this is when you want to display a string, but only up to x digits.
-
I haven't responded yet simply because, to be honest, i had no idea this command even existed :/ I could see it maybe being used for a fixed-width font text-wrapping routine or something, though that might not be quite so simple as you'd have to check for a space character and EOS character.
-
In that case, you can play with CPI >.>
-
Here's the next one:
CPL
-
CPL is a great command for logic. Pretty much, it inverts the A register. The most common thing I use it for, is with drawing sprites and pixels with erasing logic. For example, to display a sprite by erasing, take the sprite data and invert it with cpl, then use AND logic on the screen. Also, you can use it to invert the graph buffer:
ld hl,9340h
ld bc,3
ld a,(hl)
cpl
ld (hl),a
inc hl
djnz $-4
dec c
jr nz,-7
ret
-
CPL can also be used to subtract from 255. This is useful to subtract A from any number N by doing this:
cpl
add a,N+1
Of course, if N=254, 255, or 0, replace the add with a dec a, nothing, or inc a respectively.
-
Building off of what calc said, NEG is like CPL \ INC A, except NEG will modify the c flag.
-
Like others have said, I use cpl for a quicker/smaller xor $FF, for example in my tilemapper to get the mask when rotating the screen:
updateRotation:
ld a,(xOff) ;what is the xOffset?
and 7 ;we're only interested in the lower three bits (essentially xOff % 8)
ld hl,gbufMask
ld e,a
ld d,0
add hl,de ;pointer to the rotation mask
ld a,(hl)
ld hl,maskLeft
ld (hl),a
ld hl,maskRight
cpl ;xor $FF
ld (hl),a
;...
ret
gbufMask:
.db %11111111 ;0 display all of the first byte, none of the second
.db %11111110 ;1 after rotating left once, we only want the 7 leftern most bits of byte 1, and rightmost bit of byte 2
.db %11111100 ;2
.db %11111000 ;3
.db %11110000 ;4
.db %11100000 ;5
.db %11000000 ;6
.db %10000000 ;7
...which, btw, is based off an idea calc84maniac mentioned on IRC a few years ago of doing the rotation in the fastcopy routine, though i remember you saying that your main fastcopy loop was much faster than mine :)
-
The next one is
SCF
Normally, this is used for math (do share ;D)
Sometimes I find the need to use the carry flag for conditions beyond what it is normally used for (detecting overflows or underflows). For example, I could theoratically use C or NC to see if an object is collidable or not. Whatever the situation, I would use SCF to set the carry flag if a condition is true.
-
Was I not updating the topic weekly? Whoops. :P
Anyways, there is a very nice use of SCF in Axe's sound routine. Instead of using it for math, it is used for conditional branching.
xor a
__FreqOutLoop1:
push bc
ld e,a
__FreqOutLoop2:
ld a,h
or l
jr z,__FreqOutDone
dec hl
dec bc
ld a,b
or c
jr nz,__FreqOutLoop2
ld a,e
xor %00000011
scf
__FreqOutDone:
pop bc
out ($00),a
ret nc
jr __FreqOutLoop1
-
I often like to use it to return whether a function was successful or not. For example, the c flag here means the pixel was in bounds, so its location was computed normally:
GetPixelLoc:
;Input:
; b is X
; c is y
;Output:
; HL points to byte
; A is the mask
; nc if not computed, c if computed
ld a,c
cp 64 \ ret nc
ld a,b
cp 96 \ ret nc
ld l,c
ld h,0
ld b,h
add hl,hl
add hl,bc
add hl,hl
add hl,hl
ld b,a
rrca \ rrca \ rrca
and 0Fh
ld c,a
ld a,b
ld b,0
add hl,bc
ld bc,(BufPtr)
add hl,bc
and 7
ld b,a
inc b
ld a,1
rrca
djnz $-1
scf
ret
Sometimes I need to correct an addition or subtraction by also adding or subtracting one:
OP1NameLength:
;Returns the name length in HL
ld hl,8478h
NameLength:
xor a \ ld b,a \ ld c,a
cpir
ld h,a \ ld l,a
scf \ sbc hl,bc
ret
(There is a better version of that)
Also, sometimes there are some very specific adjustments. In this case, you will see scf is used only when the end of the file is met, so an extra 1 must be subtracted to get the line length:
SearchLine:
;Inputs:
; HL points to the start
; BC is the number of bytes to search
; DE is the line number
; A is the line byte
;Outputs:
; A is not changed
; BC is the number of bytes left for the search
; DE points to the line
; HL is the length of the line
;===============================================================
dec de \ inc d \ inc e
or a
ld (TempWord1),hl
SearchLoop:
cpir ;21X-5
jp po,EndSearchLoop+1
dec e \ jr nz,SearchLoop-3
dec d \ jr nz,SearchLoop-3
EndSearchLoop:
scf
ld de,(TempWord1)
sbc hl,de
ret
-
OR
In bit manipulation, you can use this to set bits
ld a, %10000000
or %10000001 ;The first and last bits will be set no matter what. All others will not be affected.
OR A is a wonderful substitue to "CP 0." Also, OR A is a great way to reset the carry flag. Whenever you want to subtract something from HL, you have to use the instruction SBC, and in a lot of cases, you want to make sure that the carry flag is reset, or your subtraction result could come out wrong.
ld hl, 10000
ld de, 5000
or a
sbc hl, de ;Now it's guranteed that HL = 5000 instead of 4999.
-
OR can be used to check if a multi-byte value is zero. For example, to check if BC is zero, use
ld a,b
or c
jr z,Value_Is_Zero
If you have a 32-bit value in DEHL, you can similarly use
ld a,d
or e
or h
or l
jr z,Value_Is_Zero
-
BIT
This will test a bit in a register to see if it's 1 or 0. I find this instruction especially useful for (hl) and A (If I don't want to lose the value that I have stored in register A with bit manipulation)
-
In a similar vein, you can do some soft-core SMC to speed up a routine such as pixel-testing or even turning the pixel on or off:
;===============================================================
PlotPixel:
;===============================================================
;Inputs:
; B is the x-coordinate
; C is the y-coordinate
; D=Method
; 1=Test
; 2=Off
; 3=On
; (BufPtr) points to the buffer to draw to
;Outputs:
; BC is equal to (BufPtr)
; DE not modified
; HL points to the byte the pixel is drawn to
; When using the pixel test method:
; z means pixel off
; nz means pixel on
;Destroys:
; A
;===============================================================
ld a,3Fh ;7
srl b \ rra ;12 19
srl b \ rra ;12 31
srl b \ rra ;12 43
cpl ;4 47
or d ;4 51
rrca \ rrca ;8 59
ld (Method),a ;13 72
ld a,b ;4 76
ld b,0 ;7 83
ld h,b \ ld l,c ;4 87
add hl,bc ;11 98
add hl,bc ;11 109
add hl,hl ;11 120
add hl,hl ;11 131
ld c,a ;4 135
add hl,bc ;11 146
ld bc,(BufPtr) ;20 166 BufPtr points to the buffer
add hl,bc ;11 177
.db $CB ;15 192 First byte of BIT
Method:
.db 46h ; second byte of the BIT instruction
ret ;10 202 37 bytes
-
bit 5,a
bit 3,a
These will set the hidden flag in the F register in the same way as, and in addition to, the z flag. Obsucre? Yes. Useful? >:D
-
The hidden flags only exist on the old models of the TI-83+, though (the ones with real Zilog chips)
-
But is there actually any use for the hidden flags? Are the values documented?
-
EXX
-
That is a pretty useful command, especially if you need more registers, or you need to preserve some and you don't use interrupts. I personally rarely use interrupts, so super complicated math functions get to use b'c'd'e'h'l' :D
So details:
4 cycles, 1 byte, hex code: D9
Swaps registers with shadow registers
So as a really poor example, say I need to copy some data to memory somewhere, but I need to preserve all registers for some reason. Also, assume you don't need interrupts :P
di
exx
ld bc,768
ld hl,BufData
ld de,plotSScreen
ldir
exx
And yay, your registers are preserved :D
-
I use shadow BC, DE, HL in all of my intense rendering methods: Project M parallax tilemapper, F-Zero mode7 engine, and TI-Boy tilemapping. (I rarely find a need to use shadow AF, which I would usually give to interrupts).
Also, shadow registers play a crucial role in TI-Boy's CPU emulator, as they hold the Game Boy's BC,DE,HL registers. This allows me to execute many GBZ80 opcodes almost directly, by doing something like exx \ add a,e \ exx in order to emulate add a,e, etc.
-
Wow, nice, now I see why it is so fast o.o
-
@calc84:That's pretty cool. =)
-
Can anyone think of a good use for otd/oti? I just saw JimE's Keyin (http://www.ticalc.org/archives/files/fileinfo/239/23966.html) routine which has a pretty interesting use of ini to store all DI keypresses into saferam that you can access using "bit key,(iy+keygroup)". It's a pretty interesting idea i might implement into some of my programs, especially since you don't have to worry about registers getting overwritten :)
-
I have used these commands to read/write data to the LCD ports.
EDIT: For example, if I wanted to draw a simple sprite, assuming the LCD coordinates are already loaded:
ld bc,0811h ;b=8, size of the sprite, c=11h, the LCD Data port
ld hl,SpriteData
DrawLoop:
<<any code to provide a proper delay>>
outi
jr nz,DrawLoop
ret
SpriteData:
.db 3Ch,42h,81h,81h,81h,81h,42h,3Ch ;ball
-
That's what i figured they could be used for, though i was thinking more along the lines of a fastcopy routine. Thanks for the sample.
-
Let's not forget USB.
send8BytesBitsOverPipe0:
ld hl, bytesToSend
ld bc, 8*256+$A0
otir
ret
;#######################################
;take in all the bytes from a port
;input: c = port
;output: b = bytes received
importBytes:
ld b, 0
importLoop:
in a, ($96)
or a
ret z
ini ;this dec's b
inc b
inc b
jr importLoop
Every USB program includes some form of ini/inir and oti/otir. The reason you can do this is because the USB driver has internal buffers and does not need a delay when you write to it.
-
Hmm, so if I am reading your post properly, the first code sends 8 bytes to port $A0, and the second writes all the bytes being received, to HL, and then returns the number of bytes that were received? Is USB control that easy?
-
Hmm, so if I am reading your post properly, the first code sends 8 bytes to port $A0, and the second writes all the bytes being received, to HL, and then returns the number of bytes that were received? Is USB control that easy?
The one that reads all of the data is that easy. The driver basically says, "I got some data for you" and you read it all.
The output one is super simplified, but this small section of code does appear in most data transfer code.
USB is super hard.
-
Okay, I will have to experiment a little with that, then. I don't care to use a standard USB protocol, anyways.
-
ooh, are you going to be using it for direct linking between calcs?
-
Yes, that is what I would try to do, if I can manage it.