A Nintendo Entertainment System emulator in pure MoonBit: 6502 CPU, PPU, frame-counter APU and the NROM/UxROM/CNROM/MMC1/MMC3 mappers, verified line by line against the nestest golden trace.
addressing.mbt operand decoding; page-crossing penalties are computed here
ops.mbt what an instruction does to registers and flags
isa.mbt the opcode table; each arm fixes the addressing mode and the costmoon testmoon run cmd/main -- --inspect internal/fixtures/nestest.nesfile internal/fixtures/nestest.nes
mapper NROM
mirroring horizontal
program 1 x 16 KiB (4000 bytes)
character 1 x 8 KiB (2000 bytes)
battery false
reset C004moon run cmd/main -- --run internal/fixtures/nestest.nes --frames 2 --write-png frame.pngframes 2
cycles 59567
pc C5F4 a=00 x=00 y=00 sp=FA
wrote frame.png (184638 bytes)| Capability | Status |
|---|---|
| All 151 official 6502 opcodes with data-sheet cycle counts | done |
| Unofficial opcodes: LAX, SAX, SLO, RLA, SRE, RRA, DCP, ISC, ANC, ALR, ARR, SBX, multi-byte NOPs | done |
| Page-crossing penalties on abs,X / abs,Y / (zp),Y reads | done |
| Branch penalties including the page-crossing extra | done |
| The JMP ($xxFF) page-boundary bug | done |
| BRK / RTI / JSR / RTS stack behaviour | done |
| BIT as a two-bit branch on one memory read | done |
| Golden-log trace comparison against nestest.log | all 8991 lines |
| iNES header parsing, including the twelve-bit bank counts | done |
| NROM cartridge, 16 KiB mirroring into both halves of the map | done |
| UxROM (mapper 2): switchable low window, last bank fixed at $C000 | done |
| CNROM (mapper 3): switchable character bank | done |
| MMC1 (mapper 1): serial shift register, four bank arrangements, run-time mirroring | done |
| MMC3 (mapper 4): two switchable 8 KiB windows, 1 KiB character banking, scanline IRQ | done |
| PPU registers, read buffer, scroll latch, palette mirrors | done |
| PPU: per-pixel background and sprite rendering | done |
| Sprite DMA through $4014 with the odd-cycle stall | done |
| Controller shift registers in hardware order | done |
| Four-screen mirroring with its own 4 KiB of nametable RAM | done |
| Sprite-zero hit: opaque sprite pixel over opaque background pixel | done |
| Mappers: MMC5, VRC, and the discrete boards (AxROM, GxROM, MMC2) | planned |
| APU frame counter: 4-step and 5-step sequences, frame IRQ | done |
| APU channel status and length counters, as $4015 reports them | done |
| APU: waveform synthesis and mixing | planned |
| Sprite overflow flag | approximate |
| PNG output of a finished frame, written out without a dependency | done |
nescope.mbt errors, status flags, buttons, region timings
cpu.mbt register file, bus trait, fetch/step, stack
addressing.mbt the addressing modes and their page penalties
ops.mbt operations: loads, ALU, shifts, compare, branch, jump
isa.mbt the opcode table
cart.mbt iNES parsing and the cartridge's memories
mapper.mbt bank switching: NROM, UxROM, CNROM, MMC1
ppu.mbt registers, rendering, sprites, palette and mirroring
png.mbt palette to RGB, and a dependency-free PNG writer
apu.mbt the frame counter and channel status registers
bus.mbt the console: address decoding, DMA, controllers
cmd/main/ the CLItype PaletteIndex = UIntpub(open) trait Bus {
fn read(self : Self, address : UInt) -> UInt
fn write(self : Self, address : UInt, value : UInt) -> Unit
}pub(all) suberror NesError {
UnexpectedEnd(offset~ : Int64, needed~ : Int64, available~ : Int64)
NotAnINes
BadValue(field~ : String, value~ : Int64)
Truncated(kind~ : String, declared~ : Int64, present~ : Int64)
UnsupportedMapper(number~ : UInt)
UnknownOpcode(opcode~ : UInt, at~ : UInt)
LockoutChip
}pub(all) struct Apu {
mode : FrameMode
irq_inhibit : Bool
frame_irq : Bool
cycles : Int64
step : Int
length : FixedArray[UInt]
halt : FixedArray[Bool]
control : FixedArray[UInt]
dmc_active : Bool
enabled_channels : UInt
}pub(all) enum Button {
A
B
Select
Start
Up
Down
Left
Right
}pub(all) struct Cart {
mirroring : Mirroring
has_battery : Bool
prg : Bytes
chr : FixedArray[Byte]
chr_ram : Bool
mapper : MapperKind
prg_banks : Int64
chr_banks : Int64
prg_bank : Int64
prg_bank_high : Int64
chr_bank : Int64
shift : UInt
shift_count : UInt
control : UInt
mirroring_from_mapper : Bool
bank_registers : FixedArray[Int64]
bank_select : UInt
prg_mode : Bool
chr_mode : Bool
irq_counter : UInt
irq_latch : UInt
irq_line : Bool
irq_enabled : Bool
}pub(all) struct Cpu {
a : UInt
x : UInt
y : UInt
sp : UInt
pc : UInt
p : UInt
cycles : Int64
stalled : Int64
last_access : MemoryAccess?
}pub(all) struct Ctrl {
nametable : UInt
increment32 : Bool
sprite_pattern : UInt
background_pattern : UInt
tall_sprites : Bool
nmi_enabled : Bool
}pub(all) enum Flag {
Carry
Zero
Interrupt
Decimal
Break
Unused
Overflow
Negative
}pub(all) struct FlatRam {
bytes : FixedArray[Byte]
}pub(all) enum FrameMode {
FourStep
FiveStep
}pub(all) enum FrameStep {
Idle
Quarter
Half
Both
}pub(all) enum MapperKind {
Nrom
UxRom
Cnrom
Mmc1
Mmc3
Unsupported(UInt)
}pub(all) struct Mask {
show_background_left : Bool
show_sprites_left : Bool
show_background : Bool
show_sprites : Bool
emphasize_red : Bool
emphasize_green : Bool
emphasize_blue : Bool
greyscale : Bool
}pub(all) struct MemoryAccess {
address : UInt
value : UInt
write : Bool
}pub(all) enum Mirroring {
Horizontal
Vertical
SingleLow
SingleHigh
FourScreen
}pub(all) struct Ppu {
ctrl : Ctrl
mask : Mask
status : Status
v : UInt
t : UInt
fine_x : UInt
write_latch : Bool
nt_ram : FixedArray[Byte]
palette : FixedArray[Byte]
oam : FixedArray[Byte]
read_buffer : UInt
scanline : Int
dot : Int
frame : FixedArray[Byte]
frame_complete : Bool
mirroring : Mirroring
chr : FixedArray[Byte]
chr_ram : Bool
scanlines : UInt
chr_bank : Int64
chr_pages : FixedArray[Int64]?
oam_addr : UInt
}pub(all) enum Region {
NTSC
PAL
Dendy
}pub(all) struct Status {
vblank : Bool
sprite_zero_hit : Bool
overflow : Bool
}fn encode_png(frame : FixedArray[Byte]) -> Bytesfn hex2(value : UInt) -> Stringfn hex4(value : UInt) -> Stringfn hex8(value : UInt) -> Stringfn nes_colour(index : UInt) -> (UInt, UInt, UInt)Install
Download zipA Nintendo Entertainment System emulator in pure MoonBit: 6502 CPU, PPU, frame-counter APU and the NROM/UxROM/CNROM/MMC1/MMC3 mappers, verified line by line against the nestest golden trace.