Jump to content

GB Programming: Difference between revisions

>Torchickens
No edit summary
 
(17 intermediate revisions by 9 users not shown)
Line 4:
 
Let's get started!
 
 
==A new world==
Line 49 ⟶ 48:
* A group of 32 bits is called a '''double word''' or '''dword'''.
* A group of 64 bits is called a '''quadruple word''' or '''qword'''.
We will mostly be working with bytes, sometimes with words and rarely with nibbles. It is weryvery rare to work with other structures, so you may forget them if you will.
 
 
Now, let's talk about '''hexadecimal'''. It is '''base 16''', so we will be working with 16 symbols : 0 1 2 3 4 5 6 7 8 9 A B C D E F. Again, we will prepend hex numbers with a $ to differentiate them.
Line 71 ⟶ 69:
 
For the rest of this tutorial, we will mostly be using hexadecimal, but always remember the binary lying down below!
 
 
 
==A dip into technical information==
Line 127 ⟶ 123:
|DE
|HL
|([BC)]
|([DE)]
|([HL)]
|([imm16)]
|-
|A
Line 292 ⟶ 288:
|Yes
|-
|([BC)]
|Yes
|No
Line 308 ⟶ 304:
|No
|-
|([DE)]
|Yes
|No
Line 324 ⟶ 320:
|No
|-
|([HL)]
|Yes
|Yes
Line 340 ⟶ 336:
|No
|-
|([imm16)]
|Yes
|No
Line 400 ⟶ 396:
|Store value of register B into register D.
|-
|''ld ([$8325)], a''
|Store the value of register A into memory address $8325.
|}
Line 411 ⟶ 407:
 
Notice that F and AF aren't usable anywhere. Actually, only a few instructions use them.
 
 
===Negative numbers===
Line 418 ⟶ 413:
I've told you, "individual registers can hold unsigned 8-bit values, and pairs unsigned 16-bit values". However, these aren't true: these values can be signed. How does that work?
 
What we will be doing is cutting our number range in half, and telling one half is composed of negative numbers. But how to distinguish positive and negative numbers? Well, we tell the MSB is no longer meaning the symbol in front of 2^7, but it will give away the sign of the number (0 = positive, 1 = negative). So, instead of having values in ranges 0 -to 255 and 0 -to 65535, we will have values in ranges -128 -to 127 and -32768 -to 32767. Neat!
 
How to multiply by -1? Easy! You can either :
Line 439 ⟶ 434:
</pre>
And, you just saw why I told you to consider 0 the same as 256 : they are similar! As 256 uses 8 zero bits preceded by a 1... but the 1 is discarded.
 
 
===Memory===
Line 450 ⟶ 444:
So, how does running a program works? What happens is that a special register is incremented (its value is raised by one), then the processor fetches the byte located at the address held by that register, and processes it as an opcode ; when done, everything is repeated. Instructions can be one to three opcodes (bytes) large, so this cycle may repeat for a single instruction.
 
So now, how to access memory? With parenthesesbrackets! To access memory address $CD38, you just have to use ([$CD38)]. Yay!
 
To access the memory location pointed to by HL, just do... ([hl)]! It's the same with BC and DE.
 
So, to retrieve the value at memory address $56116511 into register A : ''ld a, ([$6511)]''
 
And to store the value of register C into the memory pointed to by HL : ''ld ([hl)], c''
 
Remember to refer to the chart above for the legal LD combinations.
 
Obviously, ''ld ([$6511)], a'' will overwrite the previous value stored here. But ''ld ([$6511)], hl'' will store a 16-bit value, which is a word long, that is two bytes long! So, not only will ([$6511)] be overwritten, but ([$6512)] too! Always be very careful about the memory you're touching. Otherwise, stuff like the [[ZZAZZ glitch]] happen.
 
For those wondering, ''ld a, ($6511)'' leaves ($6511) untouched.
 
For those wondering, ''ld a, [$6511]'' leaves [$6511] untouched.
 
==Flags==
Line 472 ⟶ 465:
|7||6||5||4||3||2||1||0
|-
|S||Z||-N||H||C||-||P/V-||N-||C-
|}
BothAll "-" are unused flags. TheirThey behaviorare isset very complicated, and they aren't "official".to Treat0 themat asall randomtimes.
 
===S : Sign===
If the accumulator is negative (from a signed perspective), the flag is set. You can also view it as a copy of the accumulator's 7th bit.
 
===Z : Zero===
Line 484 ⟶ 474:
===H : Half-Carry===
Works like the Carry flag, but referring to the least-significant ''nibble''. It is only used with the DAA instruction, so... forget it until then.
 
===P/V : Parity/Overflow===
This flag's meaning depends on the last operation.
 
If it means parity, it is set if the number of 1 in the accumulator is even. If it is odd, then it is reset.
 
If it means overflow, it is set if the last operation caused the accumulator's sign to change.
 
===N : Add/Subtract===
Line 501 ⟶ 484:
* SCF sets it, and
* CCF inverts it.
 
 
 
==Manipulating data==
Line 510 ⟶ 491:
!Syntax
!Effect
!S
!Z
!P/V
!C
|-
|INC <nowiki>{reg8 | reg16 | ([hl)]}</nowiki>
|Adds one to the operand ("increments" it)
|Affected, except for reg16
|Affected, except for reg16
|Detects overflow, except for reg16
|Not affected
|-
|DEC <nowiki>{reg8 | reg16 | ([hl)]}</nowiki>
|Subtracts one to the operand ("decrements" it)
|Affected, except for reg16
|Affected, except for reg16
|Detects overflow, except for reg16
|Not affected
|-
|ADD A, <nowiki>{reg8 | reg16imm8 | ([hl)]}</nowiki>
|Adds the operand to the accumulator
|Affected
|Affected
|Detects overflow
|Not affected
|-
Line 539 ⟶ 512:
|Adds the operand to HL
|Affected
|Affected
|Detects overflow
|Not affected
|-
|SUB A, <nowiki>{reg8 | reg16imm8 | ([hl)]}</nowiki>
|Subtracts the operand from the accumulator. The syntax SUB A, <nowiki>{...}</nowiki> is also valid but less common.
|Affected
|Affected
|Detects overflow
|Not affected
|-
|SBC HL, reg16
|Subtracts the operand plus the carry flag from HL
|Affected
|Affected
|Detects overflow
|Not affected
|}
Line 563 ⟶ 525:
A : Nowhere :D To multiply, you must write your own routines! However, a nice lil' trick : to do A <- A*2, simply ''add a, a''! To do A <- A*3, do ''ld b, a'', ''add a, a'', ''add a, b'' (you can swap B with any other register, of course). I'll leave you A <- A*4, A*5, A*6 and A*7 as an exercise.
 
For the rest of the tutorial, you'll see some text prefixed by a ";". These are comments, and are NOT part of the code. This line : "ld ([hl)], a ; Store the mon's ID" will be interpreted as "ld ([hl)], a". Everything following a ";" is ignored.
 
Also, the Game Boy's CPU as four very specific instructions :
{| class="wikitable"
{|
|ld ([hli)], a
|Equivalent to ''ld ([hl)], a'' then ''inc hl''.
|-
|ld ([hld)], a
|Equivalent to ''ld ([hl)], a'' then ''dec hl''.
|-
|ld a, ([hli)]
|Equivalent to ''ld a, ([hl)]'' then ''inc hl''.
|-
|ld a, ([hld)]
|Equivalent to ''ld a, ([hl)]'' then ''dec hl''.
|}
These are often used to operate on cHunkschunks of memory.
 
 
===Overflow===
Line 591 ⟶ 552:
 
Thus, the result is A equals 66 = %01000010, and the C flag is set.
 
 
===Register pairs and RAM===
Line 598 ⟶ 558:
Because two hex digits mean one byte, $D3, as well as $61, is a byte. Since $D3 and H are leftmost in both cases, ld hl, $D361 is actually a shorter form of ld h, $D3 then ld l, $61.
 
Let's say the following instruction is ld ([$2315)], hl. Applying the same logic would mean H's value would be stored at ([$2315)], and L's would be at ([$2316)]. However, you just lost THE GAME.; Becausebecause the z80 is a "little-endian" processor, L's value (the "little-end") is stored first, at ([$2315)]. So ([$2315)] is $61, and ([$2316)] is $D3.
 
Stop here, and remember this until it becomes natural to you. Because this "little-endian"ness is very tricky for beginners. It is ''very'' important when working with memory.
 
Here is an exercise : what values will ([$C000)] to ([$C00F)] contain after this code is ran?
 
Initial values :
{| class="wikitable"
|$C000||$C001||$C002||$C003||$C0004C004||$C005||$C006||$C007||$C008||$C009||$C00A||$C00B||$C00C||$C00D||$C00E||$C00F
|-
|$00||$03||$4F||$C0||$DE||$57||$2A||$00||$FF||$01||$23||$34||$56||$78||$9A||$BC
Line 613 ⟶ 573:
<pre>
ld hl, $C303
ld a, ([$C001)]
ld b, 3
add a, b
ld c, 0
sbc hl, bc
ld ([hl)], a
inc hl
ld b, ([hl)]
sub a, b
inc ([hl)]
inc hl
ld ([hl)], b
ld bc, 9
add hl, bc
ld ([hl)], a
ld ([$C00B)], hl
</pre>
 
 
==Stacks==
===A stack? Can you eat that?===
No it'syou notcan't. It's a data structure that has the cool property of not being fixed-length. How does it work? Just like a stack of plates. Imagine you're washing some plates in the back of a restaurant. Next to you is a pile of plates you need to wash. Waiters come and place ("push") plates on top of the stack and when finished washing a plate, you take ("pop") the topmost one. This way of working is called LIFO (Last In First Out).
 
In our case, we will do it by saving the top of the stack as a memory address. This value is called the ''stack pointer''. Here is an example, with the stack growing to the right :
Line 668 ⟶ 627:
|$00||$03||$4F||$C0||$7C||$2A||??||??||??||??||??||??||??||??||??||??
|}
 
 
===Coding a stack===
Line 675 ⟶ 633:
To push register DE :
<pre>
ld hl, ([$C000)] ; Retrieve stack pointer
ld ([hl)], e ; Push the low-order byte
inc hl ; Move stack pointer
ld ([hl)], d ; Repeat
inc hl
ld ([$C000)], hl ; Save stack pointer
</pre>
To pop into register DE :
<pre>
ld hl, ([$C000)] ; Retrieve stack pointer
dec hl ; Move stack pointer
ld d, ([hl)] ; Pop the high-order byte
dec hl ; Repeat
ld e, ([hl)]
ld ([$C000)], hl ; Save stack pointer
</pre>
 
 
===Good news===
Line 716 ⟶ 673:
where reg16 is any 16-bit register pair. AF can be used here.
 
Also meet SP, which makes all of this possible. SP is the '''hardware Stack Pointer'''. You can INC and DEC it, and you can't use it as a source in LD. Here are equivalents of ''push hl'' and ''pop hl'' (assuming we could use ([sp)], 'cause we can't :3)
{| class="wikitable"
|PUSH HL
|<pre>
dec sp
ld ([sp)], h
dec sp
ld ([sp)], l
</pre>
|-
|POP HL
|<pre>
ld l, ([sp)]
inc sp
ld h, ([sp)]
inc sp
</pre>
Line 739 ⟶ 696:
<pre>
push af
ld a, ([$C000)]
pop de
</pre>
Line 745 ⟶ 702:
 
Beware with the stack, even more when you're not coding your own game : everyone uses the stack ; even the CPU! (We're about to see how) The best practice to have is to leave the stack identical before and after your code. Otherwise, expect some crashes, yay!
 
 
==Control structures==
Line 769 ⟶ 725:
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 794 ⟶ 761:
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 838 ⟶ 1,048:
</pre>
|}
 
 
===Register pair and RAM===
Line 845 ⟶ 1,054:
ld hl, $C303 ; Now H = $C3 and L = $03
 
ld a, ([$C001)] ; A = $03
 
ld b, 3 ; B = $03
Line 855 ⟶ 1,064:
sbc hl, bc ; HL = HL - (BC + C flag) = $C303 - ($0300 + $00) = $C003
 
ld ([hl)], a ; ([HL)] = ([$C003)] <- A = $06
 
inc hl ; HL = $C004
 
ld b, ([hl)] ; B = ([HL)] = ([$C004)] = $DE
 
sub a, b ; A = A - B = $06 - $DE = $06 + (-$DE) = $06 + ($21 + $01) = $28, C flag = 0
Line 865 ⟶ 1,074:
Notice here that doing ''sub a, b'' actually increased A's value!
 
inc ([hl)] ; ([HL)] = ([$C004)] = $DF
 
inc hl ; HL = $C005
 
ld ([hl)], b ; ([HL)] = B = $DE
 
ld bc, 9 ; B = $00, C = $09
Line 875 ⟶ 1,084:
add hl, bc ; HL = HL + BC = $C005 + $0009 = $C00E
 
ld ([hl)], a ; ([HL)] = ([$C00E)] = A = $28
 
ld ([$C00B)], hl ; ([$C00B)] = L = $0E, and ([$C00C)] = H = $C0
 
Initial values :
Line 892 ⟶ 1,101:
|}
 
===Chaining conditionals===
You think you gotcha ? I'll be explaining the code, don't'cha worry :)
<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 !
==Credits==
 
Tutorial written by ISSOtm for Glitch City Laboratories.
"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.
 
The [http://marc.rawer.de/Gameboy/Docs/GBCPU_Instr.html GCISheet] is useful for understanding CPU instructions and can be combined with [https://iimarckus.org/etc/asmopcodes.txt IIMarckus's opcode to instruction page] or the copy on [[The Big HEX List]].
 
[https://tcrf.net/Help:Contents/Finding_Content/Debugger_guide/BGB Torchickens has started a tutorial for BGB emulator's debugger at The Cutting Room Floor]
 
[[Category:Arbitrary code execution]]
[[Category:Reference documents]]
Cookies help us deliver our services. By using our services, you agree to our use of cookies.