add 6502 disassembler

This commit is contained in:
2026-08-06 19:10:14 +02:00
parent fe1866358d
commit 4ba8632ba2
2 changed files with 138 additions and 0 deletions
+134
View File
@@ -0,0 +1,134 @@
const std = @import("std");
const types = @import("types.zig");
const Model = types.Model;
const Instruction = @import("instruction.zig");
const Operation = Instruction.Operation;
const AddressingMode = Instruction.AddressingMode;
pub const DisassembledInstruction = struct {
address: u16,
opcode: u8,
bytes: [3]u8,
len: u8,
operation: Operation,
mode: AddressingMode,
pub fn format(self: DisassembledInstruction, writer: anytype) !void {
try writer.print("{X:0>4}: ", .{self.address});
for (0..self.len) |i| {
try writer.print("{X:0>2} ", .{self.bytes[i]});
}
for (self.len..3) |_| {
try writer.writeAll(" ");
}
try writer.writeAll(" ");
const op_name = @tagName(self.operation);
var upper_buf: [8]u8 = undefined;
for (op_name, 0..) |c, i| {
upper_buf[i] = std.ascii.toUpper(c);
}
try writer.writeAll(upper_buf[0..op_name.len]);
switch (self.mode) {
.implied => {},
.accumulator => try writer.writeAll(" A"),
.immediate => try writer.print(" #${X:0>2}", .{self.bytes[1]}),
.zero_page => try writer.print(" ${X:0>2}", .{self.bytes[1]}),
.zero_page_x => try writer.print(" ${X:0>2},X", .{self.bytes[1]}),
.zero_page_y => try writer.print(" ${X:0>2},Y", .{self.bytes[1]}),
.relative => {
const rel = @as(i8, @bitCast(self.bytes[1]));
const target = @as(u16, @bitCast(@as(i16, @intCast(self.address)) +% 2 +% rel));
try writer.print(" ${X:0>4}", .{target});
},
.absolute => {
const target = @as(u16, self.bytes[1]) | (@as(u16, self.bytes[2]) << 8);
try writer.print(" ${X:0>4}", .{target});
},
.absolute_x => {
const target = @as(u16, self.bytes[1]) | (@as(u16, self.bytes[2]) << 8);
try writer.print(" ${X:0>4},X", .{target});
},
.absolute_y => {
const target = @as(u16, self.bytes[1]) | (@as(u16, self.bytes[2]) << 8);
try writer.print(" ${X:0>4},Y", .{target});
},
.indirect => {
const target = @as(u16, self.bytes[1]) | (@as(u16, self.bytes[2]) << 8);
try writer.print(" (${X:0>4})", .{target});
},
.indexed_indirect_x => try writer.print(" (${X:0>2},X)", .{self.bytes[1]}),
.indirect_indexed_y => try writer.print(" (${X:0>2}),Y", .{self.bytes[1]}),
.zero_page_indirect => try writer.print(" (${X:0>2})", .{self.bytes[1]}),
.absolute_indexed_indirect => {
const target = @as(u16, self.bytes[1]) | (@as(u16, self.bytes[2]) << 8);
try writer.print(" (${X:0>4},X)", .{target});
},
.zero_page_relative => {
const rel = @as(i8, @bitCast(self.bytes[2]));
const target = @as(u16, @bitCast(@as(i16, @intCast(self.address)) +% 3 +% rel));
try writer.print(" ${X:0>2},${X:0>4}", .{ self.bytes[1], target });
},
}
}
};
pub fn disassemble(model: Model, address: u16, bus: anytype) DisassembledInstruction {
const opcode = bus.read(address & model.addressMask());
const inst = model.instruction(opcode);
const len: u8 = switch (inst.mode) {
.implied, .accumulator => 1,
.immediate, .zero_page, .zero_page_x, .zero_page_y, .relative, .zero_page_indirect, .indexed_indirect_x, .indirect_indexed_y => 2,
.absolute, .absolute_x, .absolute_y, .indirect, .absolute_indexed_indirect, .zero_page_relative => 3,
};
var bytes = [3]u8{ opcode, 0, 0 };
if (len > 1) bytes[1] = bus.read((address +% 1) & model.addressMask());
if (len > 2) bytes[2] = bus.read((address +% 2) & model.addressMask());
return .{
.address = address,
.opcode = opcode,
.bytes = bytes,
.len = len,
.operation = inst.operation,
.mode = inst.mode,
};
}
test "Zero-Allocation Disassembler Formatting" {
const testing = std.testing;
const TestBus = @import("conformance.zig").TestBus;
var ram = TestBus{};
// LDA #$42 at 0x8000
ram.write(0x8000, 0xA9);
ram.write(0x8001, 0x42);
const disassembled = disassemble(.mos6502, 0x8000, &ram);
try testing.expectEqual(@as(u8, 2), disassembled.len);
try testing.expectEqual(Operation.lda, disassembled.operation);
var buf: [64]u8 = undefined;
var pos: usize = 0;
const BufferWriter = struct {
buf: []u8,
pos: *usize,
pub fn writeAll(self: @This(), bytes: []const u8) !void {
@memcpy(self.buf[self.pos.* .. self.pos.* + bytes.len], bytes);
self.pos.* += bytes.len;
}
pub fn print(self: @This(), comptime fmt: []const u8, args: anytype) !void {
const printed = try std.fmt.bufPrint(self.buf[self.pos.*..], fmt, args);
self.pos.* += printed.len;
}
};
const bw = BufferWriter{ .buf = &buf, .pos = &pos };
try disassembled.format(bw);
try testing.expectEqualSlices(u8, "8000: A9 42 LDA #$42", buf[0..pos]);
}
+4
View File
@@ -1 +1,5 @@
pub const m6502 = @import("m6502/root.zig");
test {
_ = @import("m6502/disassembler.zig");
}