test(nes): consolidate and simplify inline subsystem unit tests

This commit is contained in:
2026-08-27 07:33:54 +02:00
parent 754dbb9307
commit 71c747d962
23 changed files with 35 additions and 31 deletions
+1 -1
View File
@@ -108,7 +108,7 @@ pub fn output(self: *const Envelope) u4 {
self.decay_level; self.decay_level;
} }
// ponytail: consolidated envelope generator unit test suite // consolidated envelope generator unit test suite
test "envelope register write, quarter-frame clocking, decay and loop" { test "envelope register write, quarter-frame clocking, decay and loop" {
var envelope = Envelope.init(); var envelope = Envelope.init();
envelope.write(0b0010_1010); // loop=1, const_vol=0, vol=10 envelope.write(0b0010_1010); // loop=1, const_vol=0, vol=10
+2 -2
View File
@@ -212,13 +212,13 @@ pub fn irqAsserted(self: *const FrameCounter) bool {
} }
pub fn clearIrq(self: *FrameCounter) void { pub fn clearIrq(self: *FrameCounter) void {
// ponytail: simultaneous read and IRQ set does not clear flag // simultaneous read and IRQ set does not clear flag
if (!self.irq_just_set) { if (!self.irq_just_set) {
self.irq_flag = false; self.irq_flag = false;
} }
} }
// ponytail: consolidated frame counter unit test suite // consolidated frame counter unit test suite
test "frame counter 4-step and 5-step mode timings, irq window and write delays" { test "frame counter 4-step and 5-step mode timings, irq window and write delays" {
var fc = FrameCounter.init(.ntsc); var fc = FrameCounter.init(.ntsc);
+1 -1
View File
@@ -55,7 +55,7 @@ pub fn active(self: *const LengthCounter) bool {
return self.value != 0; return self.value != 0;
} }
// ponytail: consolidated length counter unit test suite // consolidated length counter unit test suite
test "length counter enable, loading, immediate clearing, clocking and halt" { test "length counter enable, loading, immediate clearing, clocking and halt" {
try std.testing.expectEqual(@as(u8, 10), table[0]); try std.testing.expectEqual(@as(u8, 10), table[0]);
+1 -1
View File
@@ -146,7 +146,7 @@ pub fn reset(self: *Noise) void {
self.length_counter.setEnabled(false); self.length_counter.setEnabled(false);
} }
// ponytail: consolidated noise unit test suite // consolidated noise unit test suite
test "noise channel control, period, length and LFSR shift register" { test "noise channel control, period, length and LFSR shift register" {
var noise = Noise.init(.ntsc); var noise = Noise.init(.ntsc);
noise.writeControl(0b0011_1010); noise.writeControl(0b0011_1010);
+1 -1
View File
@@ -253,7 +253,7 @@ pub fn sequencePosition(self: *const Pulse) u3 {
return self.sequence_position; return self.sequence_position;
} }
// ponytail: consolidated pulse unit test suite // consolidated pulse unit test suite
test "pulse channel register writes, timing, duty sequences and output gating" { test "pulse channel register writes, timing, duty sequences and output gating" {
var pulse = Pulse.init(.pulse1); var pulse = Pulse.init(.pulse1);
+3 -3
View File
@@ -68,7 +68,7 @@ pub fn init(region: Region) Apu {
} }
pub fn reset(self: *Apu) void { pub fn reset(self: *Apu) void {
// ponytail: reset silences all channels and clears APU state // reset silences all channels and clears APU state
self.writeStatus(0); self.writeStatus(0);
self.frame_counter.reset(); self.frame_counter.reset();
self.noise.reset(); self.noise.reset();
@@ -286,7 +286,7 @@ pub fn clockCpu(self: *Apu) void {
self.clockHalfFrame(); self.clockHalfFrame();
} }
// ponytail: audio downsampler (~40.58 CPU cycles per 44.1kHz sample) with 1st-order DC blocker // audio downsampler (~40.58 CPU cycles per 44.1kHz sample) with 1st-order DC blocker
self.sample_accumulator += self.output(); self.sample_accumulator += self.output();
self.sample_cycles += 1; self.sample_cycles += 1;
if (self.sample_cycles >= 40) { if (self.sample_cycles >= 40) {
@@ -463,7 +463,7 @@ pub fn output(self: *const Apu) f32 {
return pulse_out + tnd_out; return pulse_out + tnd_out;
} }
// ponytail: consolidated APU root test suite // consolidated APU root test suite
test "apu initialization, status reads, IRQ clearing, sample generation and DMC DMA" { test "apu initialization, status reads, IRQ clearing, sample generation and DMC DMA" {
var apu = Apu.init(.ntsc); var apu = Apu.init(.ntsc);
+1 -1
View File
@@ -143,7 +143,7 @@ pub fn clockHalfFrame(
} }
} }
// ponytail: consolidated sweep unit test suite // consolidated sweep unit test suite
test "sweep unit target calculation, gating and clocking" { test "sweep unit target calculation, gating and clocking" {
var sweep1 = Sweep.init(.pulse1); var sweep1 = Sweep.init(.pulse1);
sweep1.write(0b1010_0001); // P=2, shift=1 sweep1.write(0b1010_0001); // P=2, shift=1
+1 -1
View File
@@ -183,7 +183,7 @@ pub fn irqLine(self: *const Bus) bool {
return apu_irq or cart_irq; return apu_irq or cart_irq;
} }
// ponytail: consolidated bus memory mirroring test suite // consolidated bus memory mirroring test suite
test "bus internal RAM and CIRAM nametable mirroring" { test "bus internal RAM and CIRAM nametable mirroring" {
var bus = Bus.init(.ntsc, null); var bus = Bus.init(.ntsc, null);
bus.write(0x0005, 0x99); bus.write(0x0005, 0x99);
+2 -2
View File
@@ -45,7 +45,7 @@ pub fn read(self: *Controller) u8 {
return val | 0x40; return val | 0x40;
} }
// ponytail: consolidated controller test suite // consolidated controller test suite
test "controller strobe and serial shift read" { test "controller strobe and serial shift read" {
var c = Controller.init(); var c = Controller.init();
c.setButtons(0x09); // A and Start pressed c.setButtons(0x09); // A and Start pressed
@@ -53,7 +53,7 @@ test "controller strobe and serial shift read" {
c.write(0); c.write(0);
const expected = [_]u8{ 0x41, 0x40, 0x40, 0x41, 0x40, 0x40, 0x40, 0x40 }; const expected = [_]u8{ 0x41, 0x40, 0x40, 0x41, 0x40, 0x40, 0x40, 0x40 };
for (expected) |exp| try std.testing.expectEqual(exp, c.read()); for (expected) |exp| try std.testing.expectEqual(exp, c.read());
// 9th read onwards should return 1s (0x41) due to pull-up // 9th read onwards should return 1s (0x41) due to pull-up
try std.testing.expectEqual(@as(u8, 0x41), c.read()); try std.testing.expectEqual(@as(u8, 0x41), c.read());
try std.testing.expectEqual(@as(u8, 0x41), c.read()); try std.testing.expectEqual(@as(u8, 0x41), c.read());
+1 -1
View File
@@ -2,7 +2,7 @@ const std = @import("std");
const common = @import("../common.zig"); const common = @import("../common.zig");
const Mirroring = common.Mirroring; const Mirroring = common.Mirroring;
// ponytail: Mapper 28 (Action 53) // Mapper 28 (Action 53)
pub const Action53 = @This(); pub const Action53 = @This();
selected_reg: u2 = 0, selected_reg: u2 = 0,
+1 -1
View File
@@ -2,7 +2,7 @@ const std = @import("std");
const common = @import("../common.zig"); const common = @import("../common.zig");
const Mirroring = common.Mirroring; const Mirroring = common.Mirroring;
// ponytail: Mapper 34 (BNROM / NINA-001) - 32KB PRG banking, optional NINA-001 4KB CHR banking // Mapper 34 (BNROM / NINA-001) - 32KB PRG banking, optional NINA-001 4KB CHR banking
pub const Bnrom = @This(); pub const Bnrom = @This();
prg_bank: u8 = 0, prg_bank: u8 = 0,
+1 -1
View File
@@ -2,7 +2,7 @@ const std = @import("std");
const common = @import("../common.zig"); const common = @import("../common.zig");
const Mirroring = common.Mirroring; const Mirroring = common.Mirroring;
// ponytail: Mapper 71 (Camerica / Codemasters) - 16KB PRG banking at $C000-$FFFF, optional 1-screen mirroring at $9000-$9FFF // Mapper 71 (Camerica / Codemasters) - 16KB PRG banking at $C000-$FFFF, optional 1-screen mirroring at $9000-$9FFF
pub const Camerica = @This(); pub const Camerica = @This();
prg_bank: u8 = 0, prg_bank: u8 = 0,
+1 -1
View File
@@ -35,7 +35,7 @@ pub fn cpuWrite(self: *Cnrom, address: u16, value: u8, prg_ram: []u8) bool {
} }
} }
if (address >= 0x8000 and address <= 0xffff) { if (address >= 0x8000 and address <= 0xffff) {
// ponytail: mask up to 16 banks (128KB) for extended CNROM homebrew // mask up to 16 banks (128KB) for extended CNROM homebrew
self.chr_bank = value & 0x0f; self.chr_bank = value & 0x0f;
return true; return true;
} }
+1 -1
View File
@@ -2,7 +2,7 @@ const std = @import("std");
const common = @import("../common.zig"); const common = @import("../common.zig");
const Mirroring = common.Mirroring; const Mirroring = common.Mirroring;
// ponytail: Mapper 11 (Color Dreams) - 32KB PRG bank in bits 0-1, 8KB CHR bank in bits 4-7 // Mapper 11 (Color Dreams) - 32KB PRG bank in bits 0-1, 8KB CHR bank in bits 4-7
pub const ColorDreams = @This(); pub const ColorDreams = @This();
prg_bank: u8 = 0, prg_bank: u8 = 0,
+1 -1
View File
@@ -2,7 +2,7 @@ const std = @import("std");
const common = @import("../common.zig"); const common = @import("../common.zig");
const Mirroring = common.Mirroring; const Mirroring = common.Mirroring;
// ponytail: Mapper 66 (GxROM / GNROM) - 32KB PRG bank in bits 4-5, 8KB CHR bank in bits 0-1 // Mapper 66 (GxROM / GNROM) - 32KB PRG bank in bits 4-5, 8KB CHR bank in bits 0-1
pub const Gxrom = @This(); pub const Gxrom = @This();
prg_bank: u8 = 0, prg_bank: u8 = 0,
+1 -1
View File
@@ -2,7 +2,7 @@ const std = @import("std");
const common = @import("../common.zig"); const common = @import("../common.zig");
const Mirroring = common.Mirroring; const Mirroring = common.Mirroring;
// ponytail: Mapper 180 (Nichibutsu / Crazy Climber) // Mapper 180 (Nichibutsu / Crazy Climber)
// Fixed first 16KB at $8000-$BFFF, switchable 16KB at $C000-$FFFF via write to $8000-$FFFF. // Fixed first 16KB at $8000-$BFFF, switchable 16KB at $C000-$FFFF via write to $8000-$FFFF.
pub const Mapper180 = @This(); pub const Mapper180 = @This();
+1 -1
View File
@@ -25,7 +25,7 @@ pub fn cpuRead(self: *const Mmc1, address: u16, prg_rom: []const u8, prg_ram: []
if (address >= 0x8000 and address <= 0xffff) { if (address >= 0x8000 and address <= 0xffff) {
const prg_mode: u2 = @truncate(self.control >> 2); const prg_mode: u2 = @truncate(self.control >> 2);
const total_16k_banks: usize = @max(1, prg_rom.len / 0x4000); const total_16k_banks: usize = @max(1, prg_rom.len / 0x4000);
// ponytail: SUROM uses CHR bank 0 bit 4 for PRG A18 when PRG is 512KB // SUROM uses CHR bank 0 bit 4 for PRG A18 when PRG is 512KB
const surom_base: usize = if (total_16k_banks >= 32 and (self.chr_bank_0 & 0x10) != 0) 16 else 0; const surom_base: usize = if (total_16k_banks >= 32 and (self.chr_bank_0 & 0x10) != 0) 16 else 0;
var bank: usize = 0; var bank: usize = 0;
+1 -1
View File
@@ -2,7 +2,7 @@ const std = @import("std");
const common = @import("../common.zig"); const common = @import("../common.zig");
const Mirroring = common.Mirroring; const Mirroring = common.Mirroring;
// ponytail: Mapper 9 (MMC2) - Mike Tyson's Punch-Out!! // Mapper 9 (MMC2) - Mike Tyson's Punch-Out!!
// 8KB switchable PRG at $8000, fixed 24KB at $A000-$FFFF. // 8KB switchable PRG at $8000, fixed 24KB at $A000-$FFFF.
// Dual 4KB CHR latches toggled by PPU fetches at $xFD8-$xFDF and $xFE8-$xFEF. // Dual 4KB CHR latches toggled by PPU fetches at $xFD8-$xFDF and $xFE8-$xFEF.
pub const Mmc2 = @This(); pub const Mmc2 = @This();
+1 -1
View File
@@ -2,7 +2,7 @@ const std = @import("std");
const common = @import("../common.zig"); const common = @import("../common.zig");
const Mirroring = common.Mirroring; const Mirroring = common.Mirroring;
// ponytail: Mapper 10 (MMC4) - Fire Emblem & Fire Emblem Gaiden // Mapper 10 (MMC4) - Fire Emblem & Fire Emblem Gaiden
// 16KB switchable PRG at $8000, fixed last 16KB at $C000-$FFFF. // 16KB switchable PRG at $8000, fixed last 16KB at $C000-$FFFF.
// Dual 4KB CHR latches toggled by PPU fetches at $xFD8-$xFDF and $xFE8-$xFEF. // Dual 4KB CHR latches toggled by PPU fetches at $xFD8-$xFDF and $xFE8-$xFEF.
pub const Mmc4 = @This(); pub const Mmc4 = @This();
+1 -1
View File
@@ -2,7 +2,7 @@ const std = @import("std");
const common = @import("../common.zig"); const common = @import("../common.zig");
const Mirroring = common.Mirroring; const Mirroring = common.Mirroring;
// ponytail: Mapper 30 (UNROM-512) - 16KB PRG bank (bits 0-4), 8KB CHR-RAM bank (bits 5-6), 1-screen mirroring (bit 7) // Mapper 30 (UNROM-512) - 16KB PRG bank (bits 0-4), 8KB CHR-RAM bank (bits 5-6), 1-screen mirroring (bit 7)
pub const Unrom512 = @This(); pub const Unrom512 = @This();
prg_bank: u8 = 0, prg_bank: u8 = 0,
+1 -1
View File
@@ -43,7 +43,7 @@ pub fn cpuWrite(self: *Uxrom, address: u16, value: u8, prg_ram: []u8) bool {
} }
} }
if (address >= 0x8000 and address <= 0xffff) { if (address >= 0x8000 and address <= 0xffff) {
// ponytail: mask up to 32 banks (512KB) for UOROM / UNROM // mask up to 32 banks (512KB) for UOROM / UNROM
self.prg_bank = value & 0x1f; self.prg_bank = value & 0x1f;
return true; return true;
} }
+7 -3
View File
@@ -263,11 +263,15 @@ pub fn cpuWrite(self: *Registers, bus: anytype, address: u16, value: u8, renderi
return true; return true;
} }
// ponytail: consolidated PPU registers test suite // consolidated PPU registers test suite
pub const TestBus = struct { pub const TestBus = struct {
memory: [0x10000]u8 = [_]u8{0} ** 0x10000, memory: [0x10000]u8 = [_]u8{0} ** 0x10000,
pub fn read(self: *TestBus, address: u16) u8 { return self.memory[address]; } pub fn read(self: *TestBus, address: u16) u8 {
pub fn write(self: *TestBus, address: u16, value: u8) void { self.memory[address] = value; } return self.memory[address];
}
pub fn write(self: *TestBus, address: u16, value: u8) void {
self.memory[address] = value;
}
}; };
test "ppu registers $2000-$2007 control, scroll, address and buffered reads" { test "ppu registers $2000-$2007 control, scroll, address and buffered reads" {
+3 -3
View File
@@ -311,7 +311,7 @@ fn stepSpriteLogic(self: *Ppu, bus: anytype) void {
} }
self.sprite_count += 1; self.sprite_count += 1;
} else { } else {
// ponytail: 8-sprite limit reached; set overflow flag // 8-sprite limit reached; set overflow flag
self.registers.setSpriteOverflow(true); self.registers.setSpriteOverflow(true);
break; break;
} }
@@ -494,7 +494,7 @@ const MockBus = struct {
pub fn write(_: *MockBus, _: u16, _: u8) void {} pub fn write(_: *MockBus, _: u16, _: u8) void {}
}; };
// ponytail: consolidated PPU timing and rendering unit test suite // consolidated PPU timing and rendering unit test suite
test "ppu scanline progression, vblank flags, odd-frame skip and sprite 0 hit" { test "ppu scanline progression, vblank flags, odd-frame skip and sprite 0 hit" {
var ppu = Ppu.init(.ntsc); var ppu = Ppu.init(.ntsc);
var bus = MockBus{}; var bus = MockBus{};
@@ -515,7 +515,7 @@ test "ppu scanline progression, vblank flags, odd-frame skip and sprite 0 hit" {
for (0..4) |_| ppu.clock(&bus); for (0..4) |_| ppu.clock(&bus);
while (!ppu.frame_complete) ppu.clock(&bus); while (!ppu.frame_complete) ppu.clock(&bus);
try std.testing.expect(ppu.odd_frame); try std.testing.expect(ppu.odd_frame);
// Sprite 0 Hit detection // Sprite 0 Hit detection
ppu = Ppu.init(.ntsc); ppu = Ppu.init(.ntsc);
_ = ppu.cpuWrite(&bus, 0x2001, 0x18); _ = ppu.cpuWrite(&bus, 0x2001, 0x18);