Jump to content

GB Programming: Difference between revisions

Continued the page. Man, it's been a long time (in galaxy far, far away...)
>Raltseye
(Removed unnecessary space)
>ISSOtm
(Continued the page. Man, it's been a long time (in galaxy far, far away...))
Line 520:
|Not affected
|-
|ADD A, <nowiki>{reg8 | imm8 | reg16 | (hl)}</nowiki>
|Adds the operand to the accumulator
|Affected
Line 534:
|Not affected
|-
|SUB A, <nowiki>{reg8 | imm8 | reg16 | (hl)}</nowiki>
|Subtracts the operand from the accumulator. The syntax SUB A, <nowiki>{...}</nowiki> is also valid but less common.
|Affected
|Affected
Line 557:
 
Also, the Game Boy's CPU as four very specific instructions :
{| class="wikitable"
{|
|ld (hli), a
|Equivalent to ''ld (hl), a'' then ''inc hl''.
Line 754:
Third, JR takes 7 or 12 CPU cycles to run, whereas JP always takes 10.
 
Labels are actually memory addresses, they mark the target of the jumps. Here is an example of label usage :
And this brings us to the next part!
<pre>
loop: ; This is a label ! This defines label "loop".
sub a, $0A
jr c, finished
; Do stuff with a...
jr loop ; This jumps to the "sub a, $0A" right after the "loop:" line.
finished:
inc a
; Do some more stuff, we don't care anymore.
</pre>
 
You may wonder what this "jr c, finished" is. And this brings us to the next part!
 
===Conditionals===
Line 778 ⟶ 790:
Four instructions can use conditionals : CALL, RET (these are coming in soon), JP and JR. JR has a handicap, though : it can only use the Z, NZ, C and NC conditions.
 
So, there you see how you can create conditionals in z80 assembly : by shifting the flow of code.
 
I want you to understand the following : even though you have more contol over the flow of code, try to be organized ; intricate jumps can be excessively tough to understand. Also, jumps consume space and time ; try to jump the least you can.
 
Last thing, though it's more on the optimization side, but remember : if you're going for space - and that's often the case with ACE - you should use jr. But if speed is a must (that is, you absolutely need to shave 5 CPU cycles per jump, which is *RARE*), use jp. Use jp also when jr cannot reach the target - never use a chain of jr spaced by $7F bytes. It's '''pointless'''.
 
===A special jp===
There is one special case of jp, though !
{| class="wikitable"
|JP (HL)
|Has execution jumping to the address pointed to by hl.<br/>Does not accept any conditionals.
|}
Example :
<pre>
ld hl, $2457
jp (hl)
</pre>
will jump to $2457. Some might argue that "jp $2457" is better, as it'd save 1 byte and preserve the hl register.
 
However, jp (hl) is used to do dynamic jumps : jp (hl) may jump to a different location every time it is ran. When doing "static" (ie. always the same) jumps, it IS better to use jp $xxyy. jp (hl) mostly used with function pointer tables - we'll see that later.
 
===Comparing stuff===
Here is an instruction that is heavily used with conditional jumps :
{| class="wikitable"
|CP <nowiki>{reg8 | imm8}</nowiki>
|Does the same as SUB <nowiki>{...}</nowiki>, but leaves a untouched.
|}
cp is heavily used with conditionals, since it compares the accumulator's value with another. Here is a nifty table :
{| class="wikitable"
|Comparison
|Unsigned equivalent
|Signed equivalent
|-
|A == ''number''
!colspan="2"|Z is set (A - ''number'' == 0)
|-
|A != ''number''
!colspan="2"|Z is not set (A - ''number'' != 0)
|-
|A < ''number''
|C is set (A - ''number'' generated a borrow)
|S and P/V are different (P/V means overflow)
|-
|A >= ''number''
|C is reset (A - ''number'' generated no borrow)
|S and P/V are the same
|}
Example :
<pre>
ld a, (hl)
cp $63
jr z, placeItems
inc hl
inc hl
jr someplace
placeItems:
ld b, (hl)
</pre>
If (hl) equals $63, execution jumps to placeItems.
 
Otherwise, executions continues through, increments hl twice, then jumps to "someplace"
 
===Chaining conditionals===
!!WARNING!! The code I'll be writing in assembly can be written in other ways. If you think you'd have done it in another way, try it. Count the instructions in your code, and if it is less than I did, then you did well !
 
Don't assume my way is the only. It is a good idea to try to find other ways to do the stuff I propose ! It's a good exercise !
 
Okay, so let's try the following C code, assuming a is the a register :
<pre>
if(a == $2A) {
// Success stuff
} else {
// Failure stuff
}
// Rest of the code
</pre>
In assembly, that's easy !
<pre>
cp $2A
jr nz, failure
; Success stuff
jr afterConditional
failure:
; Failure stuff
afterConditional:
; Rest of the code
</pre>
Think of another way... like, having the failure stuff first.
<pre>
cp $2A
jr z, success
; Failure stuff
jr afterConditional
sucess:
; Success stuff
afterConditional:
; Rest of the code
</pre>
Got it ? Good !
 
Okay. Let's get it one level higher.
<pre>
if(b == $C0 && c == $DE) { // && means AND. But it's a logical AND - we'll see another AND later.
// Success stuff
} else {
// Failure stuff
}
</pre>
Um... let's try doing multiple jumps.
<pre>
ld a, $C0
cp b
jr nz, failure
ld a, c
cp $DE
jr nz, failure
; Success stuff
jr afterCond
failure:
; Failure stuff
afterCond:
</pre>
Phew ! Not exactly the same, but it's still going fine.
 
To do a AND, simply treat stuff as a failure if any of the conditions fail.
 
Okay. Let's get it a lil' bit different.
<pre>
if(b == $C0 || c == $DE) { // || means OR. But it's a logical OR- we'll see another OR later.
// Success stuff
} else {
// Failure stuff
}
</pre>
Um... let's try doing multiple jumps.
<pre>
ld a, $C0
cp b
jr z, success
ld a, c
cp $DE
jr z, success
; Failure stuff
jr afterCond
success:
; Success stuff
afterCond:
</pre>
Okay ! Now, to do OR, we simply run each comparison. If any succeeds, we jump straight to SUCCESS. Othewise, we FAIL.
 
I'll leave the following code as an exercise :
<pre>
if((h == $C0 || l == $DE) || a == $2A) {
// Success stuff
} else {
// Failure stuff
}
</pre>
 
===On to loopings===
Some of you might have thought "Hey, ISSOtm. Up until now, you've been going forwards all the time. Even your jumps were skipping over instructions - but forever forwards. What if we went '''backwards''' ?"
 
Well, kudos to you ! This is the basis of looping structures.
 
Here is the most simple loop in assembly :
<pre>
loop:
; Do stuff
jr cond, loop
</pre>
Ta-daah ! Here is a more explicit (less generic) loop:
<pre>
ld b, $06
countingLoop:
; Do stuff (admit it preserves b)
dec b ; Sets Z if b == 0 after the DEC.
jr nz, countingLoop ; go back if b is non-zero
; Do some MOAR stuff
</pre>
This should run the "Do stuff" part six times exactly. If said part modifies b... it will work in other ways.
 
I'll leave to you as an exercise what would happen if the "ld b, $06" was replaced by a "ld b, $00"...
 
Now you got how to create loops. Neato. Let's see how to create... routines. Or procedures. Or functions. Whatever you call them.
 
===Routines / Functions / Procedures / Whatever===
I've told you about CALL and RET in the "Conditionals" section. Now let's see what they do.
 
They are a more avanced way to do jumps. Basically, you "call" a piece of code, that does its stuff, then "returns" to your code control of the CPU. Bam, CALL and RET explained.
 
{| class="wikitable"
|CALL label16
|Calls code starting at label16
|-
|CALL cond, label16
|Same, but with the same conditionals as JP (not JR)
|-
|RET
|Returns to the previous "caller".
|-
|RET cond
|Do I need to explain ?
|}
 
But wait, there is more ! And it is '''VITAL'''. The way call works is very simple :
# Get the address of the instruction right after the call.
# Push it onto the stack.
# Jump to the address specified by the call adress.
 
And ret works in a way that is compatible with call :
# Pop a number from the stack.
# Jump to that address.
 
Now, you need to be super-duper careful with the stack, since it is used by the call-ret system. Basically, make sure that between label16 and the next ret, you have done the same amount of PUSHes and POPs :
<pre>
call routine
; Ton of code
routine:
push hl
push bc
; Code
pop bc
; Codez
pop hl
; Codeeeee
ret
</pre>
is fine
<pre>
call baaad
; Ton of code
baaad:
push hl
push bc
; Code
pop bc
; Codeeeey
ret
</pre>
is bad.
 
Unless you'e ABSOLUTELY CERTAIN about what you're doing, upon leaving your routine's code, leave the stack the SAME is was. It is absolutely necessary to avoid screwing up everything.
 
 
<hr>
==Solutions to the exercises==
===Instructions get!===
Line 873 ⟶ 1,130:
|}
 
===Chaining conditionals===
==Credits==
You think you gotcha ? I'll be explaining the code, don't'cha worry :)
Tutorial written by ISSOtm for Glitch City Laboratories.
<pre>
cp $2A ; it's better to do the a comparison first, since we'll use it for later comparisons.
jr z, success ; if we don't jump, we will do the ANDed comparison.
ld a, b
cp $C0
jr nz, failure ; first AND operand...
ld a, c
cp $DE
jr nz, failure ; ...then the second. The order doesn't matter here.
success:
; Success stuff
jr afterConditional
failure:
; Failure stuff
afterConditional:
</pre>
Didja get it ? If not, try to understand which part of the code matches which part of the C code. If you get it, you'll understand the assembly code. I admit it's tough at the first glance. I've been through this, don't'cha worry.
 
===On to loopings===
What would happen ? Well, imagine you ran the "Do stuff" part once. B is zero - we didn't touch it - and we reached a "dec b". Now, remember what the instruction does :
# We decrement B (B = 0 - 1 = 255, remember overflow ?)
# If B is zero, we set the Z flag. Otherwise we reset it. (Z is reset, since B != 0)
... so the loops starts again with B = 255.
 
tl;dr : the loop is ran 256 times !
 
"Hey ISSOtm, what if I wanted my loop to run zero times instead of 256 in that case ?" Simple !
<pre>
ld a, b ; this does NOT modifiy Z !!
cp $0 ; there is a more efficient way of doing this, but you don't know it yet.
jr z, afterLoop ; if b - 0 == 0, we skip the loop completely.
countingLoop:
; Do stuff (admit it preserves b)
dec b ; Sets Z if b == 0 after the DEC.
jr nz, countingLoop ; go back if b is non-zero
afterLoop:
; Do some MOAR stuff
</pre>
 
==Credits & Resources==
Tutorial written (mostly) by ISSOtm for Glitch City Laboratories.
 
Thanks to Torchickens and RaltsEye for correcting typos, formatting 'n stuff.
 
Includes bits of [http://tutorials.eeems.ca/ASMin28Days/welcome.html this tutorial] for the ASM part.
 
Heavily using [http://gbdev.gg8.se/wiki/articles/Pan_Docs the Pan Docs] for GameBoy-specific stuff.
 
[[Category:Arbitrary code execution]]
[[Category:Reference documents]]
Anonymous user
Cookies help us deliver our services. By using our services, you agree to our use of cookies.