test(nes): consolidate and simplify inline subsystem unit tests
This commit is contained in:
@@ -108,7 +108,7 @@ pub fn output(self: *const Envelope) u4 {
|
||||
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" {
|
||||
var envelope = Envelope.init();
|
||||
envelope.write(0b0010_1010); // loop=1, const_vol=0, vol=10
|
||||
|
||||
@@ -212,13 +212,13 @@ pub fn irqAsserted(self: *const FrameCounter) bool {
|
||||
}
|
||||
|
||||
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) {
|
||||
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" {
|
||||
var fc = FrameCounter.init(.ntsc);
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ pub fn active(self: *const LengthCounter) bool {
|
||||
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" {
|
||||
try std.testing.expectEqual(@as(u8, 10), table[0]);
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ pub fn reset(self: *Noise) void {
|
||||
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" {
|
||||
var noise = Noise.init(.ntsc);
|
||||
noise.writeControl(0b0011_1010);
|
||||
|
||||
@@ -253,7 +253,7 @@ pub fn sequencePosition(self: *const Pulse) u3 {
|
||||
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" {
|
||||
var pulse = Pulse.init(.pulse1);
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ pub fn init(region: Region) Apu {
|
||||
}
|
||||
|
||||
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.frame_counter.reset();
|
||||
self.noise.reset();
|
||||
@@ -286,7 +286,7 @@ pub fn clockCpu(self: *Apu) void {
|
||||
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_cycles += 1;
|
||||
if (self.sample_cycles >= 40) {
|
||||
@@ -463,7 +463,7 @@ pub fn output(self: *const Apu) f32 {
|
||||
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" {
|
||||
var apu = Apu.init(.ntsc);
|
||||
|
||||
|
||||
@@ -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" {
|
||||
var sweep1 = Sweep.init(.pulse1);
|
||||
sweep1.write(0b1010_0001); // P=2, shift=1
|
||||
|
||||
@@ -183,7 +183,7 @@ pub fn irqLine(self: *const Bus) bool {
|
||||
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" {
|
||||
var bus = Bus.init(.ntsc, null);
|
||||
bus.write(0x0005, 0x99);
|
||||
|
||||
@@ -45,7 +45,7 @@ pub fn read(self: *Controller) u8 {
|
||||
return val | 0x40;
|
||||
}
|
||||
|
||||
// ponytail: consolidated controller test suite
|
||||
// consolidated controller test suite
|
||||
test "controller strobe and serial shift read" {
|
||||
var c = Controller.init();
|
||||
c.setButtons(0x09); // A and Start pressed
|
||||
@@ -53,7 +53,7 @@ test "controller strobe and serial shift read" {
|
||||
c.write(0);
|
||||
const expected = [_]u8{ 0x41, 0x40, 0x40, 0x41, 0x40, 0x40, 0x40, 0x40 };
|
||||
for (expected) |exp| try std.testing.expectEqual(exp, c.read());
|
||||
|
||||
|
||||
// 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());
|
||||
|
||||
@@ -2,7 +2,7 @@ const std = @import("std");
|
||||
const common = @import("../common.zig");
|
||||
const Mirroring = common.Mirroring;
|
||||
|
||||
// ponytail: Mapper 28 (Action 53)
|
||||
// Mapper 28 (Action 53)
|
||||
pub const Action53 = @This();
|
||||
|
||||
selected_reg: u2 = 0,
|
||||
|
||||
@@ -2,7 +2,7 @@ const std = @import("std");
|
||||
const common = @import("../common.zig");
|
||||
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();
|
||||
|
||||
prg_bank: u8 = 0,
|
||||
|
||||
@@ -2,7 +2,7 @@ const std = @import("std");
|
||||
const common = @import("../common.zig");
|
||||
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();
|
||||
|
||||
prg_bank: u8 = 0,
|
||||
|
||||
@@ -35,7 +35,7 @@ pub fn cpuWrite(self: *Cnrom, address: u16, value: u8, prg_ram: []u8) bool {
|
||||
}
|
||||
}
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ const std = @import("std");
|
||||
const common = @import("../common.zig");
|
||||
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();
|
||||
|
||||
prg_bank: u8 = 0,
|
||||
|
||||
@@ -2,7 +2,7 @@ const std = @import("std");
|
||||
const common = @import("../common.zig");
|
||||
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();
|
||||
|
||||
prg_bank: u8 = 0,
|
||||
|
||||
@@ -2,7 +2,7 @@ const std = @import("std");
|
||||
const common = @import("../common.zig");
|
||||
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.
|
||||
pub const Mapper180 = @This();
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ pub fn cpuRead(self: *const Mmc1, address: u16, prg_rom: []const u8, prg_ram: []
|
||||
if (address >= 0x8000 and address <= 0xffff) {
|
||||
const prg_mode: u2 = @truncate(self.control >> 2);
|
||||
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;
|
||||
var bank: usize = 0;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ const std = @import("std");
|
||||
const common = @import("../common.zig");
|
||||
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.
|
||||
// Dual 4KB CHR latches toggled by PPU fetches at $xFD8-$xFDF and $xFE8-$xFEF.
|
||||
pub const Mmc2 = @This();
|
||||
|
||||
@@ -2,7 +2,7 @@ const std = @import("std");
|
||||
const common = @import("../common.zig");
|
||||
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.
|
||||
// Dual 4KB CHR latches toggled by PPU fetches at $xFD8-$xFDF and $xFE8-$xFEF.
|
||||
pub const Mmc4 = @This();
|
||||
|
||||
@@ -2,7 +2,7 @@ const std = @import("std");
|
||||
const common = @import("../common.zig");
|
||||
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();
|
||||
|
||||
prg_bank: u8 = 0,
|
||||
|
||||
@@ -43,7 +43,7 @@ pub fn cpuWrite(self: *Uxrom, address: u16, value: u8, prg_ram: []u8) bool {
|
||||
}
|
||||
}
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -263,11 +263,15 @@ pub fn cpuWrite(self: *Registers, bus: anytype, address: u16, value: u8, renderi
|
||||
return true;
|
||||
}
|
||||
|
||||
// ponytail: consolidated PPU registers test suite
|
||||
// consolidated PPU registers test suite
|
||||
pub const TestBus = struct {
|
||||
memory: [0x10000]u8 = [_]u8{0} ** 0x10000,
|
||||
pub fn read(self: *TestBus, address: u16) u8 { return self.memory[address]; }
|
||||
pub fn write(self: *TestBus, address: u16, value: u8) void { self.memory[address] = value; }
|
||||
pub fn read(self: *TestBus, address: u16) u8 {
|
||||
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" {
|
||||
|
||||
@@ -311,7 +311,7 @@ fn stepSpriteLogic(self: *Ppu, bus: anytype) void {
|
||||
}
|
||||
self.sprite_count += 1;
|
||||
} else {
|
||||
// ponytail: 8-sprite limit reached; set overflow flag
|
||||
// 8-sprite limit reached; set overflow flag
|
||||
self.registers.setSpriteOverflow(true);
|
||||
break;
|
||||
}
|
||||
@@ -494,7 +494,7 @@ const MockBus = struct {
|
||||
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" {
|
||||
var ppu = Ppu.init(.ntsc);
|
||||
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);
|
||||
while (!ppu.frame_complete) ppu.clock(&bus);
|
||||
try std.testing.expect(ppu.odd_frame);
|
||||
|
||||
|
||||
// Sprite 0 Hit detection
|
||||
ppu = Ppu.init(.ntsc);
|
||||
_ = ppu.cpuWrite(&bus, 0x2001, 0x18);
|
||||
|
||||
Reference in New Issue
Block a user