//! Interrupt-related (IRQ/NMI/BRK/RESET) tests. const std = @import("std"); const cpu = @import("../cpu.zig"); test "reset" { var z = cpu.testZes(.{ .init_cpu = false, .rom = &.{ .{ 0x8000, &.{0x69} }, }, }); while (!z.cpu.sync) z.stepCpu(); // Correctly read the first byte of ROM try std.testing.expectEqual(0x69, z.pins.cpu_data); } test "BRK" { var z = cpu.testZes(.{ // Set a recognizable IRQ offset .irq = 0x8964, .rom = &.{ .{ 0x8000, cpu.assemble("brk") }, }, }); // Setup some initial setup state. z.cpu.status.carry = true; z.cpu.status.irq_disabled = true; z.cpu.status.negative = true; var status_copy = z.cpu.status; // Since this is a BRK, the B flag will be set status_copy.brk = true; while (!z.cpu.status.brk) z.stepCpu(); // Store PC hi try std.testing.expectEqual(0x01fd, z.pins.cpu_addr); // Store PC lo z.stepCpu(); try std.testing.expectEqual(0x01fc, z.pins.cpu_addr); // Store processor status z.stepCpu(); const status: u8 = @bitCast(status_copy); try std.testing.expectEqual(0x01fb, z.pins.cpu_addr); // Fetch PC lo z.stepCpu(); try std.testing.expectEqual(0xfffe, z.pins.cpu_addr); try std.testing.expectEqual(0x64, z.pins.cpu_data); // Fetch PC hi z.stepCpu(); try std.testing.expectEqual(0xffff, z.pins.cpu_addr); try std.testing.expectEqual(0x89, z.pins.cpu_data); // PC should be reset correctly z.stepCpu(); try std.testing.expectEqual(0x8964, z.cpu.pc); try std.testing.expectEqual(0x8964, z.pins.cpu_addr); // Old PC should remain in stack try std.testing.expectEqual(0x80, z.ram[0x1fd]); try std.testing.expectEqual(0x00, z.ram[0x1fc]); try std.testing.expectEqual(status, z.ram[0x1fb]); } test "set and clear" { // TODO: Test CLV as well var z = cpu.testZes(.{ .rom = &.{ .{ 0x8000, cpu.assemble( \\sec \\cli \\sed \\clc \\sei \\cld ), }, } }); // Initial state try std.testing.expect(!z.cpu.status.carry); try std.testing.expect(z.cpu.status.irq_disabled); try std.testing.expect(!z.cpu.status.decimal); cpu.run(&z); // SEC try std.testing.expectEqual(0x8001, z.cpu.pc); try std.testing.expect(z.cpu.status.carry); cpu.run(&z); // CLI try std.testing.expectEqual(0x8002, z.cpu.pc); try std.testing.expect(!z.cpu.status.irq_disabled); cpu.run(&z); // SED try std.testing.expectEqual(0x8003, z.cpu.pc); try std.testing.expect(z.cpu.status.decimal); cpu.run(&z); // CLC try std.testing.expectEqual(0x8004, z.cpu.pc); try std.testing.expect(!z.cpu.status.carry); cpu.run(&z); // SEI try std.testing.expectEqual(0x8005, z.cpu.pc); try std.testing.expect(z.cpu.status.irq_disabled); cpu.run(&z); // CLD try std.testing.expectEqual(0x8006, z.cpu.pc); try std.testing.expect(!z.cpu.status.decimal); } test "branches" { var z = cpu.testZes(.{ .rom = &.{ .{ 0x8000, cpu.assemble( \\sec \\bcs #5 \\ \\lda #%11000000 \\bmi #5 \\ \\lda #0 \\beq #$87 \\ \\bit $8004 \\bvs #2 \\brk \\nop ), }, } }); cpu.run(&z); // SEC try std.testing.expectEqual(0x8001, z.cpu.pc); try std.testing.expect(z.cpu.status.carry); cpu.run(&z); // BCS try std.testing.expectEqual(0x8007, z.cpu.pc); // LDA #80 should be skipped here. try std.testing.expect(!z.cpu.status.negative); cpu.run(&z); // LDA #0 try std.testing.expectEqual(0x8009, z.cpu.pc); try std.testing.expect(z.cpu.status.zero); cpu.run(&z); // BEQ (jump backwards) try std.testing.expectEqual(0x8003, z.cpu.pc); cpu.run(&z); // LDA #%11000000 try std.testing.expectEqual(0xa9, z.cpu.opcode); try std.testing.expectEqual(0x8005, z.cpu.pc); try std.testing.expect(z.cpu.status.negative); // TODO: investigate why the CPU run helper doesn't stop after branching z.stepCpu(); // BMI z.stepCpu(); // BMI z.stepCpu(); // BMI try std.testing.expectEqual(0x800c, z.cpu.pc); cpu.run(&z); // BIT try std.testing.expectEqual(0x800e, z.cpu.pc); try std.testing.expect(z.cpu.status.overflow); z.stepCpu(); // BVS z.stepCpu(); // BVS z.stepCpu(); // BVS try std.testing.expectEqual(0x8011, z.cpu.pc); // Never hit BRK try std.testing.expect(!z.cpu.status.brk); } test "subroutines" { var z = cpu.testZes(.{ .rom = &.{ .{ 0x8000, cpu.assemble( \\jsr $8007 \\cmp $8008 \\brk \\lda #$80 \\rts ), }, } }); try std.testing.expectEqual(0xfd, z.cpu.sp); cpu.run(&z); // JSR try std.testing.expectEqual(0x8007, z.cpu.pc); try std.testing.expectEqual(0xfb, z.cpu.sp); try std.testing.expectEqual(0x01, z.ram[0x1fc]); try std.testing.expectEqual(0x80, z.ram[0x1fd]); cpu.run(&z); // LDA try std.testing.expectEqual(0x8009, z.cpu.pc); try std.testing.expectEqual(0x80, z.cpu.a); try std.testing.expect(z.cpu.status.negative); // TODO: figure out why the run harness runs two commands at once cpu.run(&z); // RTS, CMP try std.testing.expectEqual(0x8006, z.cpu.pc); try std.testing.expectEqual(0xfd, z.cpu.sp); try std.testing.expect(z.cpu.status.zero); // Should be equal // Never hit BRK try std.testing.expect(!z.cpu.status.brk); } test "JMP" { var z = cpu.testZes(.{ .rom = &.{ .{ 0x8000, cpu.assemble( \\jmp $8004 \\brk \\lda $8000 ), }, } }); cpu.run(&z); // JMP try std.testing.expectEqual(0x8004, z.cpu.pc); cpu.run(&z); // LDA try std.testing.expectEqual(0x8007, z.cpu.pc); try std.testing.expectEqual(0x4c, z.cpu.a); // Never hit BRK try std.testing.expect(!z.cpu.status.brk); } test "JMP indirect" { var z = cpu.testZes(.{ .rom = &.{ .{ 0x8000, cpu.assemble( \\jmp ($aabb) \\brk \\lda $8000 ), }, .{ 0xaabb, &.{ 0x04, 0x80 } }, } }); cpu.run(&z); // JMP try std.testing.expectEqual(0x8004, z.cpu.pc); cpu.run(&z); // LDA try std.testing.expectEqual(0x8007, z.cpu.pc); try std.testing.expectEqual(0x6c, z.cpu.a); // Never hit BRK try std.testing.expect(!z.cpu.status.brk); }