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;
|
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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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]);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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" {
|
||||||
|
|||||||
@@ -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{};
|
||||||
|
|||||||
Reference in New Issue
Block a user