From 7b5d0c76e85495d078ef83cff153bc150cced7b1 Mon Sep 17 00:00:00 2001 From: Andrew Kesterson Date: Fri, 2 Nov 2012 22:17:59 -0400 Subject: [PATCH] Milestone 1 : Boot a rom that does nothing --- Makefile | 12 ++++++ nesgame.S | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 Makefile create mode 100644 nesgame.S diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..21e60d6 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +all: nesgame.nes +nesgame.nes : nesgame.S + NESASM3 $< + +.PHONY: test +test: + fceux nesgame.nes + +.PHONY: clean +clean: + rm *nes + rm *fns diff --git a/nesgame.S b/nesgame.S new file mode 100644 index 0000000..941bf3b --- /dev/null +++ b/nesgame.S @@ -0,0 +1,124 @@ + ;; All NES ROMs have a 16 byte header that describes how the ROM + ;; works; specifically, how many banks of 16kB PRG (program) code, + ;; how many 8kB banks of CHR data, which mapper to use for bank swapping + ;; and how to perform background mirroring + + .inesprg 1 ;; 1x 16kB bank of PRG (program) code + .ineschr 1 ;; 1x 8kB bank of CHR (tile/sprite) data + .inesmap 0 ;; use mapper 0; NROM, no bank swapping + .inesmir 1 ;; background mirroring (we don't care for now) + + ;; For NESASM, we need to tell it where each bank begins. + + .bank 0 + .org $C000 ;; PRG bank 1 has 8kB at 0x0C000 + + ;; START will be called by the NES whenever the system boots + ;; or when the reset button is pressed (think of _start in libc ) + ;; but the fact that the NES looks at "START" is only because we + ;; specified it in bank 1 at 0xFFFA, the vector table +START: + SEI ;; disable IRQs (we don't have an IRQ vector) + CLD ;; disable decimal mode (NES 6502 doesn't have + ;; a decimal mode, please don't produce decimal + ;; mode instructions, NESASM!) + LDX #$40 ;; load 0x40 into X register + STX $4017 ;; store what's in X to address 0x4017 ... + ;; 0x4017 is the Joystick 2 port?! WTF does this + ;; do?! + LDX #$FF + TXS ;; Move the contents of X to the stack pointer + INX ;; increment X by 1, which causes overflow, so + ;; now X is 0 + STX $2000 ;; set PPU flag to disable NMI (0x2000 = 0) + STX $2001 ;; set PPU flag to disable rendering (0x2001 = 0) + STX $4010 ;; disable APU IRQs, no audio +_START_vblankwait: + BIT $2002 ;; Bitwise AND the accumulator (LDA) with mem + ;; at 0x2002, and set the Zero, Sign and Overflow + ;; flags accordingly. 0x2002 is the PPU status + ;; register; when 0x2002 has bit 7 set, we are + ;; in vblank, so this is how we check for it. + BPL _START_vblankwait ;; Until the sign bit is set, loop here. Wait + ;; for vblank. + +_START_clearmem: + ;; Hey look, it's the longest memset() ever! + LDA #$00 + STA $0000, x ;; store 0 to (LDX) + 0x0000 ... but X should + ;; be 0 at this point (see START where we INX), + ;; so why aren't we just using zero-page + ;; addressing? + ;; ... that's what I thought at first, before + STA $0100, x ;; I realized that I'm looking at a loop: + STA $0200, x ;; + STA $0400, x ;; for ( x = 0; x < 256 ; x++) + STA $0500, x ;; *(0x0100 + x) = 0; + STA $0600, x ;; .... + STA $0700, x ;; the INX and BNE at the bottom are the "; x++)" + ;; This clears the zero page, the stack, and + ;; the entirety of main RAM + + LDA #$FE ;; It's also not clear at all what these two + STA $0300, x ;; are setting; maybe it's object attribute + ;; memory (OAM)? + + INX ;; X is already 0 so this should do X=1, + ;; and the Zero and Sign flags should both go 0 + BNE _START_clearmem ;; "; x++)", loop back to clrmem until X rolls + +_START_vblankwait2: + BIT $2002 ;; copy paste going to happen in ASM + BPL _START_vblankwait2 ;; once we've gotten 1 vblank, + ;; cleared mem, and gotten another vblank, + ;; the PPU is ready. Wait for it. + +MAIN: + ;; horray, here is main() + ;; all we do is set the PPU mask to intensify blues, and loop forever + + ;; The PPU mask is set at $2001, the 2nd PPU Control register, and it + ;; sets one config option for every bit of the byte + ;; + ;; 76543210 + ;; |||||||| + ;; |||||||+- Grayscale (0: normal color; 1: AND all palette entries + ;; ||||||| with 0x30, effectively producing a monochrome display; + ;; ||||||| note that colour emphasis STILL works when this is on!) + ;; ||||||+-- Disable background clipping in leftmost 8 pixels of screen + ;; |||||+--- Disable sprite clipping in leftmost 8 pixels of screen + ;; ||||+---- Enable background rendering + ;; |||+----- Enable sprite rendering + ;; ||+------ Intensify reds (and darken other colors) + ;; |+------- Intensify greens (and darken other colors) + ;; +-------- Intensify blues (and darken other colors) + + LDA #%10000000 ;; blue background! + STA $2001 ;; Write to PPU Control Register 2 +_MAIN_loop: + JMP _MAIN_loop ;; Loop forever and do nothing + +NMI: + RTI ; just return + + .bank 1 ;; NESASM sees our 16kB code banks as pairs of + .org $E000 ;; 8kB code banks, so we have to declare each + ;; 8kB half-bank separately, and split code + ;; between them. + ;; .. How to know when we have written enough + ;; code? .. + + ;; insert the rest of the bank 1 code here + + .bank 1 + .org $FFFA + .dw NMI ;; For Non-Maskable Interrupts, please jump to the location + ;; of the NMI label + .dw START ;; For the reset button or power-on, jump to the location + ;; of the START label + .dw 0 ;; If we used an external IRQ vector, we would put it here + + ;; --- graphics bank + + .bank 2 ;; CHR bank 0 starts here for tile/sprite data + .org $0000 ;; CHR data is below PRG data in the memory