aboutsummaryrefslogtreecommitdiff
path: root/basics
diff options
context:
space:
mode:
Diffstat (limited to 'basics')
-rw-r--r--basics/README.md1
-rw-r--r--basics/persist.s4
-rw-r--r--basics/unrom.s171
3 files changed, 176 insertions, 0 deletions
diff --git a/basics/README.md b/basics/README.md
index 7571509..3e4c3a9 100644
--- a/basics/README.md
+++ b/basics/README.md
@@ -13,3 +13,4 @@ Anyhow, these programs are as follows:
to get a detailed explanation on each section.
- `input.s`: how to read the input from one controller.
- `persist.s`: using the MMC1 chip in order to persist data.
+- `unrom.s`: bank switching using the UNROM chip.
diff --git a/basics/persist.s b/basics/persist.s
index 392797f..430654f 100644
--- a/basics/persist.s
+++ b/basics/persist.s
@@ -11,6 +11,10 @@
;; Last but not least, all of this is done thanks to the MMC1 chip. Thus, this
;; file is also a minimalistic example on how to configure it, even if it
;; doesn't take full advantage of it (e.g. we are not doing any bank switching).
+;; Because of this, note that we are not using a proper linker configuration for
+;; it (I'm using cc65's default, which is tailored for NROM chips). Thus, bank
+;; switching will actually never work under this setup. Bank switching is
+;; covered through other examples like `basics/unrom.s`.
.segment "HEADER"
.byte 'N', 'E', 'S', $1A
diff --git a/basics/unrom.s b/basics/unrom.s
new file mode 100644
index 0000000..c6e9fd8
--- /dev/null
+++ b/basics/unrom.s
@@ -0,0 +1,171 @@
+;;;
+;; This is an example on how you can perform bank switching. In order to do this
+;; I am using the UNROM chip (iNES mapper 2), which is one of the first chips to
+;; ever support this. Because of this, it's also one of the simplest, which is
+;; quite convenient in order to learn this technique.
+;;
+;; The program will do the following:
+;; - Switch to bank 0.
+;; - Call a function from bank 0 that will set $10 to #2.
+;; - Switch to bank 1.
+;; - Call a function from bank 1 that will set $11 to #3.
+;; - Sum the values from $10 and $11 and store them in $12.
+;;
+;; At this point there are two things you can do in order to check that the
+;; whole thing worked:
+;;
+;; 1. The values on $10, $11 and $12 are as expected (2, 3, and 5
+;; respectively).
+;; 2. The last bank we switched was 1, which has a fill value on linker
+;; configuration (see `config/unrom.cfg`) of `$f9`. Thus, check that except
+;; from the code on `hello_bank1`, the rest of the values on $8000-$BFFF
+;; are set to `$f9` (i.e. by using FCEUX's Debug -> Hex editor).
+
+.segment "HEADER"
+ .byte 'N', 'E', 'S', $1A
+ .byte $08 ; 128KB of PRG-ROM (8 x 16KB)
+ .byte $00 ; No CHR-ROM. Games using this chip used RAM instead.
+
+ ;; On the 6th byte we have to set the lower nibble of the mapper (#%0010 for
+ ;; UNROM).
+ .byte $20
+
+ ;; Forcing iNES 2.0 format, which will help us for the next bytes.
+ .byte $08
+
+ ;; And now iNES 2.0-specific thingies.
+ .byte $00 ; No submapper
+ .byte $00 ; PRG ROM not 4 MiB or larger
+ .byte $00 ; No PRG RAM
+ .byte $07 ; 8192 (64 * 2^7) bytes CHR RAM, no battery
+ .byte $00 ; NTSC; use $01 for PAL
+ .byte $00 ; No special PPU
+
+.segment "VECTORS"
+ .addr nmi, reset, irq
+
+;;;
+;; Note that we now define code on bank 0 and bank 1. These names are not magic:
+;; they are defined on the linker configuration being used (see
+;; `config/unrom.cfg`). All swappable banks are called `BANK#`. The bank that is
+;; not swappable is simply called `FIXED`, and that's the bank where most of the
+;; code from this example will reside.
+
+.segment "BANK0"
+
+hello_bank0:
+ lda #2
+ sta $10
+ rts
+
+.segment "BANK1"
+
+hello_bank1:
+ lda #3
+ sta $11
+ rts
+
+;;;
+;; The rest of the banks are simply not used by this example.
+
+.segment "BANK2"
+.segment "BANK3"
+.segment "BANK4"
+.segment "BANK5"
+.segment "BANK6"
+
+;;;
+;; And the following code is "fixed". That is, it is stored at $C000-$FFFF,
+;; which is not swappable. You can check this by using FCEUX's hex editor, for
+;; example.
+
+.segment "FIXED"
+
+;; This table contains the actual sauce of bank switching. In general, the UNROM
+;; does bank switching by writing to $8000-$BFFF, but the value we are writing
+;; there *must* match the value located at the destination address in ROM
+;; (otherwise we get a bus conflict). Writing to this table already ensures that
+;; this never happens because the indexing will match the actual value.
+banktable:
+ .byte $00, $01, $02, $03, $04, $05, $06
+
+;; Variable containing the bank we are currently in. It's useful to keep track
+;; of the bank so the NMI handler can restore it if it does some bank switching
+;; of its own.
+m_current_bank = $00
+
+;; Perform a bankswitch by using the value on the `y` register. Note that you
+;; can use the `bankswitch_nosave` variant, which is useful if you just want to
+;; perform a temporary bankswitch (e.g. on NMI code).
+bankswitch:
+ sty m_current_bank
+bankswitch_nosave:
+ tya
+ sta banktable, y
+ rts
+
+;; Unused
+nmi:
+irq:
+ rti
+
+;; Check `basics/sprite.s` for a deeper look on the logic below. I have only
+;; added code after configuration/reset is done.
+reset:
+ sei
+ cld
+ ldx #$40
+ stx $4017
+
+ ldx #$ff
+ txs
+
+ inx
+ stx $2000
+ stx $2001
+ stx $4010
+
+@vblankwait1:
+ bit $2002
+ bpl @vblankwait1
+
+ ldx #0
+ lda #0
+@ram_reset_loop:
+ sta $000, x
+ sta $100, x
+ sta $200, x
+ sta $300, x
+ sta $400, x
+ sta $500, x
+ sta $600, x
+ sta $700, x
+ inx
+ bne @ram_reset_loop
+
+@vblankwait2:
+ bit $2002
+ bpl @vblankwait2
+
+ ;;;
+ ;; NOTE: configuration/reset is done, the code below is our actual program :D
+
+ ;; Switch to bank 0 and call the function from there.
+ ldy #0
+ jsr bankswitch
+ jsr hello_bank0
+
+ ;; The same but on bank 1.
+ ldy #1
+ jsr bankswitch
+ jsr hello_bank1
+
+ ;; And now we can add the numbers that were saved by both banks.
+ lda $10
+ clc
+ adc $11
+ sta $12
+
+ ;; Loop forever, there's nothing to be done here.
+@loop:
+ jmp @loop