diff --git a/src/cpu/m6502/disassembler.zig b/src/cpu/m6502/disassembler.zig new file mode 100644 index 0000000..665e1a3 --- /dev/null +++ b/src/cpu/m6502/disassembler.zig @@ -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]); +} diff --git a/src/cpu/root.zig b/src/cpu/root.zig index 9422196..9ede18d 100644 --- a/src/cpu/root.zig +++ b/src/cpu/root.zig @@ -1 +1,5 @@ pub const m6502 = @import("m6502/root.zig"); + +test { + _ = @import("m6502/disassembler.zig"); +}