mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 17:06:04 +00:00
191 lines
8.6 KiB
Markdown
191 lines
8.6 KiB
Markdown
|
|
# NES Hardware Quick Reference
|
||
|
|
|
||
|
|
A concise reference to the NES hardware for NEScript contributors. Understanding these constraints explains many of the compiler's design decisions.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## CPU: Ricoh 2A03 (MOS 6502 variant)
|
||
|
|
|
||
|
|
- **Clock speed:** 1.79 MHz (NTSC), 1.66 MHz (PAL)
|
||
|
|
- **Architecture:** 8-bit data bus, 16-bit address bus
|
||
|
|
- **Registers:**
|
||
|
|
- `A` (Accumulator) -- 8-bit, used for arithmetic and logic
|
||
|
|
- `X` (Index X) -- 8-bit, used for indexing and counting
|
||
|
|
- `Y` (Index Y) -- 8-bit, used for indexing and counting
|
||
|
|
- `SP` (Stack Pointer) -- 8-bit, points into the $0100-$01FF range
|
||
|
|
- `P` (Status) -- 8-bit flags: N(egative), V(overflow), B(reak), D(ecimal), I(nterrupt), Z(ero), C(arry)
|
||
|
|
- `PC` (Program Counter) -- 16-bit
|
||
|
|
- **No multiply, divide, or floating point instructions**
|
||
|
|
- **Instruction set:** 56 official opcodes with multiple addressing modes (implied, immediate, zero page, zero page X/Y, absolute, absolute X/Y, indirect, indexed indirect, indirect indexed)
|
||
|
|
|
||
|
|
### Why This Matters for NEScript
|
||
|
|
|
||
|
|
The 6502's 3-register architecture drives the compiler's register allocator. The lack of multiply/divide means the compiler emits software routines for `*`, `/`, and `%`. Zero-page addressing is 1 byte shorter and 1 cycle faster than absolute addressing, which is why `fast` variable placement matters.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Memory Map
|
||
|
|
|
||
|
|
| Address Range | Size | Description |
|
||
|
|
|-------------------|------------|-------------------------------------|
|
||
|
|
| `$0000`-`$00FF` | 256 bytes | Zero page -- fast access |
|
||
|
|
| `$0100`-`$01FF` | 256 bytes | Stack (grows downward) |
|
||
|
|
| `$0200`-`$02FF` | 256 bytes | OAM shadow buffer (convention) |
|
||
|
|
| `$0300`-`$07FF` | 1280 bytes | General purpose RAM |
|
||
|
|
| `$0800`-`$1FFF` | -- | Mirrors of $0000-$07FF |
|
||
|
|
| `$2000`-`$2007` | 8 bytes | PPU registers |
|
||
|
|
| `$2008`-`$3FFF` | -- | Mirrors of PPU registers |
|
||
|
|
| `$4000`-`$4017` | 24 bytes | APU and I/O registers |
|
||
|
|
| `$4018`-`$401F` | 8 bytes | CPU test mode (normally disabled) |
|
||
|
|
| `$4020`-`$5FFF` | -- | Expansion ROM (mapper-dependent) |
|
||
|
|
| `$6000`-`$7FFF` | 8 KB | SRAM / PRG RAM (if present) |
|
||
|
|
| `$8000`-`$BFFF` | 16 KB | PRG ROM lower bank |
|
||
|
|
| `$C000`-`$FFFF` | 16 KB | PRG ROM upper bank (or fixed bank) |
|
||
|
|
|
||
|
|
### Interrupt Vectors
|
||
|
|
|
||
|
|
| Address | Vector | Description |
|
||
|
|
|----------|---------|------------------------------------------|
|
||
|
|
| `$FFFA` | NMI | Non-Maskable Interrupt (vertical blank) |
|
||
|
|
| `$FFFC` | RESET | Power-on / reset entry point |
|
||
|
|
| `$FFFE` | IRQ | Interrupt Request (mapper-dependent) |
|
||
|
|
|
||
|
|
### NEScript Memory Usage
|
||
|
|
|
||
|
|
The compiler reserves the following:
|
||
|
|
- `$00`-`$0F`: System use (frame counter, temp registers, NMI flags)
|
||
|
|
- `$10`-`$FF`: Available for `fast` variables and compiler-promoted variables
|
||
|
|
- `$0200`-`$02FF`: OAM shadow buffer (DMA'd to PPU each frame)
|
||
|
|
- `$0300`-`$07FF`: General variables, state-local storage
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## PPU (Picture Processing Unit)
|
||
|
|
|
||
|
|
- **Resolution:** 256 x 240 pixels
|
||
|
|
- **Refresh rate:** 60 Hz (NTSC), 50 Hz (PAL)
|
||
|
|
- **VRAM:** 2 KB internal (2 nametables), expandable by mapper
|
||
|
|
- **Pattern tables:** 2 tables of 256 tiles each (one for backgrounds, one for sprites), stored in CHR ROM/RAM
|
||
|
|
- **Tile size:** 8x8 pixels (or 8x16 for tall sprites)
|
||
|
|
- **Color depth:** 2 bits per pixel (4 colors per tile, selected from a sub-palette)
|
||
|
|
|
||
|
|
### Nametables
|
||
|
|
|
||
|
|
- 4 logical nametables, each 960 bytes of tile indices + 64 bytes of attribute data
|
||
|
|
- With 2 KB VRAM, 2 physical nametables exist; the other 2 are mirrors
|
||
|
|
- Mirroring arrangement (horizontal or vertical) is set by the cartridge/mapper
|
||
|
|
|
||
|
|
### Sprites (OAM)
|
||
|
|
|
||
|
|
- **64 sprites** total, each defined by 4 bytes:
|
||
|
|
- Byte 0: Y position
|
||
|
|
- Byte 1: Tile index
|
||
|
|
- Byte 2: Attributes (palette, priority, flip H/V)
|
||
|
|
- Byte 3: X position
|
||
|
|
- **8 sprites per scanline** maximum (excess sprites are dropped)
|
||
|
|
- OAM is 256 bytes, typically shadow-buffered at CPU `$0200` and DMA'd via `$4014`
|
||
|
|
|
||
|
|
### Palettes
|
||
|
|
|
||
|
|
- **Background:** 4 sub-palettes, each with 4 colors (first color shared across all)
|
||
|
|
- **Sprite:** 4 sub-palettes, each with 4 colors (first color is transparent)
|
||
|
|
- Colors are indices into the NES master palette (64 entries, `$00`-`$3F`)
|
||
|
|
- Palette RAM is 32 bytes total at PPU `$3F00`-`$3F1F`
|
||
|
|
|
||
|
|
### PPU Registers
|
||
|
|
|
||
|
|
| Address | Name | Description |
|
||
|
|
|---------|----------|--------------------------------------|
|
||
|
|
| `$2000` | PPUCTRL | NMI enable, sprite size, pattern table select, nametable select |
|
||
|
|
| `$2001` | PPUMASK | Color emphasis, sprite/background enable, clipping |
|
||
|
|
| `$2002` | PPUSTATUS| Vblank flag, sprite 0 hit, sprite overflow |
|
||
|
|
| `$2003` | OAMADDR | OAM address for writes |
|
||
|
|
| `$2004` | OAMDATA | OAM data read/write |
|
||
|
|
| `$2005` | PPUSCROLL| Scroll position (write twice: X, Y) |
|
||
|
|
| `$2006` | PPUADDR | VRAM address (write twice: high, low)|
|
||
|
|
| `$2007` | PPUDATA | VRAM data read/write |
|
||
|
|
|
||
|
|
### Rendering Timing
|
||
|
|
|
||
|
|
- **Vblank** starts at scanline 241 and lasts ~20 scanlines (~2,273 CPU cycles)
|
||
|
|
- PPU updates (palette, nametable, scroll) must happen during vblank
|
||
|
|
- `on frame` code runs during the visible frame; the implicit `wait_frame()` yields until the next vblank
|
||
|
|
- Approximately **29,780 CPU cycles per frame** (NTSC)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## APU (Audio Processing Unit)
|
||
|
|
|
||
|
|
| Channel | Type | Description |
|
||
|
|
|-----------|------------|------------------------------------|
|
||
|
|
| Pulse 1 | Square wave| Variable duty cycle (12.5/25/50/75%) |
|
||
|
|
| Pulse 2 | Square wave| Same as Pulse 1 |
|
||
|
|
| Triangle | Triangle | Fixed volume, good for bass |
|
||
|
|
| Noise | Noise | Pseudo-random, for percussion |
|
||
|
|
| DMC | Sample | 1-bit delta-modulated samples |
|
||
|
|
|
||
|
|
APU registers span `$4000`-`$4017`. The audio driver (included automatically when `sfx` or `music` declarations exist) runs during NMI.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## iNES ROM Format
|
||
|
|
|
||
|
|
The standard ROM file format for NES emulators:
|
||
|
|
|
||
|
|
```
|
||
|
|
Offset Size Description
|
||
|
|
------ ------- -----------
|
||
|
|
0 4 bytes Magic number: "NES" followed by $1A
|
||
|
|
4 1 byte PRG ROM size in 16 KB units
|
||
|
|
5 1 byte CHR ROM size in 8 KB units
|
||
|
|
6 1 byte Flags 6: mapper (low nibble), mirroring, battery, trainer
|
||
|
|
7 1 byte Flags 7: mapper (high nibble), VS/Playchoice, NES 2.0
|
||
|
|
8-15 8 bytes Padding (zeros)
|
||
|
|
16+ varies PRG ROM data (N x 16384 bytes)
|
||
|
|
after varies CHR ROM data (N x 8192 bytes)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Common Mapper Numbers
|
||
|
|
|
||
|
|
| Number | Name | PRG ROM | CHR | Notes |
|
||
|
|
|--------|--------|-------------|-------------|----------------------------|
|
||
|
|
| 0 | NROM | 16/32 KB | 8 KB ROM | No bank switching |
|
||
|
|
| 1 | MMC1 | Up to 256 KB| Up to 128 KB| Switchable 16 KB PRG banks |
|
||
|
|
| 2 | UxROM | Up to 256 KB| 8 KB RAM | Switchable 16 KB PRG banks |
|
||
|
|
| 4 | MMC3 | Up to 512 KB| Up to 256 KB| Scanline counter, 8 KB banks|
|
||
|
|
|
||
|
|
### Mirroring
|
||
|
|
|
||
|
|
- **Horizontal:** nametables A-A-B-B (vertical scrolling games)
|
||
|
|
- **Vertical:** nametables A-B-A-B (horizontal scrolling games)
|
||
|
|
- Set in byte 6, bit 0 of the iNES header (0 = horizontal, 1 = vertical)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Controller
|
||
|
|
|
||
|
|
Each controller has 8 buttons read as a serial shift register via `$4016` (port 1) and `$4017` (port 2).
|
||
|
|
|
||
|
|
Read sequence:
|
||
|
|
1. Write `$01` then `$00` to `$4016` to strobe (latch button states)
|
||
|
|
2. Read `$4016` (or `$4017`) 8 times; bit 0 of each read is one button
|
||
|
|
|
||
|
|
Button order: A, B, Select, Start, Up, Down, Left, Right.
|
||
|
|
|
||
|
|
The NEScript runtime handles this automatically. The programmer reads button state via `button.a`, `button.up`, etc.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Key Constraints for NEScript
|
||
|
|
|
||
|
|
| Constraint | Limit | NEScript Response |
|
||
|
|
|-------------------------|----------------|------------------------------------------|
|
||
|
|
| Total RAM | 2 KB | Static allocation, no heap |
|
||
|
|
| Zero page | 256 bytes | `fast`/`slow` hints, compiler promotion |
|
||
|
|
| Stack | 256 bytes | Call depth limit, no recursion |
|
||
|
|
| Sprites per frame | 64 | Compiler manages OAM buffer |
|
||
|
|
| Sprites per scanline | 8 | Hardware limit, no workaround |
|
||
|
|
| Vblank time | ~2,273 cycles | PPU updates must be fast |
|
||
|
|
| Frame budget | ~29,780 cycles | Frame overrun detection in debug mode |
|
||
|
|
| No multiply/divide HW | -- | Software routines, power-of-2 optimization|
|