add nes conformance test suite and CLI runner benchmark tool
This commit is contained in:
@@ -10,33 +10,91 @@ pub fn build(b: *std.Build) void {
|
||||
.root_source_file = b.path("src/cpu/root.zig"),
|
||||
});
|
||||
|
||||
const lib = b.addLibrary(.{
|
||||
.name = "cpu",
|
||||
.root_module = cpu_mod,
|
||||
.linkage = .static,
|
||||
});
|
||||
|
||||
b.installArtifact(lib);
|
||||
|
||||
const contract_mod = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.root_source_file = b.path("src/contract/root.zig"),
|
||||
});
|
||||
|
||||
const system_mod = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.root_source_file = b.path("src/system/root.zig"),
|
||||
});
|
||||
system_mod.addImport("contract", contract_mod);
|
||||
system_mod.addImport("cpu", cpu_mod);
|
||||
|
||||
const platform_mod = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.root_source_file = b.path("src/platform/root.zig"),
|
||||
});
|
||||
platform_mod.addImport("contract", contract_mod);
|
||||
|
||||
const frontend_mod = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.root_source_file = b.path("src/frontend/root.zig"),
|
||||
});
|
||||
frontend_mod.addImport("contract", contract_mod);
|
||||
|
||||
const exe_mod = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
});
|
||||
exe_mod.addImport("contract", contract_mod);
|
||||
exe_mod.addImport("system", system_mod);
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "6soz",
|
||||
.root_module = exe_mod,
|
||||
});
|
||||
|
||||
b.installArtifact(exe);
|
||||
|
||||
// Test Step Configuration
|
||||
const test_step = b.step("test", "Run 6soz tests");
|
||||
|
||||
inline for (&.{ cpu_mod, system_mod, platform_mod, frontend_mod, exe_mod }) |mod| {
|
||||
const test_options = b.addOptions();
|
||||
if (b.lazyDependency("nes_test_roms", .{})) |roms_dep| {
|
||||
test_options.addOption(?[]const u8, "nes_test_roms_dir", roms_dep.path("").getPath(b));
|
||||
} else {
|
||||
test_options.addOption(?[]const u8, "nes_test_roms_dir", null);
|
||||
}
|
||||
if (b.lazyDependency("m6502_functional_tests", .{})) |func_dep| {
|
||||
test_options.addOption(?[]const u8, "m6502_functional_tests_dir", func_dep.path("").getPath(b));
|
||||
} else {
|
||||
test_options.addOption(?[]const u8, "m6502_functional_tests_dir", null);
|
||||
}
|
||||
|
||||
const options_mod = test_options.createModule();
|
||||
|
||||
const cpu_test_mod = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.root_source_file = b.path("src/cpu/root.zig"),
|
||||
});
|
||||
cpu_test_mod.addImport("build_options", options_mod);
|
||||
|
||||
const system_test_mod = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.root_source_file = b.path("src/system/root.zig"),
|
||||
});
|
||||
system_test_mod.addImport("contract", contract_mod);
|
||||
system_test_mod.addImport("cpu", cpu_test_mod);
|
||||
system_test_mod.addImport("build_options", options_mod);
|
||||
|
||||
inline for (&.{ cpu_test_mod, contract_mod, system_test_mod, platform_mod, frontend_mod, exe_mod }) |mod| {
|
||||
const mod_test = b.addTest(.{ .root_module = mod });
|
||||
const mod_cmd = b.addRunArtifact(mod_test);
|
||||
test_step.dependOn(&mod_cmd.step);
|
||||
|
||||
@@ -2,6 +2,19 @@
|
||||
.name = ._6soz,
|
||||
.version = "0.0.1",
|
||||
.minimum_zig_version = "0.16.0",
|
||||
.dependencies = .{
|
||||
.nes_test_roms = .{
|
||||
.url = "https://github.com/christopherpow/nes-test-roms/archive/refs/heads/master.tar.gz",
|
||||
.hash = "N-V-__8AANPxaAENPnXYuDVuqgUcOdrux1zKqz8bZZYf0RA2",
|
||||
.lazy = true,
|
||||
},
|
||||
.m6502_functional_tests = .{
|
||||
.url = "https://github.com/Klaus2m5/6502_65C02_functional_tests/archive/refs/heads/master.tar.gz",
|
||||
.hash = "N-V-__8AAKmLHACkwIMCMRdqnsyLtL5hbQdi8cm3nGs8n-O7",
|
||||
.lazy = true,
|
||||
},
|
||||
},
|
||||
|
||||
.paths = .{""},
|
||||
.fingerprint = 0xad233139f14a4bea,
|
||||
}
|
||||
|
||||
+157
-1
@@ -1 +1,157 @@
|
||||
pub fn main() !void {}
|
||||
const std = @import("std");
|
||||
const Nes = @import("system").nes;
|
||||
|
||||
pub fn main(init: std.process.Init) !void {
|
||||
const allocator = init.gpa;
|
||||
const cwd_dir: std.Io.Dir = .{ .handle = std.posix.AT.FDCWD };
|
||||
|
||||
var args_it = try std.process.Args.Iterator.initAllocator(init.minimal.args, allocator);
|
||||
defer args_it.deinit();
|
||||
|
||||
var args: [32][]const u8 = undefined;
|
||||
var args_count: usize = 0;
|
||||
|
||||
while (args_it.next()) |arg| {
|
||||
if (args_count < args.len) {
|
||||
args[args_count] = arg;
|
||||
args_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (args_count < 2) {
|
||||
printUsage();
|
||||
return;
|
||||
}
|
||||
|
||||
const command = args[1];
|
||||
if (std.mem.eql(u8, command, "help") or std.mem.eql(u8, command, "--help")) {
|
||||
printUsage();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!std.mem.eql(u8, command, "run")) {
|
||||
std.debug.print("Unknown command '{s}'. Use '6soz --help' for usage.\n", .{command});
|
||||
return;
|
||||
}
|
||||
|
||||
if (args_count < 3) {
|
||||
std.debug.print("Error: missing ROM file path.\nUsage: 6soz run <path/to/rom.nes>\n", .{});
|
||||
return;
|
||||
}
|
||||
|
||||
const rom_path = args[2];
|
||||
var target_frames: u64 = 60;
|
||||
var save_state_path: ?[]const u8 = null;
|
||||
var load_state_path: ?[]const u8 = null;
|
||||
var save_sram_path: ?[]const u8 = null;
|
||||
var load_sram_path: ?[]const u8 = null;
|
||||
|
||||
var i: usize = 3;
|
||||
while (i < args_count) : (i += 1) {
|
||||
const arg = args[i];
|
||||
if (std.mem.eql(u8, arg, "--frames") and i + 1 < args_count) {
|
||||
i += 1;
|
||||
target_frames = try std.fmt.parseInt(u64, args[i], 10);
|
||||
} else if (std.mem.eql(u8, arg, "--save-state") and i + 1 < args_count) {
|
||||
i += 1;
|
||||
save_state_path = args[i];
|
||||
} else if (std.mem.eql(u8, arg, "--load-state") and i + 1 < args_count) {
|
||||
i += 1;
|
||||
load_state_path = args[i];
|
||||
} else if (std.mem.eql(u8, arg, "--save-sram") and i + 1 < args_count) {
|
||||
i += 1;
|
||||
save_sram_path = args[i];
|
||||
} else if (std.mem.eql(u8, arg, "--load-sram") and i + 1 < args_count) {
|
||||
i += 1;
|
||||
load_sram_path = args[i];
|
||||
}
|
||||
}
|
||||
|
||||
const rom_bytes = cwd_dir.readFileAlloc(init.io, rom_path, allocator, @enumFromInt(16 * 1024 * 1024)) catch |err| {
|
||||
std.debug.print("Error reading ROM file '{s}': {s}\n", .{ rom_path, @errorName(err) });
|
||||
return;
|
||||
};
|
||||
defer allocator.free(rom_bytes);
|
||||
|
||||
var nes: Nes = undefined;
|
||||
nes.init(rom_bytes) catch |err| {
|
||||
std.debug.print("Failed to initialize NES cartridge: {s}\n", .{@errorName(err)});
|
||||
return;
|
||||
};
|
||||
|
||||
if (load_sram_path) |path| {
|
||||
if (cwd_dir.readFileAlloc(init.io, path, allocator, @enumFromInt(8192))) |sram_bytes| {
|
||||
defer allocator.free(sram_bytes);
|
||||
nes.loadStorage(0, sram_bytes) catch {};
|
||||
std.debug.print("[6soz] Loaded SRAM from '{s}'\n", .{path});
|
||||
} else |err| {
|
||||
std.debug.print("Warning: Failed to read SRAM file '{s}': {s}\n", .{ path, @errorName(err) });
|
||||
}
|
||||
}
|
||||
|
||||
if (load_state_path) |path| {
|
||||
if (cwd_dir.readFileAlloc(init.io, path, allocator, @enumFromInt(64 * 1024))) |state_bytes| {
|
||||
defer allocator.free(state_bytes);
|
||||
nes.loadState(state_bytes) catch |err| {
|
||||
std.debug.print("Failed to load save state: {s}\n", .{@errorName(err)});
|
||||
};
|
||||
std.debug.print("[6soz] Loaded Save State from '{s}'\n", .{path});
|
||||
} else |err| {
|
||||
std.debug.print("Warning: Failed to read state file '{s}': {s}\n", .{ path, @errorName(err) });
|
||||
}
|
||||
}
|
||||
|
||||
std.debug.print("[6soz] Running NES emulator for {d} frames...\n", .{target_frames});
|
||||
const start_time = std.Io.Clock.Timestamp.now(init.io, .awake);
|
||||
|
||||
var frame: u64 = 0;
|
||||
while (frame < target_frames) : (frame += 1) {
|
||||
nes.runFrame();
|
||||
}
|
||||
|
||||
const end_time = std.Io.Clock.Timestamp.now(init.io, .awake);
|
||||
const elapsed_ns = end_time.raw.nanoseconds - start_time.raw.nanoseconds;
|
||||
const elapsed_sec = @as(f64, @floatFromInt(elapsed_ns)) / 1_000_000_000.0;
|
||||
const fps = if (elapsed_sec > 0) @as(f64, @floatFromInt(target_frames)) / elapsed_sec else 0.0;
|
||||
|
||||
std.debug.print("[6soz] Benchmark Complete:\n", .{});
|
||||
std.debug.print(" - Frames: {d}\n", .{target_frames});
|
||||
std.debug.print(" - Elapsed Time: {d:.3}s\n", .{elapsed_sec});
|
||||
std.debug.print(" - Performance: {d:.1} FPS\n", .{fps});
|
||||
|
||||
if (save_state_path) |path| {
|
||||
var state_buf: [64 * 1024]u8 = undefined;
|
||||
if (nes.saveState(&state_buf)) |written| {
|
||||
cwd_dir.writeFile(init.io, .{ .sub_path = path, .data = state_buf[0..written] }) catch |err| {
|
||||
std.debug.print("Error writing save state to '{s}': {s}\n", .{ path, @errorName(err) });
|
||||
};
|
||||
std.debug.print("[6soz] Saved State to '{s}'\n", .{path});
|
||||
} else |err| {
|
||||
std.debug.print("Error serializing save state: {s}\n", .{@errorName(err)});
|
||||
}
|
||||
}
|
||||
|
||||
if (save_sram_path) |path| {
|
||||
const sram_view = nes.storageView(0);
|
||||
cwd_dir.writeFile(init.io, .{ .sub_path = path, .data = sram_view.data }) catch |err| {
|
||||
std.debug.print("Error writing SRAM to '{s}': {s}\n", .{ path, @errorName(err) });
|
||||
};
|
||||
std.debug.print("[6soz] Saved SRAM to '{s}'\n", .{path});
|
||||
}
|
||||
}
|
||||
|
||||
fn printUsage() void {
|
||||
std.debug.print(
|
||||
\\6soz - 6502 / 6510 & NES Emulator Engine
|
||||
\\Usage:
|
||||
\\ 6soz run <path/to/rom.nes> [options]
|
||||
\\
|
||||
\\Options:
|
||||
\\ --frames <N> Number of frames to execute (default: 60)
|
||||
\\ --save-state <file> Save state binary file output
|
||||
\\ --load-state <file> Load state binary file input
|
||||
\\ --save-sram <file> Save battery PRG RAM output
|
||||
\\ --load-sram <file> Load battery PRG RAM input
|
||||
\\
|
||||
, .{});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,345 @@
|
||||
// minimal system test runner for nes-test-roms & NES system contract conformance
|
||||
const std = @import("std");
|
||||
const testing = std.testing;
|
||||
const build_options = @import("build_options");
|
||||
const contract = @import("contract");
|
||||
const Nes = @import("root.zig");
|
||||
|
||||
fn loadRomAlloc(allocator: std.mem.Allocator, relative_path: []const u8) ![]u8 {
|
||||
const base_dir = build_options.nes_test_roms_dir orelse return error.TestRomsNotFetched;
|
||||
var dir = try std.Io.Dir.openDirAbsolute(testing.io, base_dir, .{});
|
||||
defer dir.close(testing.io);
|
||||
return dir.readFileAlloc(testing.io, relative_path, allocator, @enumFromInt(10 * 1024 * 1024));
|
||||
}
|
||||
|
||||
/// Runs a blargg test ROM on the full NES system implementation.
|
||||
/// Blargg test ROMs signal completion via $6000 (0x80 = running, 0x00 = passed)
|
||||
/// and output result text starting at $6004.
|
||||
pub fn runBlarggTest(relative_path: []const u8, max_instructions: u64) !void {
|
||||
const rom_bytes = try loadRomAlloc(testing.allocator, relative_path);
|
||||
defer testing.allocator.free(rom_bytes);
|
||||
|
||||
var nes: Nes = undefined;
|
||||
try nes.init(rom_bytes);
|
||||
|
||||
var i: u64 = 0;
|
||||
var result_ready = false;
|
||||
|
||||
while (i < max_instructions) : (i += 1) {
|
||||
_ = nes.step() orelse break;
|
||||
|
||||
const status = nes.bus.read(0x6000);
|
||||
if (status != 0x80) {
|
||||
const sig0 = nes.bus.read(0x6001);
|
||||
const sig1 = nes.bus.read(0x6002);
|
||||
const sig2 = nes.bus.read(0x6003);
|
||||
if (sig0 == 0xDE and sig1 == 0xB0 and sig2 == 0x61) {
|
||||
result_ready = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const status = nes.bus.read(0x6000);
|
||||
|
||||
var msg_buf: [256]u8 = undefined;
|
||||
var msg_len: usize = 0;
|
||||
while (msg_len < msg_buf.len) : (msg_len += 1) {
|
||||
const char = nes.bus.read(@intCast(0x6004 + msg_len));
|
||||
if (char == 0) break;
|
||||
msg_buf[msg_len] = char;
|
||||
}
|
||||
const msg = msg_buf[0..msg_len];
|
||||
|
||||
if (!result_ready and status == 0x80) {
|
||||
std.debug.print(
|
||||
"\n[TIMEOUT] Test '{s}' timed out after {d} instructions. Text output: {s}\n",
|
||||
.{ relative_path, i, msg },
|
||||
);
|
||||
return error.TestTimeout;
|
||||
}
|
||||
|
||||
if (status != 0) {
|
||||
std.debug.print(
|
||||
"\n[FAIL] Test '{s}' failed with status 0x{X:0>2}: {s}\n",
|
||||
.{ relative_path, status, msg },
|
||||
);
|
||||
return error.TestFailed;
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs Kevtris' nestest.nes ROM on full NES system (PC = $C000).
|
||||
pub fn runNesTestSystem(relative_path: []const u8, max_instructions: u64) !void {
|
||||
const rom_bytes = try loadRomAlloc(testing.allocator, relative_path);
|
||||
defer testing.allocator.free(rom_bytes);
|
||||
|
||||
var nes: Nes = undefined;
|
||||
try nes.init(rom_bytes);
|
||||
nes.cpu.registers.pc = 0xC000;
|
||||
|
||||
var previous_pc: u16 = 0;
|
||||
var i: u64 = 0;
|
||||
|
||||
while (i < max_instructions) : (i += 1) {
|
||||
previous_pc = nes.cpu.registers.pc;
|
||||
_ = nes.step() orelse break;
|
||||
|
||||
if (nes.cpu.registers.pc == previous_pc) break;
|
||||
}
|
||||
|
||||
const err1 = nes.bus.read(0x0002);
|
||||
const err2 = nes.bus.read(0x0003);
|
||||
if (err1 != 0 or err2 != 0) {
|
||||
std.debug.print(
|
||||
"\n[FAIL] Nestest system test failed with error codes $0002: 0x{X:0>2}, $0003: 0x{X:0>2} at PC 0x{X:0>4}\n",
|
||||
.{ err1, err2, nes.cpu.registers.pc },
|
||||
);
|
||||
return error.NestestFailed;
|
||||
}
|
||||
}
|
||||
|
||||
test "NES System Conformance - Contract SystemSpec" {
|
||||
comptime contract.validateSystem(Nes);
|
||||
try testing.expectEqualStrings("Nintendo Entertainment System", Nes.spec.name);
|
||||
try testing.expectEqual(@as(usize, 1), Nes.spec.video_outputs.len);
|
||||
try testing.expectEqual(@as(usize, 1), Nes.spec.audio_outputs.len);
|
||||
try testing.expectEqual(@as(usize, 2), Nes.spec.input_devices.len);
|
||||
try testing.expectEqual(@as(usize, 1), Nes.spec.storage_devices.len);
|
||||
}
|
||||
|
||||
test "NES System Conformance - Video Frame Output" {
|
||||
var rom: [16 + 0x4000 + 0x2000]u8 = [_]u8{0} ** (16 + 0x4000 + 0x2000);
|
||||
@memcpy(rom[0..4], "NES\x1a");
|
||||
rom[4] = 1;
|
||||
rom[5] = 1;
|
||||
|
||||
var nes: Nes = undefined;
|
||||
try nes.init(&rom);
|
||||
nes.runFrame();
|
||||
|
||||
const frame = nes.videoFrame(0);
|
||||
try testing.expectEqual(@as(u32, 256), frame.width);
|
||||
try testing.expectEqual(@as(u32, 240), frame.height);
|
||||
try testing.expectEqual(contract.PixelFormat.rgb565, frame.format);
|
||||
try testing.expectEqual(256 * 240 * 2, frame.data.len);
|
||||
}
|
||||
|
||||
test "NES System Conformance - Audio Buffer Output" {
|
||||
var rom: [16 + 0x4000 + 0x2000]u8 = [_]u8{0} ** (16 + 0x4000 + 0x2000);
|
||||
@memcpy(rom[0..4], "NES\x1a");
|
||||
rom[4] = 1;
|
||||
rom[5] = 1;
|
||||
|
||||
var nes: Nes = undefined;
|
||||
try nes.init(&rom);
|
||||
nes.runFrame();
|
||||
|
||||
const audio_buf = nes.audioBuffer(0);
|
||||
try testing.expectEqual(contract.AudioSampleFormat.i16, audio_buf.format);
|
||||
try testing.expectEqual(@as(u8, 1), audio_buf.channels);
|
||||
try testing.expect(audio_buf.data.len > 0);
|
||||
}
|
||||
|
||||
test "NES System Conformance - Controller Input Shift & Strobe" {
|
||||
var rom: [16 + 0x4000 + 0x2000]u8 = [_]u8{0} ** (16 + 0x4000 + 0x2000);
|
||||
@memcpy(rom[0..4], "NES\x1a");
|
||||
rom[4] = 1;
|
||||
rom[5] = 1;
|
||||
|
||||
var nes: Nes = undefined;
|
||||
try nes.init(&rom);
|
||||
|
||||
// Press Button A (bit 0) and Button Select (bit 2) -> 0b00000101
|
||||
nes.setInput(0, .{ .buttons = 0x05 });
|
||||
|
||||
// Strobe controller
|
||||
nes.bus.write(0x4016, 1);
|
||||
nes.bus.write(0x4016, 0);
|
||||
|
||||
// Read serial bits
|
||||
try testing.expectEqual(@as(u8, 1), nes.bus.read(0x4016) & 1); // Button A (pressed)
|
||||
try testing.expectEqual(@as(u8, 0), nes.bus.read(0x4016) & 1); // Button B
|
||||
try testing.expectEqual(@as(u8, 1), nes.bus.read(0x4016) & 1); // Button Select (pressed)
|
||||
}
|
||||
|
||||
test "NES System Conformance - Storage & Save State Integrity" {
|
||||
var rom: [16 + 0x4000 + 0x2000]u8 = [_]u8{0} ** (16 + 0x4000 + 0x2000);
|
||||
@memcpy(rom[0..4], "NES\x1a");
|
||||
rom[4] = 1;
|
||||
rom[5] = 1;
|
||||
|
||||
var nes: Nes = undefined;
|
||||
try nes.init(&rom);
|
||||
|
||||
// Storage SRAM test
|
||||
const sram_data = [_]u8{ 0xDE, 0xAD, 0xBE, 0xEF };
|
||||
try nes.loadStorage(0, &sram_data);
|
||||
const view = nes.storageView(0);
|
||||
try testing.expectEqualSlices(u8, &sram_data, view.data[0..4]);
|
||||
|
||||
// Save state test
|
||||
var save_buf: [64 * 1024]u8 = undefined;
|
||||
const written = try nes.saveState(&save_buf);
|
||||
nes.reset();
|
||||
try nes.loadState(save_buf[0..written]);
|
||||
const view_after = nes.storageView(0);
|
||||
try testing.expectEqualSlices(u8, &sram_data, view_after.data[0..4]);
|
||||
}
|
||||
|
||||
test "NES System Conformance - APU DMC Sample Playback & DAC" {
|
||||
var rom: [16 + 0x4000 + 0x2000]u8 = [_]u8{0} ** (16 + 0x4000 + 0x2000);
|
||||
@memcpy(rom[0..4], "NES\x1a");
|
||||
rom[4] = 1;
|
||||
rom[5] = 1;
|
||||
|
||||
var nes: Nes = undefined;
|
||||
try nes.init(&rom);
|
||||
|
||||
// Enable DMC in APU status ($4015 bit 4)
|
||||
nes.bus.write(0x4015, 0x10);
|
||||
// Write DMC output level ($4011) to 64
|
||||
nes.bus.write(0x4011, 64);
|
||||
// Setup rate ($4010)
|
||||
nes.bus.write(0x4010, 0x05);
|
||||
|
||||
nes.runFrame();
|
||||
|
||||
const audio_buf = nes.audioBuffer(0);
|
||||
try testing.expect(audio_buf.data.len > 0);
|
||||
}
|
||||
|
||||
test "NES System Conformance - PPU PPUMASK Grayscale & Color Emphasis" {
|
||||
var rom: [16 + 0x4000 + 0x2000]u8 = [_]u8{0} ** (16 + 0x4000 + 0x2000);
|
||||
@memcpy(rom[0..4], "NES\x1a");
|
||||
rom[4] = 1;
|
||||
rom[5] = 1;
|
||||
|
||||
var nes: Nes = undefined;
|
||||
try nes.init(&rom);
|
||||
|
||||
// Enable Grayscale (bit 0) and Red/Green/Blue Emphasis (bits 5,6,7 -> 0xE1) in PPUMASK ($2001)
|
||||
nes.bus.write(0x2001, 0xE1);
|
||||
nes.runFrame();
|
||||
|
||||
const frame = nes.videoFrame(0);
|
||||
try testing.expectEqual(256 * 240 * 2, frame.data.len);
|
||||
}
|
||||
|
||||
test "NES System Conformance - All Mappers iNES Header Support" {
|
||||
const mappers = [_]struct { id: u8, prg_banks: u8, chr_banks: u8 }{
|
||||
.{ .id = 0, .prg_banks = 1, .chr_banks = 1 }, // NROM
|
||||
.{ .id = 1, .prg_banks = 8, .chr_banks = 8 }, // MMC1
|
||||
.{ .id = 2, .prg_banks = 8, .chr_banks = 0 }, // UxROM
|
||||
.{ .id = 3, .prg_banks = 2, .chr_banks = 4 }, // CNROM
|
||||
.{ .id = 4, .prg_banks = 16, .chr_banks = 16 }, // MMC3
|
||||
.{ .id = 5, .prg_banks = 16, .chr_banks = 16 }, // MMC5
|
||||
.{ .id = 7, .prg_banks = 8, .chr_banks = 0 }, // AxROM
|
||||
.{ .id = 9, .prg_banks = 8, .chr_banks = 16 }, // MMC2
|
||||
.{ .id = 10, .prg_banks = 8, .chr_banks = 16 }, // MMC4
|
||||
.{ .id = 11, .prg_banks = 4, .chr_banks = 16 }, // Color Dreams
|
||||
.{ .id = 13, .prg_banks = 2, .chr_banks = 0 }, // CPROM
|
||||
.{ .id = 21, .prg_banks = 8, .chr_banks = 16 }, // Konami VRC4a/c
|
||||
.{ .id = 22, .prg_banks = 8, .chr_banks = 16 }, // Konami VRC2a
|
||||
.{ .id = 23, .prg_banks = 8, .chr_banks = 16 }, // Konami VRC2b/VRC4e
|
||||
.{ .id = 24, .prg_banks = 16, .chr_banks = 16 }, // Konami VRC6a
|
||||
.{ .id = 25, .prg_banks = 8, .chr_banks = 16 }, // Konami VRC4b/d
|
||||
.{ .id = 26, .prg_banks = 16, .chr_banks = 16 }, // Konami VRC6b
|
||||
.{ .id = 34, .prg_banks = 8, .chr_banks = 8 }, // BNROM / NINA-01
|
||||
.{ .id = 65, .prg_banks = 8, .chr_banks = 16 }, // Irem H3001
|
||||
.{ .id = 66, .prg_banks = 4, .chr_banks = 4 }, // GxROM
|
||||
.{ .id = 68, .prg_banks = 8, .chr_banks = 16 }, // Sunsoft-4
|
||||
.{ .id = 69, .prg_banks = 8, .chr_banks = 16 }, // Sunsoft FME-7
|
||||
.{ .id = 71, .prg_banks = 8, .chr_banks = 0 }, // Camerica
|
||||
.{ .id = 73, .prg_banks = 8, .chr_banks = 0 }, // Konami VRC3
|
||||
.{ .id = 75, .prg_banks = 8, .chr_banks = 16 }, // Konami VRC1
|
||||
.{ .id = 76, .prg_banks = 8, .chr_banks = 16 }, // Namco 109
|
||||
.{ .id = 78, .prg_banks = 8, .chr_banks = 16 }, // Irem 74HC161/32
|
||||
.{ .id = 79, .prg_banks = 4, .chr_banks = 8 }, // NINA-03
|
||||
.{ .id = 85, .prg_banks = 8, .chr_banks = 16 }, // Konami VRC7
|
||||
.{ .id = 87, .prg_banks = 2, .chr_banks = 4 }, // Jaleco 74x139
|
||||
.{ .id = 88, .prg_banks = 8, .chr_banks = 16 }, // Namco 118
|
||||
.{ .id = 89, .prg_banks = 8, .chr_banks = 16 }, // Sunsoft-2
|
||||
.{ .id = 93, .prg_banks = 8, .chr_banks = 0 }, // Sunsoft-3
|
||||
.{ .id = 94, .prg_banks = 16, .chr_banks = 0 }, // UN1ROM
|
||||
.{ .id = 118, .prg_banks = 8, .chr_banks = 16 }, // TxSROM
|
||||
.{ .id = 119, .prg_banks = 8, .chr_banks = 16 }, // TQROM
|
||||
.{ .id = 140, .prg_banks = 4, .chr_banks = 16 }, // Jaleco Command
|
||||
.{ .id = 180, .prg_banks = 8, .chr_banks = 0 }, // Crazy Climber
|
||||
.{ .id = 206, .prg_banks = 8, .chr_banks = 16 }, // DxROM / Namco 108
|
||||
.{ .id = 210, .prg_banks = 8, .chr_banks = 16 }, // Namco 175/340
|
||||
.{ .id = 232, .prg_banks = 16, .chr_banks = 0 }, // Camerica Quattro
|
||||
};
|
||||
|
||||
for (mappers) |m| {
|
||||
var rom: [16 + 256 * 1024 + 256 * 1024]u8 = [_]u8{0} ** (16 + 256 * 1024 + 256 * 1024);
|
||||
@memcpy(rom[0..4], "NES\x1a");
|
||||
rom[4] = m.prg_banks;
|
||||
rom[5] = m.chr_banks;
|
||||
rom[6] = (m.id & 0x0F) << 4;
|
||||
rom[7] = m.id & 0xF0;
|
||||
|
||||
var nes: Nes = undefined;
|
||||
try nes.init(&rom);
|
||||
nes.runFrame();
|
||||
}
|
||||
}
|
||||
|
||||
test "NES System - Nestest Conformance" {
|
||||
if (build_options.nes_test_roms_dir == null) return error.SkipZigTest;
|
||||
try runNesTestSystem("other/nestest.nes", 100_000);
|
||||
}
|
||||
|
||||
test "NES System - Instr Test: 01-implied" {
|
||||
if (build_options.nes_test_roms_dir == null) return error.SkipZigTest;
|
||||
try runBlarggTest("nes_instr_test/rom_singles/01-implied.nes", 10_000_000);
|
||||
}
|
||||
|
||||
test "NES System - Instr Test: 02-immediate" {
|
||||
if (build_options.nes_test_roms_dir == null) return error.SkipZigTest;
|
||||
try runBlarggTest("nes_instr_test/rom_singles/02-immediate.nes", 10_000_000);
|
||||
}
|
||||
|
||||
test "NES System - Instr Test: 03-zero_page" {
|
||||
if (build_options.nes_test_roms_dir == null) return error.SkipZigTest;
|
||||
try runBlarggTest("nes_instr_test/rom_singles/03-zero_page.nes", 10_000_000);
|
||||
}
|
||||
|
||||
test "NES System - Instr Test: 04-zp_xy" {
|
||||
if (build_options.nes_test_roms_dir == null) return error.SkipZigTest;
|
||||
try runBlarggTest("nes_instr_test/rom_singles/04-zp_xy.nes", 10_000_000);
|
||||
}
|
||||
|
||||
test "NES System - Instr Test: 05-absolute" {
|
||||
if (build_options.nes_test_roms_dir == null) return error.SkipZigTest;
|
||||
try runBlarggTest("nes_instr_test/rom_singles/05-absolute.nes", 10_000_000);
|
||||
}
|
||||
|
||||
test "NES System - Instr Test: 06-abs_xy" {
|
||||
if (build_options.nes_test_roms_dir == null) return error.SkipZigTest;
|
||||
try runBlarggTest("nes_instr_test/rom_singles/06-abs_xy.nes", 10_000_000);
|
||||
}
|
||||
|
||||
test "NES System - Instr Test: 07-ind_x" {
|
||||
if (build_options.nes_test_roms_dir == null) return error.SkipZigTest;
|
||||
try runBlarggTest("nes_instr_test/rom_singles/07-ind_x.nes", 10_000_000);
|
||||
}
|
||||
|
||||
test "NES System - Instr Test: 08-ind_y" {
|
||||
if (build_options.nes_test_roms_dir == null) return error.SkipZigTest;
|
||||
try runBlarggTest("nes_instr_test/rom_singles/08-ind_y.nes", 10_000_000);
|
||||
}
|
||||
|
||||
test "NES System - Instr Test: 09-branches" {
|
||||
if (build_options.nes_test_roms_dir == null) return error.SkipZigTest;
|
||||
try runBlarggTest("nes_instr_test/rom_singles/09-branches.nes", 10_000_000);
|
||||
}
|
||||
|
||||
test "NES System - Instr Test: 10-stack" {
|
||||
if (build_options.nes_test_roms_dir == null) return error.SkipZigTest;
|
||||
try runBlarggTest("nes_instr_test/rom_singles/10-stack.nes", 10_000_000);
|
||||
}
|
||||
|
||||
test "NES System - Instr Test: 11-special" {
|
||||
if (build_options.nes_test_roms_dir == null) return error.SkipZigTest;
|
||||
try runBlarggTest("nes_instr_test/rom_singles/11-special.nes", 10_000_000);
|
||||
}
|
||||
Reference in New Issue
Block a user