;;; hello world demo in gb-z80 ;;; by feeb ;;; published at http://pp.feeb.dog ;;; i. intro / motivation ;;; --------------------- ;;; programming the gameboy seemed like a fun way to get some hands-on ;;; experience writing assembly. my first goal was to create a minimal, working ;;; demonstration that contains the absolute basics for something non-trivial. ;;; two benefits of having something like this around: ;;; - functions as a personal reference for working assembler on the gb-z80 ;;; - (hopefully) a useful educational tool for others in my position who want ;;; to get started writing for the gameboy ;;; this is a simple program that displays a checkerboard on the screen, a ;;; process that involves a small number of simple interactions with the ;;; hardware. ;;; ii. materials ;;; ------------- ;;; this demonstration is intended to assemble under wla-dx, which may be ;;; available in your package manager, and is otherwise available here: ;;; http://www.villehelin.com/wla.html ;;; you can assemble this literate source file with the following procedure, ;;; which assumes this file is saved as main.s: ;;; wla-gb main.s ;;; wlalink Linkfile woof.gb ;;; where the contents of Linkfile are as follows: ;;; [objects] ;;; main.o ;;; the manpages for wla-dx(7), wla-gb(1), and wlalink(1) are great references. ;;; iii. assembler configuration ;;; ---------------------------- ;;; the following conveys the gameboy's memory layout to the assembler: .memorymap slotsize $4000 ; size of memory blocks defaultslot 0 ; slot we will be writing to slot 0 $0000 ; mapped to rom in hardware slot 1 $4000 ; mapped to ram .endme .rombanksize $4000 ; size of the rom bank .rombanks 2 ; this configuration is additionally written to ; 0x148. (wla-dx complains if this is not ; defined via a directive) ;;; this controls how the assembler will lay out the final rom image. this is ;;; largely determined by the actual physical constraints of the device, but ;;; there is some wiggle room in the cartridge architecture. for more details on ;;; this, look at the documentation for the memory location 0x147 in [1]. ;;; iv. rom configuration ;;; --------------------- ;;; the following section populates the rom with necessary configurations to ;;; get up and running successfully. it is important to note that the gb ;;; hardware checks two specific things and refuses to boot if they are invalid: ;;; - the nintendo logo (bits 0x104 -> 0x133, as specified below). ;;; - the checksum bits at 0x14D, 0x14E and 0x14F. .org $100 ; if validation is successful, execution starts ; at memory addrexx 0x100. _start: nop jr boot ; the nintendo logo is checked at 0x104, so we ; need to quickly jump. nintendo_logo: ;; gameboy hardware checks this memory location, and refuses to boot ;; if any of these bytes deviate. .org $104 .db $CE $ED $66 $66 $CC $0D $00 $0B $03 $73 $00 $83 $00 $0C $00 $0D .db $00 $08 $11 $1F $88 $89 $00 $0E $DC $CC $6E $E6 $DD $DD $D9 $99 .db $BB $BB $67 $63 $6E $0E $EC $CC $DD $DC $99 $9F $BB $B9 $33 $3E ;;; there is a lot of room for metadata at the beginning of the rom, so we ;;; configure that here. i believe that this configuration is generally ;;; unimportant except for being the basis of the checksum calculation, which ;;; will prevent boot if invalid. ;;; these are documented in great detail in section 2.5.4 of [1]. configuration: ;; title of game in uppercase ascii. this is located in 0x0134 ;; but this wla-dx specific directive makes this more legible .name "WOOF WOOF" .org $143 ; cartridge type .db $0 ; 0x00 = not a color gb .org $144 ; licensing code ; used in calculating checksum and complement .db $0 ; set to 0 by default .db $0 ; " .org $146 ; super gb function availability .db $0 ; 0x00 = no super gb function .org $147 ; cartridge architecture .DB $0 ; 0x00 = rom only .org $148 ; rom size .db $0 ; 0x00 = 256kbit .org $149 ; ram size .db $0 ; 0x00 none .org $14A ; destination code .db $1 ; 0x01 = non-japanese .org $14B ; old licensing code .db $0 ; dummy value .org $14C ; mask rom version number .db $0 ; typical value ;; gameboy will not boot the rom if the checksum bytes aren't set ;; correctly. these are wla-dx specific directives that will calculate ;; these bytes, which saves us some headache. .computechecksum .computecomplementcheck ;;; the following are the memory locations for a number of hardware interrupts. ;;; for our small demo, we wish to ignore these. these will be disabled with the ;;; `di` instruction in boot, but until then we wish to immediately return when ;;; an interrupt is called, so we do that here. at this point i will not spend ;;; documenting their behavior outside of how to disable them. .org $40 ; vertical blank interrupt reti .org $48 ; lcdc status interrupt reti .org $50 ; timer overflow interrupt reti .org $58 ; serial transfer completion interrupt reti .org $60 ; high to low of p10->p13 interrupt reti ;;; at this point, the gameboy has successfully booted and transfers control ;;; to our code. ;;; v. ram configuration ;;; -------------------- ;;; our goal is to display a checkerboard, so we are going to perform the ;;; following tasks: ;;; - configure the lcd screen ;;; - configure tile palettes ;;; - populate a checkerboard tile into the video ram ;;; - populate the background tile map display to repeat our checkered tile ;;; through the entire background ;;; a. graphics initialization ;;; -------------------------- .org $150 ; the first free byte after checksums boot: di ; disable interrupts nop ; wait for disabled interrupts to take effect ;; window/background display configuration (LCDC) ;; 7: lcd control (1 = lcd on) ;; 6: window tile map memory position select (0 = 0x9800 -> 0x9BFF) ;; 5: window display (0 = off) ;; 4: bg & window tile memory position select (1 = 0x8000 -> 0x8FFF) ;; 3: bg tile map display memory position select (1 = 0x9C00 -> 0x9FFF) ;; 2: object (aka sprite) size (0 = 8x8) ;; 1: object (aka sprite) display (0 = off) ;; 0: bg & window display (1 = on) ld hl, $FF40 ld (hl), %10011001 ;; palette configuration ld a, %11100100 ; default value (11 darkest, 00 lightest, etc) cpl ldh ($47), a ; palette for background tiles (BGP) ldh ($48), a ; palette 1 for sprites (OBP0) ldh ($49), a ; palette 2 for sprites (OBP1) call load_tile ; populate tile data call bg_display ; render background halt ; we're done, so stop nop ; halting while interrupts are disabled causes ; the instruction after the halt to be skipped, ; so making it a NOP is prudent. ;;; b. loading tiles ;;; ---------------- load_tile: ;; load representation of checkered tile into video ram. ;; [1] has good documentation of the tile memory format in section 2.5.4 ;; "Tiles". ;; this also serves as a demonstration of a primitive way to load a ;; number of bytes into video RAM. ld hl, $8000 ; set pointer to beginning of video ram ; (this should match the value specified in LCDC bit 4) ld bc, $2 ld (hl), $00cc add hl, bc ld (hl), $00cc add hl, bc ld (hl), $0033 add hl, bc ld (hl), $0033 add hl, bc ld (hl), $00cc add hl, bc ld (hl), $00cc add hl, bc ld (hl), $0033 add hl, bc ld (hl), $0033 ret ;;; c. displaying tiles ;;; ------------------- ;;; this is a slightly more complicated demonstration of assembly. ;;; the goal is to populate all memory locations from 0x9FFF down to 0x9C00 with ;;; the digit '0', which indexes our checkered tile. ;;; the instruction `cp` can only compare a byte at a time, and the combined ;;; register we are using for memory addressing (HL) is 2 bytes. so here we use ;;; two comparisons to make sure that write to the full span of memory locations. bg_display: ld h, $9F bg_d_loop: dec l ld (hl), $0 jr nz, bg_d_loop dec h ld a, $9B cp h jr nz, bg_d_loop ret ;;; and now you should have a cool checkered screen running on your ;;; gameboy/emulator, which should be ready for hacking in the rest of your cool ;;; demo. email me if i messed anything up. thanks for reading! ;;; vi. thanks / references ;;; ----------------------- ;;; i referred to this amazing reference constantly while writing this: ;;; [1] http://marc.rawer.de/Gameboy/Docs/GBCPUman.pdf ;;; greetz: ;;; tokenrove ;;; ntkvby ;;; recurse center