diff --git a/src/contract/audio.zig b/src/contract/audio.zig
new file mode 100644
index 0000000..a674cdb
--- /dev/null
+++ b/src/contract/audio.zig
@@ -0,0 +1,33 @@
+pub const SampleFormat = enum {
+ u8,
+ i16,
+ i24,
+ i32,
+ f32,
+};
+
+/// Static description of an audio stream.
+///
+/// Used for both outputs and inputs.
+pub const StreamSpec = struct {
+ sample_rate: u32,
+ channels: u8,
+ format: SampleFormat,
+};
+
+/// Borrowed interleaved audio samples.
+///
+/// For stereo:
+///
+/// L R L R L R ...
+///
+/// `frames` counts complete sample frames, not individual samples.
+pub const Buffer = struct {
+ data: []const u8,
+
+ frames: usize,
+
+ sample_rate: u32,
+ channels: u8,
+ format: SampleFormat,
+};
diff --git a/src/contract/common.zig b/src/contract/common.zig
new file mode 100644
index 0000000..20bec76
--- /dev/null
+++ b/src/contract/common.zig
@@ -0,0 +1,21 @@
+const std = @import("std");
+
+pub const Rational = struct {
+ numerator: u32,
+ denominator: u32 = 1,
+
+ pub fn asF64(self: Rational) f64 {
+ std.debug.assert(self.denominator != 0);
+
+ return @as(f64, @floatFromInt(self.numerator)) /
+ @as(f64, @floatFromInt(self.denominator));
+ }
+};
+
+// This needs to be in individual system
+// if they need it
+//pub const Region = enum {
+// ntsc_u,
+// ntsc_j,
+// pal,
+//};
diff --git a/src/contract/input.zig b/src/contract/input.zig
new file mode 100644
index 0000000..199eb35
--- /dev/null
+++ b/src/contract/input.zig
@@ -0,0 +1,156 @@
+const std = @import("std");
+
+pub const AxisValue = i16;
+
+/// Normalized pointer coordinate.
+///
+/// 0 = minimum
+/// 65535 = maximum
+pub const PointerState = struct {
+ active: bool = false,
+
+ x: u16 = 0,
+ y: u16 = 0,
+
+ pressure: u16 = 0,
+};
+
+pub const Vec3 = struct {
+ x: f32 = 0,
+ y: f32 = 0,
+ z: f32 = 0,
+};
+
+pub const MotionState = struct {
+ acceleration: Vec3 = .{},
+ gyroscope: Vec3 = .{},
+};
+
+/// Static capabilities of one input device/port.
+pub const DeviceSpec = struct {
+ button_count: u8 = 0,
+ axis_count: u8 = 0,
+ pointer_count: u8 = 0,
+
+ has_motion: bool = false,
+
+ /// Number of independent haptic/rumble actuators.
+ haptic_count: u8 = 0,
+};
+
+/// Runtime input view supplied by a frontend.
+///
+/// The frontend owns the slices.
+pub const DeviceInput = struct {
+ buttons: u64 = 0,
+
+ axes: []const AxisValue = &.{},
+ pointers: []const PointerState = &.{},
+
+ motion: ?MotionState = null,
+
+ pub fn isPressed(
+ self: DeviceInput,
+ button: usize,
+ ) bool {
+ std.debug.assert(button < 64);
+
+ return (self.buttons &
+ (@as(u64, 1) << @intCast(button))) != 0;
+ }
+};
+
+/// Feedback generated by an emulated input device.
+///
+/// Values are normalized:
+///
+/// 0 = off
+/// 65535 = maximum intensity
+pub const DeviceFeedback = struct {
+ haptics: []const u16 = &.{},
+};
+
+/// Convenience owning state for frontends/systems that want
+/// compile-time fixed storage.
+///
+/// This is a helper, not the actual ABI boundary.
+pub fn FixedDeviceState(
+ comptime button_count: usize,
+ comptime axis_count: usize,
+ comptime pointer_count: usize,
+ comptime has_motion: bool,
+) type {
+ comptime {
+ if (button_count > 64) {
+ @compileError(
+ "FixedDeviceState supports at most 64 buttons",
+ );
+ }
+ }
+
+ return struct {
+ buttons: u64 = 0,
+
+ axes: [axis_count]AxisValue =
+ [_]AxisValue{0} ** axis_count,
+
+ pointers: [pointer_count]PointerState =
+ [_]PointerState{.{}} ** pointer_count,
+
+ motion: if (has_motion) MotionState else void =
+ if (has_motion) .{} else {},
+
+ const Self = @This();
+
+ pub fn isPressed(
+ self: *const Self,
+ button: usize,
+ ) bool {
+ std.debug.assert(button < button_count);
+
+ return (self.buttons &
+ (@as(u64, 1) << @intCast(button))) != 0;
+ }
+
+ pub fn press(
+ self: *Self,
+ button: usize,
+ ) void {
+ std.debug.assert(button < button_count);
+
+ self.buttons |=
+ @as(u64, 1) << @intCast(button);
+ }
+
+ pub fn release(
+ self: *Self,
+ button: usize,
+ ) void {
+ std.debug.assert(button < button_count);
+
+ self.buttons &=
+ ~(@as(u64, 1) << @intCast(button));
+ }
+
+ pub fn setAxis(
+ self: *Self,
+ axis: usize,
+ value: AxisValue,
+ ) void {
+ std.debug.assert(axis < axis_count);
+ self.axes[axis] = value;
+ }
+
+ pub fn view(self: *const Self) DeviceInput {
+ return .{
+ .buttons = self.buttons,
+ .axes = &self.axes,
+ .pointers = &self.pointers,
+ .motion = if (has_motion)
+ self.motion
+ else
+ null,
+ };
+ }
+ };
+}
diff --git a/src/contract/root.zig b/src/contract/root.zig
new file mode 100644
index 0000000..ea52102
--- /dev/null
+++ b/src/contract/root.zig
@@ -0,0 +1,33 @@
+pub const common = @import("common.zig");
+pub const video = @import("video.zig");
+pub const audio = @import("audio.zig");
+pub const input = @import("input.zig");
+pub const storage = @import("storage.zig");
+pub const system = @import("system.zig");
+
+pub const Rational = common.Rational;
+
+pub const PixelFormat = video.PixelFormat;
+pub const ScanMode = video.ScanMode;
+pub const VideoOutputSpec = video.VideoOutputSpec;
+pub const VideoFrame = video.VideoFrame;
+
+pub const AudioSampleFormat = audio.SampleFormat;
+pub const AudioStreamSpec = audio.StreamSpec;
+pub const AudioBuffer = audio.Buffer;
+
+pub const AxisValue = input.AxisValue;
+pub const PointerState = input.PointerState;
+pub const Vec3 = input.Vec3;
+pub const MotionState = input.MotionState;
+pub const InputDeviceSpec = input.DeviceSpec;
+pub const DeviceInput = input.DeviceInput;
+pub const DeviceFeedback = input.DeviceFeedback;
+pub const FixedDeviceState = input.FixedDeviceState;
+
+pub const StorageSpec = storage.Spec;
+pub const StorageView = storage.View;
+pub const StorageLoadError = storage.LoadError;
+
+pub const SystemSpec = system.Spec;
+pub const validateSystem = system.validate;
diff --git a/src/contract/storage.zig b/src/contract/storage.zig
new file mode 100644
index 0000000..fa0817b
--- /dev/null
+++ b/src/contract/storage.zig
@@ -0,0 +1,30 @@
+pub const Spec = struct {
+ name: []const u8,
+
+ /// Minimum accepted storage size.
+ min_size: usize,
+
+ /// Maximum accepted storage size.
+ max_size: usize,
+
+ persistent: bool = true,
+ removable: bool = false,
+ writable: bool = true,
+};
+
+/// Borrowed view of persistent system storage.
+///
+/// `generation` should change whenever its contents are modified.
+/// A frontend can use this to avoid unnecessarily writing unchanged
+/// save data to disk.
+pub const View = struct {
+ data: []const u8,
+
+ generation: u64 = 0,
+};
+
+pub const LoadError = error{
+ InvalidSlot,
+ InvalidSize,
+ ReadOnly,
+};
diff --git a/src/contract/system.zig b/src/contract/system.zig
new file mode 100644
index 0000000..1d18877
--- /dev/null
+++ b/src/contract/system.zig
@@ -0,0 +1,132 @@
+const video = @import("video.zig");
+const audio = @import("audio.zig");
+const input = @import("input.zig");
+const storage = @import("storage.zig");
+
+pub const Spec = struct {
+ name: []const u8,
+
+ video_outputs: []const video.VideoOutputSpec = &.{},
+
+ audio_outputs: []const audio.StreamSpec = &.{},
+ audio_inputs: []const audio.StreamSpec = &.{},
+
+ input_devices: []const input.DeviceSpec = &.{},
+
+ storage_devices: []const storage.Spec = &.{},
+};
+
+pub fn validate(comptime System: type) void {
+ comptime {
+ if (!@hasDecl(System, "spec")) {
+ @compileError(
+ "System must expose `pub const spec: contract.SystemSpec`",
+ );
+ }
+
+ if (@TypeOf(System.spec) != Spec) {
+ @compileError(
+ "`System.spec` must have type contract.SystemSpec",
+ );
+ }
+
+ requireFunction(
+ System,
+ "reset",
+ fn (*System) void,
+ );
+
+ requireFunction(
+ System,
+ "runFrame",
+ fn (*System) void,
+ );
+
+ if (System.spec.video_outputs.len != 0) {
+ requireFunction(
+ System,
+ "videoFrame",
+ fn (*const System, usize) video.VideoFrame,
+ );
+ }
+
+ if (System.spec.audio_outputs.len != 0) {
+ requireFunction(
+ System,
+ "audioBuffer",
+ fn (*const System, usize) audio.Buffer,
+ );
+ }
+
+ if (System.spec.audio_inputs.len != 0) {
+ requireFunction(
+ System,
+ "submitAudioInput",
+ fn (*System, usize, audio.Buffer) void,
+ );
+ }
+
+ if (System.spec.input_devices.len != 0) {
+ requireFunction(
+ System,
+ "setInput",
+ fn (*System, usize, input.DeviceInput) void,
+ );
+ }
+
+ var has_haptics = false;
+
+ for (System.spec.input_devices) |device| {
+ if (device.haptic_count != 0) {
+ has_haptics = true;
+ break;
+ }
+ }
+
+ if (has_haptics) {
+ requireFunction(
+ System,
+ "deviceFeedback",
+ fn (*const System, usize) input.DeviceFeedback,
+ );
+ }
+
+ if (System.spec.storage_devices.len != 0) {
+ requireFunction(
+ System,
+ "storageView",
+ fn (*const System, usize) storage.View,
+ );
+
+ requireFunction(
+ System,
+ "loadStorage",
+ fn (
+ *System,
+ usize,
+ []const u8,
+ ) storage.LoadError!void,
+ );
+ }
+ }
+}
+
+fn requireFunction(
+ comptime System: type,
+ comptime name: []const u8,
+ comptime Expected: type,
+) void {
+ if (!@hasDecl(System, name)) {
+ @compileError(
+ "System must implement `" ++ name ++ "`",
+ );
+ }
+
+ const Actual = @TypeOf(@field(System, name));
+
+ if (Actual != Expected) {
+ @compileError(
+ "`" ++ name ++ "` has an invalid signature",
+ );
+ }
+}
diff --git a/src/contract/video.zig b/src/contract/video.zig
new file mode 100644
index 0000000..0f5580c
--- /dev/null
+++ b/src/contract/video.zig
@@ -0,0 +1,69 @@
+const common = @import("common.zig");
+
+pub const PixelFormat = enum {
+ indexed8,
+
+ rgb565,
+ rgb555,
+ rgba5551,
+
+ rgb888,
+ rgba8888,
+ bgra8888,
+};
+
+pub const ScanMode = enum {
+ progressive,
+ interlaced,
+};
+
+pub const Field = enum {
+ none,
+ even,
+ odd,
+};
+
+/// Static capabilities of one video output.
+pub const VideoOutputSpec = struct {
+ max_width: u16,
+ max_height: u16,
+
+ /// Format exposed by this system core to the frontend.
+ format: PixelFormat,
+
+ /// Nominal display aspect ratio.
+ aspect_ratio: common.Rational,
+
+ /// Nominal refresh rate of this output.
+ refresh_rate: common.Rational,
+};
+
+/// One produced video frame.
+///
+/// The system owns the backing memory. The frontend only borrows it.
+pub const VideoFrame = struct {
+ data: []const u8,
+
+ width: u16,
+ height: u16,
+
+ /// Number of bytes between the beginning of consecutive rows.
+ pitch: usize,
+
+ format: PixelFormat,
+
+ scan_mode: ScanMode = .progressive,
+ field: Field = .none,
+
+ /// Required when `format == .indexed8`.
+ ///
+ /// Each entry is RGBA8888.
+ palette: ?[]const u32 = null,
+
+ /// Runtime display aspect ratio.
+ ///
+ /// Null means use VideoOutputSpec.aspect_ratio.
+ aspect_ratio: ?common.Rational = null,
+
+ frame_number: u64 = 0,
+};
diff --git a/src/cpu/m6502/addressing.zig b/src/cpu/m6502/addressing.zig
index f5fcb2d..2b8435a 100644
--- a/src/cpu/m6502/addressing.zig
+++ b/src/cpu/m6502/addressing.zig
@@ -10,7 +10,8 @@ pub const addRelative = utils.addRelative;
pub const pageCrossed = utils.pageCrossed;
pub const bitNumber = utils.bitNumber;
-pub fn readOperand(cpu: anytype, mode: AddressingMode) u8 {
+// comptime mode parameter evaluates addressing switch at compile-time
+pub inline fn readOperand(cpu: anytype, comptime mode: AddressingMode) u8 {
return switch (mode) {
.immediate => cpu.fetchByte(),
@@ -52,7 +53,7 @@ pub fn readOperand(cpu: anytype, mode: AddressingMode) u8 {
};
}
-pub fn readAbsoluteIndexed(cpu: anytype, index: u8) u8 {
+pub inline fn readAbsoluteIndexed(cpu: anytype, index: u8) u8 {
const low = cpu.fetchByte();
const high = cpu.fetchByte();
const base = utils.makeWord(low, high);
@@ -70,7 +71,7 @@ pub fn readAbsoluteIndexed(cpu: anytype, index: u8) u8 {
return cpu.readCycle(address);
}
-pub fn readIndexedIndirectX(cpu: anytype) u8 {
+pub inline fn readIndexedIndirectX(cpu: anytype) u8 {
const operand = cpu.fetchByte();
_ = cpu.readCycle(operand);
const pointer = operand +% cpu.registers.x;
@@ -80,7 +81,7 @@ pub fn readIndexedIndirectX(cpu: anytype) u8 {
return cpu.readCycle(utils.makeWord(low, high));
}
-pub fn readIndirectIndexedY(cpu: anytype) u8 {
+pub inline fn readIndirectIndexedY(cpu: anytype) u8 {
const pointer = cpu.fetchByte();
const low = cpu.readCycle(pointer);
const high = cpu.readCycle(pointer +% 1);
@@ -100,7 +101,8 @@ pub fn readIndirectIndexedY(cpu: anytype) u8 {
return cpu.readCycle(address);
}
-pub fn writeOperand(cpu: anytype, mode: AddressingMode, value: u8) void {
+// comptime mode parameter evaluates addressing switch at compile-time
+pub inline fn writeOperand(cpu: anytype, comptime mode: AddressingMode, value: u8) void {
switch (mode) {
.zero_page => {
const address = cpu.fetchByte();
@@ -140,7 +142,7 @@ pub fn writeOperand(cpu: anytype, mode: AddressingMode, value: u8) void {
}
}
-pub fn writeAbsoluteIndexed(cpu: anytype, index: u8, value: u8) void {
+pub inline fn writeAbsoluteIndexed(cpu: anytype, index: u8, value: u8) void {
const low = cpu.fetchByte();
const high = cpu.fetchByte();
const base = utils.makeWord(low, high);
@@ -152,7 +154,7 @@ pub fn writeAbsoluteIndexed(cpu: anytype, index: u8, value: u8) void {
cpu.writeCycle(address, value);
}
-pub fn writeIndexedIndirectX(cpu: anytype, value: u8) void {
+pub inline fn writeIndexedIndirectX(cpu: anytype, value: u8) void {
const operand = cpu.fetchByte();
_ = cpu.readCycle(operand);
const pointer = operand +% cpu.registers.x;
@@ -162,7 +164,7 @@ pub fn writeIndexedIndirectX(cpu: anytype, value: u8) void {
cpu.writeCycle(utils.makeWord(low, high), value);
}
-pub fn writeIndirectIndexedY(cpu: anytype, value: u8) void {
+pub inline fn writeIndirectIndexedY(cpu: anytype, value: u8) void {
const pointer = cpu.fetchByte();
const low = cpu.readCycle(pointer);
const high = cpu.readCycle(pointer +% 1);
@@ -176,7 +178,7 @@ pub fn writeIndirectIndexedY(cpu: anytype, value: u8) void {
cpu.writeCycle(address, value);
}
-pub fn modifyAddress(
+pub inline fn modifyAddress(
cpu: anytype,
address: u16,
comptime operation: fn (*@TypeOf(cpu.*), u8) u8,
@@ -193,7 +195,7 @@ pub fn modifyAddress(
cpu.writeCycle(address, new);
}
-pub fn modifyAbsoluteIndexed(
+pub inline fn modifyAbsoluteIndexed(
cpu: anytype,
index: u8,
comptime operation: fn (*@TypeOf(cpu.*), u8) u8,
@@ -215,7 +217,7 @@ pub fn modifyAbsoluteIndexed(
modifyAddress(cpu, address, operation);
}
-pub fn modifyIndexedIndirectX(
+pub inline fn modifyIndexedIndirectX(
cpu: anytype,
comptime operation: fn (*@TypeOf(cpu.*), u8) u8,
) void {
@@ -228,7 +230,7 @@ pub fn modifyIndexedIndirectX(
modifyAddress(cpu, utils.makeWord(low, high), operation);
}
-pub fn modifyIndirectIndexedY(
+pub inline fn modifyIndirectIndexedY(
cpu: anytype,
comptime operation: fn (*@TypeOf(cpu.*), u8) u8,
) void {
diff --git a/src/cpu/m6502/alu.zig b/src/cpu/m6502/alu.zig
index 784d974..f4ce4ee 100644
--- a/src/cpu/m6502/alu.zig
+++ b/src/cpu/m6502/alu.zig
@@ -4,15 +4,20 @@ const Instruction = @import("instruction.zig");
// pure ALU arithmetic & bitwise operations monomorphized for Cpu
const Alu = @This();
+// single-pass NZ flag update via setNZ mask
pub inline fn updateNZ(cpu: anytype, value: u8) void {
- cpu.registers.status.zero = (value == 0);
- cpu.registers.status.negative = (value & 0x80) != 0;
+ cpu.registers.status.setNZ(value);
}
+// single-pass batched status flag updates for compare, shifts, and arithmetic
pub fn compare(cpu: anytype, register: u8, value: u8) void {
const result = register -% value;
- cpu.registers.status.carry = register >= value;
- updateNZ(cpu, result);
+ const c: u8 = if (register >= value) 0x01 else 0;
+ const z: u8 = if (result == 0) 0x02 else 0;
+ const n: u8 = result & 0x80;
+ var b: u8 = @bitCast(cpu.registers.status);
+ b = (b & ~@as(u8, 0x83)) | c | z | n;
+ cpu.registers.status = @bitCast(b);
}
pub fn adc(cpu: anytype, value: u8) void {
@@ -27,19 +32,29 @@ pub fn adc(cpu: anytype, value: u8) void {
if (decimal and cpu.model.features().cmos_instructions) cpu.tick();
- const out: struct { value: u8, carry: bool } = if (decimal) decimal: {
- const low_sum = @as(u16, accumulator & 0x0f) + @as(u16, value & 0x0f) + carry;
- var adjusted = sum;
- if (low_sum > 9) adjusted += 6;
- const carry_out = adjusted > 0x99;
- if (carry_out) adjusted += 0x60;
- break :decimal .{ .value = @truncate(adjusted), .carry = carry_out };
- } else .{ .value = binary_result, .carry = sum > 0xff };
+ if (!decimal) {
+ const c: u8 = if (sum > 0xff) 0x01 else 0;
+ const z: u8 = if (binary_result == 0) 0x02 else 0;
+ const v: u8 = if (overflow) 0x40 else 0;
+ const n: u8 = binary_result & 0x80;
+ var b: u8 = @bitCast(cpu.registers.status);
+ b = (b & ~@as(u8, 0xc3)) | c | z | v | n;
+ cpu.registers.status = @bitCast(b);
+ cpu.registers.a = binary_result;
+ return;
+ }
- cpu.registers.status.carry = out.carry;
+ const low_sum = @as(u16, accumulator & 0x0f) + @as(u16, value & 0x0f) + carry;
+ var adjusted = sum;
+ if (low_sum > 9) adjusted += 6;
+ const carry_out = adjusted > 0x99;
+ if (carry_out) adjusted += 0x60;
+ const dec_val: u8 = @truncate(adjusted);
+
+ cpu.registers.status.carry = carry_out;
cpu.registers.status.overflow = overflow;
- cpu.registers.a = out.value;
- updateNZ(cpu, out.value);
+ cpu.registers.a = dec_val;
+ updateNZ(cpu, dec_val);
}
pub fn sbc(cpu: anytype, value: u8) void {
@@ -54,49 +69,74 @@ pub fn sbc(cpu: anytype, value: u8) void {
if (decimal and cpu.model.features().cmos_instructions) cpu.tick();
- const result: u8 = if (decimal) decimal: {
- var low: i16 = @as(i16, accumulator & 0x0f) - @as(i16, value & 0x0f) - @as(i16, @intCast(borrow));
- var high: i16 = @as(i16, accumulator >> 4) - @as(i16, value >> 4);
- if (low < 0) {
- low -= 6;
- high -= 1;
- }
- if (high < 0) high -= 6;
- break :decimal (@as(u8, @intCast(high & 0x0f)) << 4) | @as(u8, @intCast(low & 0x0f));
- } else binary_result;
+ if (!decimal) {
+ const c: u8 = if (no_borrow) 0x01 else 0;
+ const z: u8 = if (binary_result == 0) 0x02 else 0;
+ const v: u8 = if (overflow) 0x40 else 0;
+ const n: u8 = binary_result & 0x80;
+ var b: u8 = @bitCast(cpu.registers.status);
+ b = (b & ~@as(u8, 0xc3)) | c | z | v | n;
+ cpu.registers.status = @bitCast(b);
+ cpu.registers.a = binary_result;
+ return;
+ }
+
+ var low: i16 = @as(i16, accumulator & 0x0f) - @as(i16, value & 0x0f) - @as(i16, @intCast(borrow));
+ var high: i16 = @as(i16, accumulator >> 4) - @as(i16, value >> 4);
+ if (low < 0) {
+ low -= 6;
+ high -= 1;
+ }
+ if (high < 0) high -= 6;
+ const dec_val: u8 = (@as(u8, @intCast(high & 0x0f)) << 4) | @as(u8, @intCast(low & 0x0f));
cpu.registers.status.carry = no_borrow;
cpu.registers.status.overflow = overflow;
- cpu.registers.a = result;
- updateNZ(cpu, result);
+ cpu.registers.a = dec_val;
+ updateNZ(cpu, dec_val);
}
pub fn asl(cpu: anytype, value: u8) u8 {
- cpu.registers.status.carry = (value & 0x80) != 0;
+ const carry: u8 = (value >> 7);
const result = value << 1;
- updateNZ(cpu, result);
+ const z: u8 = if (result == 0) 0x02 else 0;
+ const n: u8 = result & 0x80;
+ var b: u8 = @bitCast(cpu.registers.status);
+ b = (b & ~@as(u8, 0x83)) | carry | z | n;
+ cpu.registers.status = @bitCast(b);
return result;
}
pub fn lsr(cpu: anytype, value: u8) u8 {
- cpu.registers.status.carry = (value & 1) != 0;
+ const carry: u8 = value & 1;
const result = value >> 1;
- updateNZ(cpu, result);
+ const z: u8 = if (result == 0) 0x02 else 0;
+ var b: u8 = @bitCast(cpu.registers.status);
+ b = (b & ~@as(u8, 0x83)) | carry | z;
+ cpu.registers.status = @bitCast(b);
return result;
}
pub fn rol(cpu: anytype, value: u8) u8 {
const carry: u8 = @intFromBool(cpu.registers.status.carry);
- cpu.registers.status.carry = (value & 0x80) != 0;
+ const carry_out: u8 = (value >> 7);
const result = (value << 1) | carry;
- updateNZ(cpu, result);
+ const z: u8 = if (result == 0) 0x02 else 0;
+ const n: u8 = result & 0x80;
+ var b: u8 = @bitCast(cpu.registers.status);
+ b = (b & ~@as(u8, 0x83)) | carry_out | z | n;
+ cpu.registers.status = @bitCast(b);
return result;
}
pub fn ror(cpu: anytype, value: u8) u8 {
const carry: u8 = if (cpu.registers.status.carry) 0x80 else 0;
- cpu.registers.status.carry = (value & 1) != 0;
+ const carry_out: u8 = value & 1;
const result = (value >> 1) | carry;
- updateNZ(cpu, result);
+ const z: u8 = if (result == 0) 0x02 else 0;
+ const n: u8 = result & 0x80;
+ var b: u8 = @bitCast(cpu.registers.status);
+ b = (b & ~@as(u8, 0x83)) | carry_out | z | n;
+ cpu.registers.status = @bitCast(b);
return result;
}
diff --git a/src/cpu/m6502/conformance.zig b/src/cpu/m6502/conformance.zig
index 0e89999..16e0457 100644
--- a/src/cpu/m6502/conformance.zig
+++ b/src/cpu/m6502/conformance.zig
@@ -36,20 +36,39 @@ pub fn CycleCpu(comptime model: Model) type {
return root.Cpu(TestBus, model, .cycle);
}
-pub const functional_test_bin = @embedFile("conformance/6502_functional_test.bin");
-pub const cmos_extended_test_bin = @embedFile("conformance/65C02_extended_opcodes_test.bin");
-pub const nestest_nes = @embedFile("conformance/nestest.nes");
+const build_options = @import("build_options");
+
+fn loadFuncTestRomAlloc(allocator: std.mem.Allocator, relative_path: []const u8) ![]u8 {
+ const base_dir = build_options.m6502_functional_tests_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));
+}
+
+fn loadNesTestRomAlloc(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));
+}
+
+const functional_test_bin_path = "bin_files/6502_functional_test.bin";
+const cmos_extended_test_bin_path = "bin_files/65C02_extended_opcodes_test.bin";
/// Runs a binary ROM conformance test against a CPU model and memory harness.
pub fn runBinaryTest(
comptime model: Model,
comptime accuracy: Accuracy,
- rom_data: []const u8,
+ relative_path: []const u8,
load_address: u16,
start_pc: u16,
success_pc: u16,
max_instructions: u64,
) !void {
+ if (build_options.m6502_functional_tests_dir == null) return error.SkipZigTest;
+ const rom_data = try loadFuncTestRomAlloc(testing.allocator, relative_path);
+ defer testing.allocator.free(rom_data);
+
var bus = TestBus{};
@memcpy(bus.ram[load_address .. load_address + rom_data.len], rom_data);
@@ -92,6 +111,10 @@ pub fn runNesTest(
comptime accuracy: Accuracy,
max_instructions: u64,
) !void {
+ if (build_options.nes_test_roms_dir == null) return error.SkipZigTest;
+ const nestest_nes = try loadNesTestRomAlloc(testing.allocator, "other/nestest.nes");
+ defer testing.allocator.free(nestest_nes);
+
var bus = TestBus{};
// PRG ROM is 16KB starting at byte 16 (after 16-byte iNES header)
const prg = nestest_nes[16 .. 16 + 16384];
@@ -128,7 +151,7 @@ test "MOS 6502 Functional Test (Instruction Accuracy)" {
try runBinaryTest(
.mos6502,
.instruction,
- functional_test_bin,
+ functional_test_bin_path,
0x0000,
0x0400,
0x3469,
@@ -140,7 +163,7 @@ test "MOS 6502 Functional Test (Cycle Accuracy)" {
try runBinaryTest(
.mos6502,
.cycle,
- functional_test_bin,
+ functional_test_bin_path,
0x0000,
0x0400,
0x3469,
@@ -163,7 +186,7 @@ test "MOS 6510 Functional Test (Instruction Accuracy)" {
try runBinaryTest(
.mos6510,
.instruction,
- functional_test_bin,
+ functional_test_bin_path,
0x0000,
0x0400,
0x3469,
@@ -175,7 +198,7 @@ test "MOS 6510 Functional Test (Cycle Accuracy)" {
try runBinaryTest(
.mos6510,
.cycle,
- functional_test_bin,
+ functional_test_bin_path,
0x0000,
0x0400,
0x3469,
@@ -205,7 +228,7 @@ test "WDC 65C02 Extended Opcodes Test (Instruction Accuracy)" {
try runBinaryTest(
.wdc65c02,
.instruction,
- cmos_extended_test_bin,
+ cmos_extended_test_bin_path,
0x0000,
0x0400,
0x24f1,
@@ -217,7 +240,7 @@ test "WDC 65C02 Extended Opcodes Test (Cycle Accuracy)" {
try runBinaryTest(
.wdc65c02,
.cycle,
- cmos_extended_test_bin,
+ cmos_extended_test_bin_path,
0x0000,
0x0400,
0x24f1,
@@ -230,7 +253,7 @@ test "Rockwell R65C02 Extended Opcodes Test (Instruction Accuracy)" {
try runBinaryTest(
.r65c02,
.instruction,
- cmos_extended_test_bin,
+ cmos_extended_test_bin_path,
0x0000,
0x0400,
0x24f1,
@@ -242,7 +265,7 @@ test "Rockwell R65C02 Extended Opcodes Test (Cycle Accuracy)" {
try runBinaryTest(
.r65c02,
.cycle,
- cmos_extended_test_bin,
+ cmos_extended_test_bin_path,
0x0000,
0x0400,
0x24f1,
@@ -250,6 +273,7 @@ test "Rockwell R65C02 Extended Opcodes Test (Cycle Accuracy)" {
);
}
+
// Save-State & Debug Hook Tests
test "Cpu Save-State Serialization (CpuSnapshot)" {
var bus = TestBus{};
@@ -276,11 +300,11 @@ test "Falling-Edge NMI Latching" {
// Initial high level
cpu.setNmi(true);
- try testing.expect(!cpu.nmi_latch);
+ try testing.expect(!cpu.flags.nmi_latch);
// Falling edge high -> low
cpu.setNmi(false);
- try testing.expect(cpu.nmi_latch);
+ try testing.expect(cpu.flags.nmi_latch);
}
test "Zero-Overhead Debug Hook (CpuWithHook)" {
diff --git a/src/cpu/m6502/conformance/6502_functional_test.bin b/src/cpu/m6502/conformance/6502_functional_test.bin
deleted file mode 100644
index c9a35e1..0000000
Binary files a/src/cpu/m6502/conformance/6502_functional_test.bin and /dev/null differ
diff --git a/src/cpu/m6502/conformance/65C02_extended_opcodes_test.bin b/src/cpu/m6502/conformance/65C02_extended_opcodes_test.bin
deleted file mode 100644
index b1ea946..0000000
Binary files a/src/cpu/m6502/conformance/65C02_extended_opcodes_test.bin and /dev/null differ
diff --git a/src/cpu/m6502/conformance/LICENCE b/src/cpu/m6502/conformance/LICENCE
deleted file mode 100644
index 94a9ed0..0000000
--- a/src/cpu/m6502/conformance/LICENCE
+++ /dev/null
@@ -1,674 +0,0 @@
- GNU GENERAL PUBLIC LICENSE
- Version 3, 29 June 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc.
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The GNU General Public License is a free, copyleft license for
-software and other kinds of works.
-
- The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works. By contrast,
-the GNU General Public License is intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users. We, the Free Software Foundation, use the
-GNU General Public License for most of our software; it applies also to
-any other work released this way by its authors. You can apply it to
-your programs, too.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
- To protect your rights, we need to prevent others from denying you
-these rights or asking you to surrender the rights. Therefore, you have
-certain responsibilities if you distribute copies of the software, or if
-you modify it: responsibilities to respect the freedom of others.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must pass on to the recipients the same
-freedoms that you received. You must make sure that they, too, receive
-or can get the source code. And you must show them these terms so they
-know their rights.
-
- Developers that use the GNU GPL protect your rights with two steps:
-(1) assert copyright on the software, and (2) offer you this License
-giving you legal permission to copy, distribute and/or modify it.
-
- For the developers' and authors' protection, the GPL clearly explains
-that there is no warranty for this free software. For both users' and
-authors' sake, the GPL requires that modified versions be marked as
-changed, so that their problems will not be attributed erroneously to
-authors of previous versions.
-
- Some devices are designed to deny users access to install or run
-modified versions of the software inside them, although the manufacturer
-can do so. This is fundamentally incompatible with the aim of
-protecting users' freedom to change the software. The systematic
-pattern of such abuse occurs in the area of products for individuals to
-use, which is precisely where it is most unacceptable. Therefore, we
-have designed this version of the GPL to prohibit the practice for those
-products. If such problems arise substantially in other domains, we
-stand ready to extend this provision to those domains in future versions
-of the GPL, as needed to protect the freedom of users.
-
- Finally, every program is threatened constantly by software patents.
-States should not allow patents to restrict development and use of
-software on general-purpose computers, but in those that do, we wish to
-avoid the special danger that patents applied to a free program could
-make it effectively proprietary. To prevent this, the GPL assures that
-patents cannot be used to render the program non-free.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- TERMS AND CONDITIONS
-
- 0. Definitions.
-
- "This License" refers to version 3 of the GNU General Public License.
-
- "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
- "The Program" refers to any copyrightable work licensed under this
-License. Each licensee is addressed as "you". "Licensees" and
-"recipients" may be individuals or organizations.
-
- To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy. The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
- A "covered work" means either the unmodified Program or a work based
-on the Program.
-
- To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy. Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
- To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies. Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
- An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License. If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
- 1. Source Code.
-
- The "source code" for a work means the preferred form of the work
-for making modifications to it. "Object code" means any non-source
-form of a work.
-
- A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
- The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form. A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
- The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities. However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work. For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
- The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
- The Corresponding Source for a work in source code form is that
-same work.
-
- 2. Basic Permissions.
-
- All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met. This License explicitly affirms your unlimited
-permission to run the unmodified Program. The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work. This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
- You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force. You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright. Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
- Conveying under any other circumstances is permitted solely under
-the conditions stated below. Sublicensing is not allowed; section 10
-makes it unnecessary.
-
- 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
- No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
- When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
- 4. Conveying Verbatim Copies.
-
- You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
- You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
- 5. Conveying Modified Source Versions.
-
- You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
- a) The work must carry prominent notices stating that you modified
- it, and giving a relevant date.
-
- b) The work must carry prominent notices stating that it is
- released under this License and any conditions added under section
- 7. This requirement modifies the requirement in section 4 to
- "keep intact all notices".
-
- c) You must license the entire work, as a whole, under this
- License to anyone who comes into possession of a copy. This
- License will therefore apply, along with any applicable section 7
- additional terms, to the whole of the work, and all its parts,
- regardless of how they are packaged. This License gives no
- permission to license the work in any other way, but it does not
- invalidate such permission if you have separately received it.
-
- d) If the work has interactive user interfaces, each must display
- Appropriate Legal Notices; however, if the Program has interactive
- interfaces that do not display Appropriate Legal Notices, your
- work need not make them do so.
-
- A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit. Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
- 6. Conveying Non-Source Forms.
-
- You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
- a) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by the
- Corresponding Source fixed on a durable physical medium
- customarily used for software interchange.
-
- b) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by a
- written offer, valid for at least three years and valid for as
- long as you offer spare parts or customer support for that product
- model, to give anyone who possesses the object code either (1) a
- copy of the Corresponding Source for all the software in the
- product that is covered by this License, on a durable physical
- medium customarily used for software interchange, for a price no
- more than your reasonable cost of physically performing this
- conveying of source, or (2) access to copy the
- Corresponding Source from a network server at no charge.
-
- c) Convey individual copies of the object code with a copy of the
- written offer to provide the Corresponding Source. This
- alternative is allowed only occasionally and noncommercially, and
- only if you received the object code with such an offer, in accord
- with subsection 6b.
-
- d) Convey the object code by offering access from a designated
- place (gratis or for a charge), and offer equivalent access to the
- Corresponding Source in the same way through the same place at no
- further charge. You need not require recipients to copy the
- Corresponding Source along with the object code. If the place to
- copy the object code is a network server, the Corresponding Source
- may be on a different server (operated by you or a third party)
- that supports equivalent copying facilities, provided you maintain
- clear directions next to the object code saying where to find the
- Corresponding Source. Regardless of what server hosts the
- Corresponding Source, you remain obligated to ensure that it is
- available for as long as needed to satisfy these requirements.
-
- e) Convey the object code using peer-to-peer transmission, provided
- you inform other peers where the object code and Corresponding
- Source of the work are being offered to the general public at no
- charge under subsection 6d.
-
- A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
- A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling. In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage. For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product. A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
- "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source. The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
- If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information. But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
- The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed. Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
- Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
- 7. Additional Terms.
-
- "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law. If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
- When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it. (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.) You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
- Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
- a) Disclaiming warranty or limiting liability differently from the
- terms of sections 15 and 16 of this License; or
-
- b) Requiring preservation of specified reasonable legal notices or
- author attributions in that material or in the Appropriate Legal
- Notices displayed by works containing it; or
-
- c) Prohibiting misrepresentation of the origin of that material, or
- requiring that modified versions of such material be marked in
- reasonable ways as different from the original version; or
-
- d) Limiting the use for publicity purposes of names of licensors or
- authors of the material; or
-
- e) Declining to grant rights under trademark law for use of some
- trade names, trademarks, or service marks; or
-
- f) Requiring indemnification of licensors and authors of that
- material by anyone who conveys the material (or modified versions of
- it) with contractual assumptions of liability to the recipient, for
- any liability that these contractual assumptions directly impose on
- those licensors and authors.
-
- All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10. If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term. If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
- If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
- Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
- 8. Termination.
-
- You may not propagate or modify a covered work except as expressly
-provided under this License. Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
- However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
- Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
- Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License. If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
- 9. Acceptance Not Required for Having Copies.
-
- You are not required to accept this License in order to receive or
-run a copy of the Program. Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance. However,
-nothing other than this License grants you permission to propagate or
-modify any covered work. These actions infringe copyright if you do
-not accept this License. Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
- 10. Automatic Licensing of Downstream Recipients.
-
- Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License. You are not responsible
-for enforcing compliance by third parties with this License.
-
- An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations. If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
- You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License. For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
- 11. Patents.
-
- A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based. The
-work thus licensed is called the contributor's "contributor version".
-
- A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version. For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
- Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
- In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement). To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
- If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients. "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
- If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
- A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License. You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
- Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
- 12. No Surrender of Others' Freedom.
-
- If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all. For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
- 13. Use with the GNU Affero General Public License.
-
- Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU Affero General Public License into a single
-combined work, and to convey the resulting work. The terms of this
-License will continue to apply to the part which is the covered work,
-but the special requirements of the GNU Affero General Public License,
-section 13, concerning interaction through a network will apply to the
-combination as such.
-
- 14. Revised Versions of this License.
-
- The Free Software Foundation may publish revised and/or new versions of
-the GNU General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
- Each version is given a distinguishing version number. If the
-Program specifies that a certain numbered version of the GNU General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation. If the Program does not specify a version number of the
-GNU General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
- If the Program specifies that a proxy can decide which future
-versions of the GNU General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
- Later license versions may give you additional or different
-permissions. However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
- 15. Disclaimer of Warranty.
-
- THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
- 16. Limitation of Liability.
-
- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
- 17. Interpretation of Sections 15 and 16.
-
- If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-state the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-
- Copyright (C)
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-
-Also add information on how to contact you by electronic and paper mail.
-
- If the program does terminal interaction, make it output a short
-notice like this when it starts in an interactive mode:
-
- Copyright (C)
- This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, your program's commands
-might be different; for a GUI interface, you would use an "about box".
-
- You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU GPL, see
-.
-
- The GNU General Public License does not permit incorporating your program
-into proprietary programs. If your program is a subroutine library, you
-may consider it more useful to permit linking proprietary applications with
-the library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License. But first, please read
-.
diff --git a/src/cpu/m6502/conformance/nestest.nes b/src/cpu/m6502/conformance/nestest.nes
deleted file mode 100644
index fc2a88c..0000000
Binary files a/src/cpu/m6502/conformance/nestest.nes and /dev/null differ
diff --git a/src/cpu/m6502/root.zig b/src/cpu/m6502/root.zig
index 42d7506..dd0741b 100644
--- a/src/cpu/m6502/root.zig
+++ b/src/cpu/m6502/root.zig
@@ -18,11 +18,20 @@ pub const Vector = enum(u16) {
irq = 0xfffe,
};
-const CpuState = enum {
- running,
- waiting,
- stopped,
- jammed,
+const CpuState = enum(u4) {
+ running = 0,
+ waiting = 1,
+ stopped = 2,
+ jammed = 3,
+};
+
+// pack interrupt bools + state into one byte for single-load hot-path check
+const CpuFlags = packed struct(u8) {
+ nmi_line: bool = true,
+ nmi_latch: bool = false,
+ nmi_pending: bool = false,
+ irq_asserted: bool = false,
+ state: CpuState = .running,
};
pub const Accuracy = enum {
@@ -33,11 +42,7 @@ pub const Accuracy = enum {
pub const CpuSnapshot = struct {
registers: Registers,
cycles: u64,
- state: CpuState,
- nmi_line: bool,
- nmi_latch: bool,
- nmi_pending: bool,
- irq_asserted: bool,
+ flags: CpuFlags,
};
pub fn Cpu(
@@ -74,12 +79,8 @@ pub fn CpuWithHook(
bus: *Bus,
registers: Registers = .{},
cycles: u64 = 0,
- state: CpuState = .running,
-
- nmi_line: bool = false,
- nmi_latch: bool = false,
- nmi_pending: bool = false,
- irq_asserted: bool = false,
+ // single byte holds all interrupt + state flags
+ flags: CpuFlags = .{},
const Self = @This();
@@ -94,38 +95,29 @@ pub fn CpuWithHook(
return .{
.registers = self.registers,
.cycles = self.cycles,
- .state = self.state,
- .nmi_line = self.nmi_line,
- .nmi_latch = self.nmi_latch,
- .nmi_pending = self.nmi_pending,
- .irq_asserted = self.irq_asserted,
+ .flags = self.flags,
};
}
pub fn loadState(self: *Self, snapshot: CpuSnapshot) void {
self.registers = snapshot.registers;
self.cycles = snapshot.cycles;
- self.state = snapshot.state;
- self.nmi_line = snapshot.nmi_line;
- self.nmi_latch = snapshot.nmi_latch;
- self.nmi_pending = snapshot.nmi_pending;
- self.irq_asserted = snapshot.irq_asserted;
+ self.flags = snapshot.flags;
}
- pub fn setNmi(self: *Self, active: bool) void {
- if (self.nmi_line and !active) {
- self.nmi_latch = true;
+ pub inline fn setNmi(self: *Self, active: bool) void {
+ if (self.flags.nmi_line and !active) {
+ self.flags.nmi_latch = true;
}
- self.nmi_line = active;
+ self.flags.nmi_line = active;
+ }
+
+ pub inline fn setIrq(self: *Self, active: bool) void {
+ self.flags.irq_asserted = active;
}
pub fn reset(self: *Self) void {
- self.state = .running;
-
- self.nmi_line = false;
- self.nmi_latch = false;
- self.nmi_pending = false;
- self.irq_asserted = false;
+ self.flags = .{};
self.cycles = 0;
self.registers.status.interrupt_disable = true;
@@ -152,64 +144,78 @@ pub fn CpuWithHook(
self.registers.pc = utils.makeWord(low, high);
}
+ // single byte test for interrupt/state check
pub fn step(self: *Self) ?u8 {
- if (self.state == .stopped or
- self.state == .jammed)
- {
- return null;
- }
-
const start = self.cycles;
- if (self.nmi_latch or self.nmi_pending) {
- self.nmi_latch = false;
- self.nmi_pending = false;
- self.wakeFromWait();
- self.handleInterrupt(@intFromEnum(Vector.nmi));
- return @intCast(self.cycles -% start);
- }
+ if (@as(u8, @bitCast(self.flags)) != 0) {
+ if (self.flags.state == .stopped or self.flags.state == .jammed)
+ return null;
- if (self.irq_asserted) {
- self.wakeFromWait();
-
- if (!self.registers.status.interrupt_disable) {
- self.handleInterrupt(@intFromEnum(Vector.irq));
+ if (self.flags.nmi_latch or self.flags.nmi_pending) {
+ self.flags.nmi_latch = false;
+ self.flags.nmi_pending = false;
+ self.wakeFromWait();
+ self.handleInterrupt(@intFromEnum(Vector.nmi));
return @intCast(self.cycles -% start);
}
- }
- if (self.state == .waiting)
- return null;
+ if (self.flags.irq_asserted) {
+ self.wakeFromWait();
+
+ if (!self.registers.status.interrupt_disable) {
+ self.handleInterrupt(@intFromEnum(Vector.irq));
+ return @intCast(self.cycles -% start);
+ }
+ }
+
+ if (self.flags.state == .waiting)
+ return null;
+ }
const pc_before = self.registers.pc;
const opcode = self.fetchByte();
- const instruction = instruction_table[opcode];
if (Hook != void) {
+ const instruction = instruction_table[opcode];
Hook.onStep(self, pc_before, opcode, instruction);
}
- self.execute(instruction);
+ self.executeOpcode(opcode);
return @intCast(self.cycles -% start);
}
fn wakeFromWait(self: *Self) void {
- if (self.state == .waiting)
- self.state = .running;
+ if (self.flags.state == .waiting)
+ self.flags.state = .running;
}
// Execution Dispatch
- fn execute(self: *Self, instruction: Instruction) void {
- switch (instruction.operation) {
+ // comptime single-stage opcode dispatch eliminates 2-stage runtime mode branching
+ pub fn executeOpcode(self: *Self, opcode: u8) void {
+ switch (opcode) {
+ inline 0...255 => |op| {
+ const inst = instruction_table[op];
+ self.executeInst(inst.operation, inst.mode);
+ },
+ }
+ }
+
+ fn executeInst(
+ self: *Self,
+ comptime op: Instruction.Operation,
+ comptime mode: AddressingMode,
+ ) void {
+ switch (op) {
// Load & Store Instructions
- .lda => self.executeRead(instruction.mode, Self.lda),
- .ldx => self.executeRead(instruction.mode, Self.ldx),
- .ldy => self.executeRead(instruction.mode, Self.ldy),
- .sta => self.executeWrite(instruction.mode, self.registers.a),
- .stx => self.executeWrite(instruction.mode, self.registers.x),
- .sty => self.executeWrite(instruction.mode, self.registers.y),
- .stz => self.executeWrite(instruction.mode, 0),
+ .lda => self.executeRead(mode, Self.lda),
+ .ldx => self.executeRead(mode, Self.ldx),
+ .ldy => self.executeRead(mode, Self.ldy),
+ .sta => self.executeWrite(mode, self.registers.a),
+ .stx => self.executeWrite(mode, self.registers.x),
+ .sty => self.executeWrite(mode, self.registers.y),
+ .stz => self.executeWrite(mode, 0),
// Register Transfers & Stack Operations
.tax => self.executeImplied(Self.tax),
@@ -228,29 +234,29 @@ pub fn CpuWithHook(
.ply => self.ply(),
// Arithmetic & Logical Instructions
- .adc => self.executeRead(instruction.mode, Self.adc),
- .sbc => self.executeRead(instruction.mode, Self.sbc),
- .@"and" => self.executeRead(instruction.mode, Self.@"and"),
- .ora => self.executeRead(instruction.mode, Self.ora),
- .eor => self.executeRead(instruction.mode, Self.eor),
- .bit => self.executeBit(instruction.mode),
- .cmp => self.executeCompare(instruction.mode, self.registers.a),
- .cpx => self.executeCompare(instruction.mode, self.registers.x),
- .cpy => self.executeCompare(instruction.mode, self.registers.y),
- .trb => self.executeModify(instruction.mode, Self.trb),
- .tsb => self.executeModify(instruction.mode, Self.tsb),
+ .adc => self.executeRead(mode, Self.adc),
+ .sbc => self.executeRead(mode, Self.sbc),
+ .@"and" => self.executeRead(mode, Self.@"and"),
+ .ora => self.executeRead(mode, Self.ora),
+ .eor => self.executeRead(mode, Self.eor),
+ .bit => self.executeBit(mode),
+ .cmp => self.executeCompare(mode, self.registers.a),
+ .cpx => self.executeCompare(mode, self.registers.x),
+ .cpy => self.executeCompare(mode, self.registers.y),
+ .trb => self.executeModify(mode, Self.trb),
+ .tsb => self.executeModify(mode, Self.tsb),
// Increments, Decrements & Shifts
- .inc => self.executeIncDec(instruction.mode, Self.inc),
- .dec => self.executeIncDec(instruction.mode, Self.dec),
+ .inc => self.executeIncDec(mode, Self.inc),
+ .dec => self.executeIncDec(mode, Self.dec),
.inx => self.executeImplied(Self.inx),
.dex => self.executeImplied(Self.dex),
.iny => self.executeImplied(Self.iny),
.dey => self.executeImplied(Self.dey),
- .asl => self.executeShift(instruction.mode, Self.asl),
- .lsr => self.executeShift(instruction.mode, Self.lsr),
- .rol => self.executeShift(instruction.mode, Self.rol),
- .ror => self.executeShift(instruction.mode, Self.ror),
+ .asl => self.executeShift(mode, Self.asl),
+ .lsr => self.executeShift(mode, Self.lsr),
+ .rol => self.executeShift(mode, Self.rol),
+ .ror => self.executeShift(mode, Self.ror),
// Branch & Jump Instructions
.bcc => self.branch(!self.registers.status.carry),
@@ -262,9 +268,9 @@ pub fn CpuWithHook(
.bvc => self.branch(!self.registers.status.overflow),
.bvs => self.branch(self.registers.status.overflow),
.bra => self.branch(true),
- .bbr0, .bbr1, .bbr2, .bbr3, .bbr4, .bbr5, .bbr6, .bbr7 => self.bbr(bitNumber(instruction.operation, .bbr0)),
- .bbs0, .bbs1, .bbs2, .bbs3, .bbs4, .bbs5, .bbs6, .bbs7 => self.bbs(bitNumber(instruction.operation, .bbs0)),
- .jmp => self.jmp(instruction.mode),
+ .bbr0, .bbr1, .bbr2, .bbr3, .bbr4, .bbr5, .bbr6, .bbr7 => self.bbr(bitNumber(op, .bbr0)),
+ .bbs0, .bbs1, .bbs2, .bbs3, .bbs4, .bbs5, .bbs6, .bbs7 => self.bbs(bitNumber(op, .bbs0)),
+ .jmp => self.jmp(mode),
.jsr => self.jsr(),
.rti => self.rti(),
.rts => self.rts(),
@@ -277,43 +283,43 @@ pub fn CpuWithHook(
.cld => self.executeImplied(Self.cld),
.sed => self.executeImplied(Self.sed),
.clv => self.executeImplied(Self.clv),
- .nop => self.nop(instruction.mode),
+ .nop => self.nop(mode),
.brk => self.brk(),
- .rmb0, .rmb1, .rmb2, .rmb3, .rmb4, .rmb5, .rmb6, .rmb7 => self.rmb(bitNumber(instruction.operation, .rmb0)),
- .smb0, .smb1, .smb2, .smb3, .smb4, .smb5, .smb6, .smb7 => self.smb(bitNumber(instruction.operation, .smb0)),
+ .rmb0, .rmb1, .rmb2, .rmb3, .rmb4, .rmb5, .rmb6, .rmb7 => self.rmb(bitNumber(op, .rmb0)),
+ .smb0, .smb1, .smb2, .smb3, .smb4, .smb5, .smb6, .smb7 => self.smb(bitNumber(op, .smb0)),
.wai => self.wai(),
.stp => self.stp(),
.kil => self.kil(),
// Undocumented NMOS 6502 Opcodes
- .ahx => self.ahx(instruction.mode),
- .alr => self.executeRead(instruction.mode, Self.alr),
- .anc => self.executeRead(instruction.mode, Self.anc),
- .arr => self.executeRead(instruction.mode, Self.arr),
- .axs => self.executeRead(instruction.mode, Self.axs),
- .dcp => self.executeModify(instruction.mode, Self.dcp),
- .isc => self.executeModify(instruction.mode, Self.isc),
- .las => self.executeRead(instruction.mode, Self.las),
- .lax => self.executeRead(instruction.mode, Self.lax),
- .rla => self.executeModify(instruction.mode, Self.rla),
- .rra => self.executeModify(instruction.mode, Self.rra),
+ .ahx => self.ahx(mode),
+ .alr => self.executeRead(mode, Self.alr),
+ .anc => self.executeRead(mode, Self.anc),
+ .arr => self.executeRead(mode, Self.arr),
+ .axs => self.executeRead(mode, Self.axs),
+ .dcp => self.executeModify(mode, Self.dcp),
+ .isc => self.executeModify(mode, Self.isc),
+ .las => self.executeRead(mode, Self.las),
+ .lax => self.executeRead(mode, Self.lax),
+ .rla => self.executeModify(mode, Self.rla),
+ .rra => self.executeModify(mode, Self.rra),
.sax => self.executeWrite(
- instruction.mode,
+ mode,
self.registers.a & self.registers.x,
),
- .shx => self.shx(instruction.mode),
- .shy => self.shy(instruction.mode),
- .slo => self.executeModify(instruction.mode, Self.slo),
- .sre => self.executeModify(instruction.mode, Self.sre),
- .tas => self.tas(instruction.mode),
- .xaa => self.executeRead(instruction.mode, Self.xaa),
+ .shx => self.shx(mode),
+ .shy => self.shy(mode),
+ .slo => self.executeModify(mode, Self.slo),
+ .sre => self.executeModify(mode, Self.sre),
+ .tas => self.tas(mode),
+ .xaa => self.executeRead(mode, Self.xaa),
}
}
// Execution Families
fn executeRead(
self: *Self,
- mode: AddressingMode,
+ comptime mode: AddressingMode,
comptime operation: fn (*Self, u8) void,
) void {
const value = self.readOperand(mode);
@@ -322,7 +328,7 @@ pub fn CpuWithHook(
fn executeWrite(
self: *Self,
- mode: AddressingMode,
+ comptime mode: AddressingMode,
value: u8,
) void {
self.writeOperand(mode, value);
@@ -330,7 +336,7 @@ pub fn CpuWithHook(
fn executeModify(
self: *Self,
- mode: AddressingMode,
+ comptime mode: AddressingMode,
comptime operation: fn (*Self, u8) u8,
) void {
switch (mode) {
@@ -380,7 +386,7 @@ pub fn CpuWithHook(
fn executeShift(
self: *Self,
- mode: AddressingMode,
+ comptime mode: AddressingMode,
comptime operation: fn (*Self, u8) u8,
) void {
if (mode == .accumulator) {
@@ -402,7 +408,7 @@ pub fn CpuWithHook(
fn executeIncDec(
self: *Self,
- mode: AddressingMode,
+ comptime mode: AddressingMode,
comptime operation: fn (*Self, u8) u8,
) void {
if (mode == .accumulator) {
@@ -420,12 +426,12 @@ pub fn CpuWithHook(
operation(self);
}
- fn executeCompare(self: *Self, mode: AddressingMode, register: u8) void {
+ fn executeCompare(self: *Self, comptime mode: AddressingMode, register: u8) void {
const value = self.readOperand(mode);
self.compare(register, value);
}
- fn executeBit(self: *Self, mode: AddressingMode) void {
+ fn executeBit(self: *Self, comptime mode: AddressingMode) void {
const value = self.readOperand(mode);
self.registers.status.zero =
@@ -441,76 +447,84 @@ pub fn CpuWithHook(
}
}
- // Bus Access & Fetch
- pub fn tick(self: *Self) void {
+ // bypass bus.tick() in instruction accuracy mode to eliminate unnecessary function overhead
+ pub inline fn tick(self: *Self) void {
self.cycles +%= 1;
- if (comptime @hasDecl(Bus, "tick")) {
- self.bus.tick();
+ if (accuracy == .cycle) {
+ if (comptime @hasDecl(Bus, "tick")) {
+ self.bus.tick();
+ }
+ if (comptime @hasDecl(Bus, "nmiLine")) {
+ self.setNmi(!self.bus.nmiLine());
+ }
+ if (comptime @hasDecl(Bus, "irqLine")) {
+ self.setIrq(self.bus.irqLine());
+ }
}
}
- pub fn readCycle(self: *Self, address: u16) u8 {
+ pub inline fn readCycle(self: *Self, address: u16) u8 {
const value = self.bus.read(address & address_mask);
self.tick();
return value;
}
- pub fn writeCycle(self: *Self, address: u16, value: u8) void {
+ pub inline fn writeCycle(self: *Self, address: u16, value: u8) void {
self.bus.write(address & address_mask, value);
self.tick();
}
- pub fn fetchByte(self: *Self) u8 {
+ pub inline fn fetchByte(self: *Self) u8 {
const value = self.readCycle(self.registers.pc);
self.registers.pc +%= 1;
return value;
}
- pub fn fetchWord(self: *Self) u16 {
+ pub inline fn fetchWord(self: *Self) u16 {
const low = self.fetchByte();
const high = self.fetchByte();
return utils.makeWord(low, high);
}
// Addressing Operations
- fn readOperand(self: *Self, mode: AddressingMode) u8 {
+ inline fn readOperand(self: *Self, comptime mode: AddressingMode) u8 {
return Addressing.readOperand(self, mode);
}
- fn writeOperand(self: *Self, mode: AddressingMode, value: u8) void {
+ inline fn writeOperand(self: *Self, comptime mode: AddressingMode, value: u8) void {
Addressing.writeOperand(self, mode, value);
}
- fn modifyAddress(self: *Self, address: u16, comptime operation: fn (*Self, u8) u8) void {
+ inline fn modifyAddress(self: *Self, address: u16, comptime operation: fn (*Self, u8) u8) void {
Addressing.modifyAddress(self, address, operation);
}
- fn modifyAbsoluteIndexed(self: *Self, index: u8, comptime operation: fn (*Self, u8) u8) void {
+ inline fn modifyAbsoluteIndexed(self: *Self, index: u8, comptime operation: fn (*Self, u8) u8) void {
Addressing.modifyAbsoluteIndexed(self, index, operation);
}
- fn modifyIndexedIndirectX(self: *Self, comptime operation: fn (*Self, u8) u8) void {
+ inline fn modifyIndexedIndirectX(self: *Self, comptime operation: fn (*Self, u8) u8) void {
Addressing.modifyIndexedIndirectX(self, operation);
}
- fn modifyIndirectIndexedY(self: *Self, comptime operation: fn (*Self, u8) u8) void {
+ inline fn modifyIndirectIndexedY(self: *Self, comptime operation: fn (*Self, u8) u8) void {
Addressing.modifyIndirectIndexedY(self, operation);
}
- fn bitNumber(operation: Instruction.Operation, first: Instruction.Operation) u3 {
+ inline fn bitNumber(operation: Instruction.Operation, first: Instruction.Operation) u3 {
return Addressing.bitNumber(operation, first);
}
- fn addRelative(base: u16, offset: i8) u16 {
+ inline fn addRelative(base: u16, offset: i8) u16 {
return Addressing.addRelative(base, offset);
}
- fn pageCrossed(base: u16, address: u16) bool {
+ inline fn pageCrossed(base: u16, address: u16) bool {
return Addressing.pageCrossed(base, address);
}
- fn branch(self: *Self, condition: bool) void {
+ inline fn branch(self: *Self, condition: bool) void {
const offset: i8 = @bitCast(self.fetchByte());
if (!condition)
@@ -530,7 +544,7 @@ pub fn CpuWithHook(
}
// Stack & Interrupt Primitives
- fn push(self: *Self, value: u8) void {
+ inline fn push(self: *Self, value: u8) void {
self.writeCycle(
0x0100 | @as(u16, self.registers.sp),
value,
@@ -539,7 +553,7 @@ pub fn CpuWithHook(
self.registers.sp -%= 1;
}
- fn pop(self: *Self) u8 {
+ inline fn pop(self: *Self) u8 {
self.registers.sp +%= 1;
return self.readCycle(
@@ -547,12 +561,12 @@ pub fn CpuWithHook(
);
}
- fn push16(self: *Self, value: u16) void {
+ inline fn push16(self: *Self, value: u16) void {
self.push(@truncate(value >> 8));
self.push(@truncate(value));
}
- fn pop16(self: *Self) u16 {
+ inline fn pop16(self: *Self) u16 {
const low = self.pop();
const high = self.pop();
return utils.makeWord(low, high);
@@ -605,7 +619,7 @@ pub fn CpuWithHook(
self.registers.pc = utils.makeWord(low, high);
}
- fn jmp(self: *Self, mode: AddressingMode) void {
+ fn jmp(self: *Self, comptime mode: AddressingMode) void {
switch (mode) {
.absolute => {
self.registers.pc = self.fetchWord();
@@ -664,7 +678,7 @@ pub fn CpuWithHook(
self.registers.pc = utils.makeWord(target_low, target_high);
}
- fn nop(self: *Self, mode: AddressingMode) void {
+ fn nop(self: *Self, comptime mode: AddressingMode) void {
switch (mode) {
.implied,
.accumulator,
@@ -761,25 +775,25 @@ pub fn CpuWithHook(
self.registers.pc +%= 1;
}
- // Status & Comparison Helpers
- fn updateNZ(self: *Self, value: u8) void {
- self.registers.status.zero = value == 0;
- self.registers.status.negative = value & 0x80 != 0;
+ // single-pass status helpers & ALU batched flag assignments
+ inline fn updateNZ(self: *Self, value: u8) void {
+ self.registers.status.setNZ(value);
}
- fn loadStatus(self: *Self, value: u8) void {
- self.registers.status.carry = (value & 0x01) != 0;
- self.registers.status.zero = (value & 0x02) != 0;
- self.registers.status.interrupt_disable = (value & 0x04) != 0;
- self.registers.status.decimal = (value & 0x08) != 0;
- self.registers.status.overflow = (value & 0x40) != 0;
- self.registers.status.negative = (value & 0x80) != 0;
+ inline fn loadStatus(self: *Self, value: u8) void {
+ var b = value | 0x20;
+ b &= ~@as(u8, 0x10);
+ self.registers.status = @bitCast(b);
}
- fn compare(self: *Self, register: u8, value: u8) void {
+ inline fn compare(self: *Self, register: u8, value: u8) void {
const result = register -% value;
- self.registers.status.carry = register >= value;
- self.updateNZ(result);
+ const c: u8 = if (register >= value) 0x01 else 0;
+ const z: u8 = if (result == 0) 0x02 else 0;
+ const n: u8 = result & 0x80;
+ var b: u8 = @bitCast(self.registers.status);
+ b = (b & ~@as(u8, 0x83)) | c | z | n;
+ self.registers.status = @bitCast(b);
}
// Documented Arithmetic & Logic
@@ -787,55 +801,37 @@ pub fn CpuWithHook(
const accumulator = self.registers.a;
const carry: u16 = @intFromBool(self.registers.status.carry);
- const sum =
- @as(u16, accumulator) +
- @as(u16, value) +
- carry;
-
+ const sum = @as(u16, accumulator) + @as(u16, value) + carry;
const binary_result: u8 = @truncate(sum);
- const overflow =
- ((accumulator ^ binary_result) &
- (value ^ binary_result) &
- 0x80) != 0;
-
- const decimal =
- features.decimal_arithmetic and
- self.registers.status.decimal;
+ const overflow = ((accumulator ^ binary_result) & (value ^ binary_result) & 0x80) != 0;
+ const decimal = features.decimal_arithmetic and self.registers.status.decimal;
if (decimal and features.cmos_instructions) self.tick();
- const out: struct { value: u8, carry: bool } =
- if (decimal) decimal: {
- const low_sum =
- @as(u16, accumulator & 0x0f) +
- @as(u16, value & 0x0f) +
- carry;
+ if (!decimal) {
+ const c: u8 = if (sum > 0xff) 0x01 else 0;
+ const z: u8 = if (binary_result == 0) 0x02 else 0;
+ const v: u8 = if (overflow) 0x40 else 0;
+ const n: u8 = binary_result & 0x80;
+ var b: u8 = @bitCast(self.registers.status);
+ b = (b & ~@as(u8, 0xc3)) | c | z | v | n;
+ self.registers.status = @bitCast(b);
+ self.registers.a = binary_result;
+ return;
+ }
- var adjusted = sum;
+ const low_sum = @as(u16, accumulator & 0x0f) + @as(u16, value & 0x0f) + carry;
+ var adjusted = sum;
+ if (low_sum > 9) adjusted += 6;
+ const carry_out = adjusted > 0x99;
+ if (carry_out) adjusted += 0x60;
+ const dec_val: u8 = @truncate(adjusted);
- if (low_sum > 9)
- adjusted += 6;
-
- const carry_out = adjusted > 0x99;
-
- if (carry_out)
- adjusted += 0x60;
-
- break :decimal .{
- .value = @truncate(adjusted),
- .carry = carry_out,
- };
- } else .{
- .value = binary_result,
- .carry = sum > 0xff,
- };
-
- self.registers.status.carry = out.carry;
+ self.registers.status.carry = carry_out;
self.registers.status.overflow = overflow;
-
- self.registers.a = out.value;
- self.updateNZ(out.value);
+ self.registers.a = dec_val;
+ self.updateNZ(dec_val);
}
fn @"and"(self: *Self, value: u8) void {
@@ -856,96 +852,93 @@ pub fn CpuWithHook(
fn sbc(self: *Self, value: u8) void {
const accumulator = self.registers.a;
- // SBC subtracts M + (1 - C).
- const borrow: u16 =
- if (self.registers.status.carry) 0 else 1;
-
- const diff: i16 =
- @as(i16, accumulator) -
- @as(i16, value) -
- @as(i16, @intCast(borrow));
-
+ const borrow: u16 = if (self.registers.status.carry) 0 else 1;
+ const diff: i16 = @as(i16, accumulator) - @as(i16, value) - @as(i16, @intCast(borrow));
const binary_result: u8 = @truncate(@as(u16, @bitCast(diff)));
- const overflow =
- ((accumulator ^ binary_result) &
- (accumulator ^ value) &
- 0x80) != 0;
-
+ const overflow = ((accumulator ^ binary_result) & (accumulator ^ value) & 0x80) != 0;
const no_borrow = diff >= 0;
-
- const decimal =
- features.decimal_arithmetic and
- self.registers.status.decimal;
+ const decimal = features.decimal_arithmetic and self.registers.status.decimal;
if (decimal and features.cmos_instructions) {
self.tick();
}
- const result: u8 =
- if (decimal) decimal: {
- var low: i16 =
- @as(i16, accumulator & 0x0f) -
- @as(i16, value & 0x0f) -
- @as(i16, @intCast(borrow));
+ if (!decimal) {
+ const c: u8 = if (no_borrow) 0x01 else 0;
+ const z: u8 = if (binary_result == 0) 0x02 else 0;
+ const v: u8 = if (overflow) 0x40 else 0;
+ const n: u8 = binary_result & 0x80;
+ var b: u8 = @bitCast(self.registers.status);
+ b = (b & ~@as(u8, 0xc3)) | c | z | v | n;
+ self.registers.status = @bitCast(b);
+ self.registers.a = binary_result;
+ return;
+ }
- var high: i16 =
- @as(i16, accumulator >> 4) -
- @as(i16, value >> 4);
+ var low: i16 = @as(i16, accumulator & 0x0f) - @as(i16, value & 0x0f) - @as(i16, @intCast(borrow));
+ var high: i16 = @as(i16, accumulator >> 4) - @as(i16, value >> 4);
- if (low < 0) {
- low -= 6;
- high -= 1;
- }
+ if (low < 0) {
+ low -= 6;
+ high -= 1;
+ }
- if (high < 0)
- high -= 6;
+ if (high < 0)
+ high -= 6;
- break :decimal (@as(u8, @intCast(high & 0x0f)) << 4) |
- @as(u8, @intCast(low & 0x0f));
- } else binary_result;
+ const dec_val: u8 = (@as(u8, @intCast(high & 0x0f)) << 4) | @as(u8, @intCast(low & 0x0f));
self.registers.status.carry = no_borrow;
self.registers.status.overflow = overflow;
- self.registers.a = result;
- self.updateNZ(result);
+ self.registers.a = dec_val;
+ self.updateNZ(dec_val);
}
// Documented Shifts & Memory Updates
fn asl(self: *Self, value: u8) u8 {
- self.registers.status.carry = value & 0x80 != 0;
+ const carry: u8 = (value >> 7);
const result = value << 1;
- self.updateNZ(result);
+ const z: u8 = if (result == 0) 0x02 else 0;
+ const n: u8 = result & 0x80;
+ var b: u8 = @bitCast(self.registers.status);
+ b = (b & ~@as(u8, 0x83)) | carry | z | n;
+ self.registers.status = @bitCast(b);
return result;
}
fn lsr(self: *Self, value: u8) u8 {
- self.registers.status.carry = value & 1 != 0;
+ const carry: u8 = value & 1;
const result = value >> 1;
- self.updateNZ(result);
+ const z: u8 = if (result == 0) 0x02 else 0;
+ var b: u8 = @bitCast(self.registers.status);
+ b = (b & ~@as(u8, 0x83)) | carry | z;
+ self.registers.status = @bitCast(b);
return result;
}
fn rol(self: *Self, value: u8) u8 {
- const carry: u8 =
- @intFromBool(self.registers.status.carry);
-
- self.registers.status.carry =
- (value & 0x80) != 0;
-
- const result =
- (value << 1) | carry;
-
- self.updateNZ(result);
+ const carry: u8 = @intFromBool(self.registers.status.carry);
+ const carry_out: u8 = (value >> 7);
+ const result = (value << 1) | carry;
+ const z: u8 = if (result == 0) 0x02 else 0;
+ const n: u8 = result & 0x80;
+ var b: u8 = @bitCast(self.registers.status);
+ b = (b & ~@as(u8, 0x83)) | carry_out | z | n;
+ self.registers.status = @bitCast(b);
return result;
}
fn ror(self: *Self, value: u8) u8 {
const carry: u8 = if (self.registers.status.carry) 0x80 else 0;
- self.registers.status.carry = value & 1 != 0;
+ const carry_out: u8 = value & 1;
const result = (value >> 1) | carry;
- self.updateNZ(result);
+ const z: u8 = if (result == 0) 0x02 else 0;
+ const n: u8 = result & 0x80;
+ var b: u8 = @bitCast(self.registers.status);
+ b = (b & ~@as(u8, 0x83)) | carry_out | z | n;
+ self.registers.status = @bitCast(b);
return result;
}
@@ -1211,28 +1204,28 @@ pub fn CpuWithHook(
}
// Undocumented Store Operations
- fn ahx(self: *Self, mode: AddressingMode) void {
+ fn ahx(self: *Self, comptime mode: AddressingMode) void {
self.writeHighMasked(
mode,
self.registers.a & self.registers.x,
);
}
- fn shx(self: *Self, mode: AddressingMode) void {
+ fn shx(self: *Self, comptime mode: AddressingMode) void {
self.writeHighMasked(
mode,
self.registers.x,
);
}
- fn shy(self: *Self, mode: AddressingMode) void {
+ fn shy(self: *Self, comptime mode: AddressingMode) void {
self.writeHighMasked(
mode,
self.registers.y,
);
}
- fn tas(self: *Self, mode: AddressingMode) void {
+ fn tas(self: *Self, comptime mode: AddressingMode) void {
self.registers.sp =
self.registers.a & self.registers.x;
@@ -1244,7 +1237,7 @@ pub fn CpuWithHook(
fn writeHighMasked(
self: *Self,
- mode: AddressingMode,
+ comptime mode: AddressingMode,
source: u8,
) void {
switch (mode) {
@@ -1345,7 +1338,7 @@ pub fn CpuWithHook(
fn kil(self: *Self) void {
// Enter the jammed state after the opcode fetch.
_ = self.readCycle(self.registers.pc);
- self.state = .jammed;
+ self.flags.state = .jammed;
}
fn rmb(self: *Self, bit: u3) void {
@@ -1463,7 +1456,7 @@ pub fn CpuWithHook(
_ = self.readCycle(self.registers.pc);
if (features.wait_and_stop) {
- self.state = .waiting;
+ self.flags.state = .waiting;
}
}
@@ -1472,12 +1465,8 @@ pub fn CpuWithHook(
_ = self.readCycle(self.registers.pc);
if (features.wait_and_stop) {
- self.state = .stopped;
+ self.flags.state = .stopped;
}
}
};
}
-
-
-
-
diff --git a/src/cpu/m6502/types.zig b/src/cpu/m6502/types.zig
index e71cb24..3b4141c 100644
--- a/src/cpu/m6502/types.zig
+++ b/src/cpu/m6502/types.zig
@@ -24,6 +24,15 @@ pub const Status = packed struct(u8) {
status.unused = true;
return status;
}
+
+ // single-pass NZ status update via direct bitwise mask to eliminate double bitfield read-modify-write bit shifts
+ pub inline fn setNZ(self: *Status, value: u8) void {
+ const z: u8 = if (value == 0) 0x02 else 0;
+ const n: u8 = value & 0x80;
+ var b: u8 = @bitCast(self.*);
+ b = (b & ~@as(u8, 0x82)) | z | n;
+ self.* = @bitCast(b);
+ }
};
pub const Registers = struct {
diff --git a/src/cpu/m6502/utils.zig b/src/cpu/m6502/utils.zig
index 1d869d0..3f59018 100644
--- a/src/cpu/m6502/utils.zig
+++ b/src/cpu/m6502/utils.zig
@@ -5,17 +5,16 @@ pub inline fn makeWord(low: u8, high: u8) u16 {
return @as(u16, low) | (@as(u16, high) << 8);
}
-pub fn addRelative(base: u16, offset: i8) u16 {
- const signed_offset: i16 = offset;
- const unsigned_offset: u16 = @bitCast(signed_offset);
- return base +% unsigned_offset;
+// inline helper functions for zero call overhead
+pub inline fn addRelative(base: u16, offset: i8) u16 {
+ return base +% @as(u16, @bitCast(@as(i16, offset)));
}
-pub fn pageCrossed(base: u16, address: u16) bool {
- return (base & 0xff00) != (address & 0xff00);
+pub inline fn pageCrossed(base: u16, address: u16) bool {
+ return (base ^ address) >= 0x0100;
}
-pub fn bitNumber(operation: anytype, first: anytype) u3 {
+pub inline fn bitNumber(operation: anytype, first: anytype) u3 {
return @intCast(@intFromEnum(operation) - @intFromEnum(first));
}
diff --git a/src/cpu/root.zig b/src/cpu/root.zig
index b632510..a4210fe 100644
--- a/src/cpu/root.zig
+++ b/src/cpu/root.zig
@@ -5,3 +5,23 @@ test {
_ = @import("m6502/disassembler.zig");
_ = @import("m6502/mos6510.zig");
}
+
+const Bus = struct {
+ memory: [65536]u8 = [_]u8{0} ** 65536,
+
+ pub fn read(self: *@This(), address: u16) u8 {
+ return self.memory[address];
+ }
+
+ pub fn write(self: *@This(), address: u16, value: u8) void {
+ self.memory[address] = value;
+ }
+
+ pub fn tick(_: *@This()) void {}
+};
+
+const Cpu = m6502.Cpu(Bus, .mos6502, .cycle);
+
+export fn cpu_run(cpu: *Cpu) void {
+ _ = cpu.step();
+}