Compare commits

..

11 Commits

13 changed files with 201 additions and 14 deletions

View File

@ -12,14 +12,16 @@ pub const Io = struct {
/// Read / Write
ime: bool,
ie: InterruptEnable,
keyinput: KeyInput,
pub fn init() Self {
return .{
.dispcnt = .{ .raw = 0x0000_0000 },
.dispstat = .{ .raw = 0x0000_0000 },
.vcount = .{ .raw = 0x0000_0000 },
.dispcnt = .{ .raw = 0x0000 },
.dispstat = .{ .raw = 0x0000 },
.vcount = .{ .raw = 0x0000 },
.ime = false,
.ie = .{ .raw = 0x0000_0000 },
.ie = .{ .raw = 0x0000 },
.keyinput = .{ .raw = 0x01FF },
};
}
@ -36,6 +38,7 @@ pub const Io = struct {
pub fn write32(self: *Self, addr: u32, word: u32) void {
switch (addr) {
0x0400_0000 => self.dispcnt.raw = @truncate(u16, word),
0x0400_0200 => self.ie.raw = @truncate(u16, word),
0x0400_0208 => self.ime = word & 1 == 1,
else => std.debug.panic("[I/O:32] tried to write 0x{X:} to 0x{X:}", .{ word, addr }),
@ -47,6 +50,7 @@ pub const Io = struct {
0x0400_0000 => self.dispcnt.raw,
0x0400_0004 => self.dispstat.raw,
0x0400_0006 => self.vcount.raw,
0x0400_0130 => self.keyinput.raw,
0x0400_0200 => self.ie.raw,
0x0400_0208 => @boolToInt(self.ime),
else => std.debug.panic("[I/O:16] tried to read from {X:}", .{addr}),
@ -128,3 +132,19 @@ const InterruptEnable = extern union {
game_pak: Bit(u16, 13),
raw: u16,
};
/// Read Only
/// 0 = Pressed, 1 = Released
const KeyInput = extern union {
a: Bit(u16, 0),
b: Bit(u16, 1),
select: Bit(u16, 2),
start: Bit(u16, 3),
right: Bit(u16, 4),
left: Bit(u16, 5),
up: Bit(u16, 6),
down: Bit(u16, 7),
shoulder_r: Bit(u16, 8),
shoulder_l: Bit(u16, 9),
raw: u16,
};

View File

@ -17,15 +17,19 @@ const blockDataTransfer = @import("cpu/arm/block_data_transfer.zig").blockDataTr
const branch = @import("cpu/arm/branch.zig").branch;
const branchAndExchange = @import("cpu/arm/branch.zig").branchAndExchange;
const softwareInterrupt = @import("cpu/arm/software_interrupt.zig").softwareInterrupt;
const multiply = @import("cpu/arm/multiply.zig").multiply;
// THUMB Instruction Groups
const format1 = @import("cpu/thumb/format1.zig").format1;
const format2 = @import("cpu/thumb/format2.zig").format2;
const format3 = @import("cpu/thumb/format3.zig").format3;
const format4 = @import("cpu/thumb/format4.zig").format4;
const format2 = @import("cpu/thumb/format2.zig").format2;
const format5 = @import("cpu/thumb/format5.zig").format5;
const format6 = @import("cpu/thumb/format6.zig").format6;
const format9 = @import("cpu/thumb/format9.zig").format9;
const format12 = @import("cpu/thumb/format12.zig").format12;
const format13 = @import("cpu/thumb/format13.zig").format13;
const format14 = @import("cpu/thumb/format14.zig").format14;
const format15 = @import("cpu/thumb/format15.zig").format15;
const format16 = @import("cpu/thumb/format16.zig").format16;
const format19 = @import("cpu/thumb/format19.zig").format19;
@ -369,6 +373,14 @@ fn thumbPopulate() [0x400]ThumbInstrFn {
lut[i] = format6(rd);
}
if (i >> 7 & 0x7 == 0b011) {
const B = i >> 6 & 1 == 1;
const L = i >> 5 & 1 == 1;
const offset = i & 0x1F;
lut[i] = format9(B, L, offset);
}
if (i >> 6 & 0xF == 0b1010) {
const isSP = i >> 5 & 1 == 1;
const rd = i >> 2 & 0x7;
@ -376,9 +388,22 @@ fn thumbPopulate() [0x400]ThumbInstrFn {
lut[i] = format12(isSP, rd);
}
if (i >> 2 & 0xFF == 0xB0) {
const S = i >> 1 & 1 == 1;
lut[i] = format13(S);
}
if (i >> 6 & 0xF == 0b1011 and i >> 3 & 0x3 == 0b10) {
const L = i >> 5 & 1 == 1;
const R = i >> 2 & 1 == 1;
lut[i] = format14(L, R);
}
if (i >> 6 & 0xF == 0b1100) {
const L = i >> 5 & 1 == 1;
const rb = i >> 2 & 0x3;
const rb = i >> 2 & 0x7;
lut[i] = format15(L, rb);
}
@ -438,6 +463,13 @@ fn armPopulate() [0x1000]ArmInstrFn {
lut[i] = halfAndSignedDataTransfer(P, U, I, W, L);
}
if (i >> 6 & 0x3F == 0b000000 and i & 0xF == 0b1001) {
const A = i >> 5 & 1 == 1;
const S = i >> 4 & 1 == 1;
lut[i] = multiply(A, S);
}
if (i >> 10 & 0x3 == 0b01) {
const I = i >> 9 & 1 == 1;
const P = i >> 8 & 1 == 1;

View File

@ -58,7 +58,7 @@ pub fn halfAndSignedDataTransfer(comptime P: bool, comptime U: bool, comptime I:
// STRH
bus.write16(address, @truncate(u16, cpu.r[rd]));
} else {
std.debug.panic("[CPU] TODO: Figure out if this is also SWP", .{});
std.debug.print("[CPU|ARM|SignedDataTransfer] {X:0>8} was improperly decoded", .{opcode});
}
}

25
src/cpu/arm/multiply.zig Normal file
View File

@ -0,0 +1,25 @@
const std = @import("std");
const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").ArmInstrFn;
pub fn multiply(comptime A: bool, comptime S: bool) InstrFn {
return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u32) void {
const rd = opcode >> 16 & 0xF;
const rn = opcode >> 12 & 0xF;
const rs = opcode >> 8 & 0xF;
const rm = opcode & 0xF;
const result = cpu.r[rm] * cpu.r[rs] + if (A) cpu.r[rn] else 0;
cpu.r[rd] = result;
if (S) {
cpu.cpsr.n.write(result >> 31 & 1 == 1);
cpu.cpsr.z.write(result == 0);
// V is unaffected, C is *actually* undefined in ARMv4
}
}
}.inner;
}

View File

@ -8,7 +8,7 @@ pub fn format12(comptime isSP: bool, comptime rd: u3) InstrFn {
return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
// ADD
const left = if (isSP) cpu.r[13] else cpu.fakePC() & 0xFFFF_FFFC;
const left = if (isSP) cpu.r[13] else (cpu.r[15] + 2) & 0xFFFF_FFFD;
const right = (opcode & 0xFF) << 2;
const result = left + right; // TODO: What about overflows?
cpu.r[rd] = result;

View File

@ -0,0 +1,13 @@
const std = @import("std");
const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").ThumbInstrFn;
pub fn format13(comptime _: bool) InstrFn {
return struct {
fn inner(_: *Arm7tdmi, _: *Bus, _: u16) void {
std.debug.panic("[CPU|THUMB|Fmt13] Implement Format 13 THUMB Instructions", .{});
}
}.inner;
}

View File

@ -0,0 +1,50 @@
const std = @import("std");
const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").ThumbInstrFn;
pub fn format14(comptime L: bool, comptime R: bool) InstrFn {
return struct {
fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
var address: u32 = undefined;
if (L) {
// POP
address = cpu.r[13];
var i: usize = 0;
while (i < 8) : (i += 1) {
if ((opcode >> @truncate(u3, i)) & 1 == 1) {
cpu.r[i] = bus.read32(address);
address += 4;
}
}
if (R) {
const value = bus.read32(address);
cpu.r[15] = value & 0xFFFF_FFFE;
address += 4;
}
} else {
address = cpu.r[13] - 4;
if (R) {
bus.write32(address, cpu.r[14]);
address -= 4;
}
var i: usize = 8;
while (i > 0) : (i -= 1) {
const j = i - 1;
if ((opcode >> @truncate(u3, j)) & 1 == 1) {
bus.write32(address, cpu.r[j]);
address -= 4;
}
}
}
cpu.r[13] = address + if (!L) 4 else 0;
}
}.inner;
}

View File

@ -19,7 +19,7 @@ pub fn format16(comptime cond: u4) InstrFn {
};
if (should_execute) {
cpu.r[15] = (cpu.fakePC() & 0xFFFF_FFFC) +% offset;
cpu.r[15] = (cpu.r[15] + 2) +% offset;
}
}
}.inner;

View File

@ -9,7 +9,7 @@ pub fn format19(comptime is_low: bool) InstrFn {
return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
// BL
const offset = opcode & 0x3FF;
const offset = opcode & 0x7FF;
if (is_low) {
// Instruction 2
@ -19,7 +19,7 @@ pub fn format19(comptime is_low: bool) InstrFn {
cpu.r[14] = old_pc | 1;
} else {
// Instruction 1
cpu.r[14] = (cpu.fakePC() & 0xFFFF_FFFC) + (u32SignExtend(11, @as(u32, offset)) << 12);
cpu.r[14] = (cpu.r[15] + 2) +% (u32SignExtend(11, @as(u32, offset)) << 12);
}
}
}.inner;

View File

@ -92,7 +92,10 @@ pub fn format4(comptime op: u4) InstrFn {
// MUL
const result = cpu.r[rs] * cpu.r[rd];
cpu.r[rd] = result;
std.debug.panic("[CPU|THUMB|MUL] TODO: Set flags on ALU MUL", .{});
cpu.cpsr.n.write(result >> 31 & 1 == 1);
cpu.cpsr.z.write(result == 0);
// V is unaffected, assuming similar behaviour to ARMv4 MUL C is undefined
},
0xE => {
// BIC

View File

@ -11,7 +11,7 @@ pub fn format6(comptime rd: u3) InstrFn {
const offset = (opcode & 0xFF) << 2;
// FIXME: Should this overflow?
cpu.r[rd] = bus.read32((cpu.fakePC() & 0xFFFF_FFFC) + offset);
cpu.r[rd] = bus.read32((cpu.r[15] + 2 & 0xFFFF_FFFD) + offset);
}
}.inner;
}

36
src/cpu/thumb/format9.zig Normal file
View File

@ -0,0 +1,36 @@
const std = @import("std");
const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").ThumbInstrFn;
pub fn format9(comptime B: bool, comptime L: bool, comptime offset: u5) InstrFn {
return struct {
fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
const rb = opcode >> 3 & 0x7;
const rd = opcode & 0x7;
if (L) {
if (B) {
// LDRB
const address = cpu.r[rb] + offset;
cpu.r[rd] = bus.read8(address);
} else {
// LDR
const address = cpu.r[rb] + (@as(u32, offset) << 2);
cpu.r[rd] = bus.read32(address & 0xFFFF_FFFC);
}
} else {
if (B) {
// STRB
const address = cpu.r[rb] + offset;
bus.write8(address, @truncate(u8, cpu.r[rd]));
} else {
// STR
const address = cpu.r[rb] + (@as(u32, offset) << 2);
bus.write32(address & 0xFFFF_FFFC, cpu.r[rd]);
}
}
}
}.inner;
}

View File

@ -117,8 +117,16 @@ const Vram = struct {
alloc: Allocator,
fn init(alloc: Allocator) !Self {
// In Modes 3 and 4, parts of the VRAM are copied to the
// frame buffer, therefore we want to zero-initialize Vram
//
// some programs like Armwrestler assume that VRAM is zeroed-out.
const black = std.mem.zeroes([0x18000]u8);
const buf = try alloc.alloc(u8, 0x18000);
std.mem.copy(u8, buf, &black);
return Self{
.buf = try alloc.alloc(u8, 0x18000),
.buf = buf,
.alloc = alloc,
};
}