aboutsummaryrefslogtreecommitdiff
path: root/ppu/ppu_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'ppu/ppu_test.go')
-rw-r--r--ppu/ppu_test.go108
1 files changed, 108 insertions, 0 deletions
diff --git a/ppu/ppu_test.go b/ppu/ppu_test.go
new file mode 100644
index 0000000..9426031
--- /dev/null
+++ b/ppu/ppu_test.go
@@ -0,0 +1,108 @@
+package ppu
+
+// more tests for the scroll registers since they can't be checked outside
+// the package without a $2006 write that would mess up the bits under test
+
+import (
+ "testing"
+
+ "crecher.net/gones/v2/cart"
+)
+
+// nilMapper is enough for these tests
+type nilMapper struct{}
+
+func (nilMapper) CPURead(addr uint16) uint8 { return 0 }
+func (nilMapper) CPUWrite(addr uint16, v uint8) {}
+func (nilMapper) PPURead(addr uint16) uint8 { return 0 }
+func (nilMapper) PPUWrite(addr uint16, v uint8) {}
+func (nilMapper) Mirroring() cart.Mirroring { return cart.Horizontal }
+
+func newPPU() *PPU { return New(nilMapper{}, cart.Horizontal) }
+
+func TestCtrlNametableBitsSetT(t *testing.T) {
+ p := newPPU()
+ p.WriteRegister(0x2000, 0x03) // nametable select = 3 -> t bits 10-11
+ if got := p.t & 0x0C00; got != 0x0C00 {
+ t.Errorf("t&0x0C00 = %#04x, want 0x0C00", got)
+ }
+
+ p.WriteRegister(0x2000, 0x00)
+ if got := p.t & 0x0C00; got != 0 {
+ t.Errorf("t&0x0C00 = %#04x, want 0 after clearing nametable select", got)
+ }
+}
+
+func TestScrollFirstWriteSetsCoarseXAndFineX(t *testing.T) {
+ p := newPPU()
+ p.WriteRegister(0x2005, 0b11101_011) // coarseX=0b11101=29, fineX=0b011=3
+
+ if got := p.t & 0x001F; got != 29 {
+ t.Errorf("coarse X in t = %d, want 29", got)
+ }
+ if p.x != 3 {
+ t.Errorf("fine X = %d, want 3", p.x)
+ }
+ if !p.w {
+ t.Error("w should be set after the first $2005 write")
+ }
+}
+
+func TestScrollSecondWriteSetsCoarseYAndFineY(t *testing.T) {
+ p := newPPU()
+ p.WriteRegister(0x2005, 0x00) // first write, establishes w=true
+ p.WriteRegister(0x2005, 0b10110_101) // coarseY=0b10110=22, fineY=0b101=5
+
+ if got := (p.t >> 5) & 0x1F; got != 22 {
+ t.Errorf("coarse Y in t = %d, want 22", got)
+ }
+ if got := (p.t >> 12) & 0x7; got != 5 {
+ t.Errorf("fine Y in t = %d, want 5", got)
+ }
+ if p.w {
+ t.Error("w should be cleared after the second $2005 write")
+ }
+}
+
+func TestScrollWriteDoesNotTouchNametableBits(t *testing.T) {
+ p := newPPU()
+ p.WriteRegister(0x2000, 0x03) // select nametable 3
+ p.WriteRegister(0x2005, 0xFF)
+ p.WriteRegister(0x2005, 0xFF)
+
+ if got := p.t & 0x0C00; got != 0x0C00 {
+ t.Errorf("PPUSCROLL writes clobbered the nametable-select bits: t&0x0C00 = %#04x, want 0x0C00", got)
+ }
+}
+
+func TestAddrWriteSequenceSetsTThenV(t *testing.T) {
+ p := newPPU()
+ p.v = 0x1234 // poison v so we can tell the first write didn't touch it
+
+ p.WriteRegister(0x2006, 0x3D) // high byte: only the low 6 bits are kept, bit 14 cleared
+ if got := p.t & 0xFF00; got != 0x3D00 {
+ t.Errorf("t high byte = %#04x, want 0x3D00", got)
+ }
+ if p.v != 0x1234 {
+ t.Errorf("v changed after the first $2006 write: %#04x", p.v)
+ }
+
+ p.WriteRegister(0x2006, 0x56)
+ if p.t != 0x3D56 {
+ t.Errorf("t = %#04x, want 0x3D56", p.t)
+ }
+ if p.v != 0x3D56 {
+ t.Errorf("v = %#04x, want 0x3D56 (should be copied from t on the second write)", p.v)
+ }
+}
+
+func TestAddrHighByteClearsBit14(t *testing.T) {
+ p := newPPU()
+ p.WriteRegister(0x2006, 0xFF) // top 2 bits of the written byte are discarded
+ if p.t&0x4000 != 0 {
+ t.Errorf("t bit 14 should always be cleared by a $2006 high-byte write, t = %#04x", p.t)
+ }
+ if got := p.t & 0xFF00; got != 0x3F00 {
+ t.Errorf("t high byte = %#04x, want 0x3F00 (0xFF masked to 6 bits)", got)
+ }
+}