From d2db52e4952bbfeffef615ad7282385aa3eccbde Mon Sep 17 00:00:00 2001 From: Rekai Musuka Date: Sun, 25 Jun 2023 16:37:43 -0500 Subject: [PATCH] feat: implement ARM7TDMI (and stub ARM946E-S) --- build.zig | 9 + build.zig.zon | 10 + lib/bitfield.zig | 146 +++++++ src/arm.zig | 398 ++++++++++++++++++ src/arm/cpu/arm/block_data_transfer.zig | 111 +++++ src/arm/cpu/arm/branch.zig | 31 ++ src/arm/cpu/arm/data_processing.zig | 185 ++++++++ src/arm/cpu/arm/half_signed_data_transfer.zig | 53 +++ src/arm/cpu/arm/multiply.zig | 59 +++ src/arm/cpu/arm/psr_transfer.zig | 59 +++ src/arm/cpu/arm/single_data_swap.zig | 29 ++ src/arm/cpu/arm/single_data_transfer.zig | 55 +++ src/arm/cpu/arm/software_interrupt.zig | 23 + src/arm/cpu/barrel_shifter.zig | 146 +++++++ src/arm/cpu/thumb/alu.zig | 108 +++++ src/arm/cpu/thumb/block_data_transfer.zig | 102 +++++ src/arm/cpu/thumb/branch.zig | 57 +++ src/arm/cpu/thumb/data_processing.zig | 209 +++++++++ src/arm/cpu/thumb/data_transfer.zig | 153 +++++++ src/arm/cpu/thumb/software_interrupt.zig | 23 + src/arm/v4t.zig | 229 ++++++++++ src/lib.zig | 259 +++++++++++- 22 files changed, 2450 insertions(+), 4 deletions(-) create mode 100644 build.zig.zon create mode 100644 lib/bitfield.zig create mode 100644 src/arm.zig create mode 100644 src/arm/cpu/arm/block_data_transfer.zig create mode 100644 src/arm/cpu/arm/branch.zig create mode 100644 src/arm/cpu/arm/data_processing.zig create mode 100644 src/arm/cpu/arm/half_signed_data_transfer.zig create mode 100644 src/arm/cpu/arm/multiply.zig create mode 100644 src/arm/cpu/arm/psr_transfer.zig create mode 100644 src/arm/cpu/arm/single_data_swap.zig create mode 100644 src/arm/cpu/arm/single_data_transfer.zig create mode 100644 src/arm/cpu/arm/software_interrupt.zig create mode 100644 src/arm/cpu/barrel_shifter.zig create mode 100644 src/arm/cpu/thumb/alu.zig create mode 100644 src/arm/cpu/thumb/block_data_transfer.zig create mode 100644 src/arm/cpu/thumb/branch.zig create mode 100644 src/arm/cpu/thumb/data_processing.zig create mode 100644 src/arm/cpu/thumb/data_transfer.zig create mode 100644 src/arm/cpu/thumb/software_interrupt.zig create mode 100644 src/arm/v4t.zig diff --git a/build.zig b/build.zig index ba26258..73730c3 100644 --- a/build.zig +++ b/build.zig @@ -24,6 +24,12 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); + const zba_util_mod = b.dependency("zba-util", .{}).module("zba-util"); + const bitfield_mod = b.createModule(.{ .source_file = .{ .path = "lib/bitfield.zig" }, .dependencies = &.{} }); + + lib.addModule("zba-util", zba_util_mod); // https://git.musuka.dev/paoda/zba-util + lib.addModule("bitfield", bitfield_mod); + // This declares intent for the library to be installed into the standard // location when the user invokes the "install" step (the default step when // running `zig build`). @@ -37,6 +43,9 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); + main_tests.addModule("zba-util", zba_util_mod); // https://git.musuka.dev/paoda/zba-util + main_tests.addModule("bitfield", bitfield_mod); + const run_main_tests = b.addRunArtifact(main_tests); // This creates a build step. It will be visible in the `zig build --help` menu, diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..0422ba5 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = "arm32", + .version = "0.1.0", + .dependencies = .{ + .@"zba-util" = .{ + .url = "https://git.musuka.dev/paoda/zba-util/archive/e616cf09e53f5c402c8f040d14baa211683e70e3.tar.gz", + .hash = "1220b80b2c0989dcc47275ab9d7d70da4858ef3c1fe1f934e8d838e65028127f6ef3", + }, + }, +} diff --git a/lib/bitfield.zig b/lib/bitfield.zig new file mode 100644 index 0000000..b9ca43e --- /dev/null +++ b/lib/bitfield.zig @@ -0,0 +1,146 @@ +const std = @import("std"); + +fn PtrCastPreserveCV(comptime T: type, comptime PtrToT: type, comptime NewT: type) type { + return switch (PtrToT) { + *T => *NewT, + *const T => *const NewT, + *volatile T => *volatile NewT, + *const volatile T => *const volatile NewT, + + else => @compileError("wtf you doing"), + }; +} + +fn BitType(comptime FieldType: type, comptime ValueType: type, comptime shamt: usize) type { + const self_bit: FieldType = (1 << shamt); + + return extern struct { + bits: Bitfield(FieldType, shamt, 1), + + pub fn set(self: anytype) void { + self.bits.field().* |= self_bit; + } + + pub fn unset(self: anytype) void { + self.bits.field().* &= ~self_bit; + } + + pub fn read(self: anytype) ValueType { + return @bitCast(ValueType, @truncate(u1, self.bits.field().* >> shamt)); + } + + // Since these are mostly used with MMIO, I want to avoid + // reading the memory just to write it again, also races + pub fn write(self: anytype, val: ValueType) void { + if (@bitCast(bool, val)) { + self.set(); + } else { + self.unset(); + } + } + }; +} + +// Original Bit Constructor +// pub fn Bit(comptime FieldType: type, comptime shamt: usize) type { +// return BitType(FieldType, u1, shamt); +// } + +pub fn Bit(comptime FieldType: type, comptime shamt: usize) type { + return BitType(FieldType, bool, shamt); +} + +fn Boolean(comptime FieldType: type, comptime shamt: usize) type { + return BitType(FieldType, bool, shamt); +} + +pub fn Bitfield(comptime FieldType: type, comptime shamt: usize, comptime num_bits: usize) type { + if (shamt + num_bits > @bitSizeOf(FieldType)) { + @compileError("bitfield doesn't fit"); + } + + const self_mask: FieldType = ((1 << num_bits) - 1) << shamt; + + const ValueType = std.meta.Int(.unsigned, num_bits); + + return extern struct { + dummy: FieldType, + + fn field(self: anytype) PtrCastPreserveCV(@This(), @TypeOf(self), FieldType) { + return @ptrCast(PtrCastPreserveCV(@This(), @TypeOf(self), FieldType), self); + } + + pub fn write(self: anytype, val: ValueType) void { + self.field().* &= ~self_mask; + self.field().* |= @intCast(FieldType, val) << shamt; + } + + pub fn read(self: anytype) ValueType { + const val: FieldType = self.field().*; + return @intCast(ValueType, (val & self_mask) >> shamt); + } + }; +} + +test "bit" { + const S = extern union { + low: Bit(u32, 0), + high: Bit(u32, 1), + val: u32, + }; + + std.testing.expect(@sizeOf(S) == 4); + std.testing.expect(@bitSizeOf(S) == 32); + + var s: S = .{ .val = 1 }; + + std.testing.expect(s.low.read() == 1); + std.testing.expect(s.high.read() == 0); + + s.low.write(0); + s.high.write(1); + + std.testing.expect(s.val == 2); +} + +test "boolean" { + const S = extern union { + low: Boolean(u32, 0), + high: Boolean(u32, 1), + val: u32, + }; + + std.testing.expect(@sizeOf(S) == 4); + std.testing.expect(@bitSizeOf(S) == 32); + + var s: S = .{ .val = 2 }; + + std.testing.expect(s.low.read() == false); + std.testing.expect(s.high.read() == true); + + s.low.write(true); + s.high.write(false); + + std.testing.expect(s.val == 1); +} + +test "bitfield" { + const S = extern union { + low: Bitfield(u32, 0, 16), + high: Bitfield(u32, 16, 16), + val: u32, + }; + + std.testing.expect(@sizeOf(S) == 4); + std.testing.expect(@bitSizeOf(S) == 32); + + var s: S = .{ .val = 0x13376969 }; + + std.testing.expect(s.low.read() == 0x6969); + std.testing.expect(s.high.read() == 0x1337); + + s.low.write(0x1337); + s.high.write(0x6969); + + std.testing.expect(s.val == 0x69691337); +} diff --git a/src/arm.zig b/src/arm.zig new file mode 100644 index 0000000..bcc52de --- /dev/null +++ b/src/arm.zig @@ -0,0 +1,398 @@ +const std = @import("std"); + +const Architecture = enum { v4t, v5te }; +const Interpreter = @import("lib.zig").Interpreter; +const Bus = @import("lib.zig").Bus; +const Scheduler = @import("lib.zig").Scheduler; + +const Bitfield = @import("bitfield").Bitfield; +const Bit = @import("bitfield").Bit; + +const condition_lut = [_]u16{ + 0xF0F0, // EQ - Equal + 0x0F0F, // NE - Not Equal + 0xCCCC, // CS - Unsigned higher or same + 0x3333, // CC - Unsigned lower + 0xFF00, // MI - Negative + 0x00FF, // PL - Positive or Zero + 0xAAAA, // VS - Overflow + 0x5555, // VC - No Overflow + 0x0C0C, // HI - unsigned hierh + 0xF3F3, // LS - unsigned lower or same + 0xAA55, // GE - greater or equal + 0x55AA, // LT - less than + 0x0A05, // GT - greater than + 0xF5FA, // LE - less than or equal + 0xFFFF, // AL - always + 0x0000, // NV - never +}; + +pub fn Arm32(comptime arch: Architecture) type { + return struct { + const Self = @This(); + + r: [16]u32 = [_]u32{0x00} ** 16, + pipe: Pipeline = Pipeline.init(), + sched: Scheduler, + bus: Bus, + cpsr: PSR, + spsr: PSR, + + bank: Bank = Bank.create(), + + const arm = @import("arm/v4t.zig").arm(Self); + const thumb = @import("arm/v4t.zig").thumb(Self); + + const Pipeline = struct { + stage: [2]?u32, + flushed: bool, + + fn init() @This() { + return .{ + .stage = [_]?u32{null} ** 2, + .flushed = false, + }; + } + + pub fn isFull(self: *const @This()) bool { + return self.stage[0] != null and self.stage[1] != null; + } + + pub fn step(self: *@This(), cpu: *Self, comptime T: type) ?u32 { + comptime std.debug.assert(T == u32 or T == u16); + + const opcode = self.stage[0]; + self.stage[0] = self.stage[1]; + self.stage[1] = cpu.fetch(T, cpu.r[15]); + + return opcode; + } + + pub fn reload(self: *@This(), cpu: *Self) void { + if (cpu.cpsr.t.read()) { + self.stage[0] = cpu.fetch(u16, cpu.r[15]); + self.stage[1] = cpu.fetch(u16, cpu.r[15] + 2); + cpu.r[15] += 4; + } else { + self.stage[0] = cpu.fetch(u32, cpu.r[15]); + self.stage[1] = cpu.fetch(u32, cpu.r[15] + 4); + cpu.r[15] += 8; + } + + self.flushed = true; + } + }; + + /// Bank of Registers from other CPU Modes + pub const Bank = struct { + /// Storage for r13_, r14_ + /// e.g. [r13, r14, r13_svc, r14_svc] + r: [2 * 6]u32, + + /// Storage for R8_fiq -> R12_fiq and their normal counterparts + /// e.g [r[0 + 8], fiq_r[0 + 8], r[1 + 8], fiq_r[1 + 8]...] + fiq: [2 * 5]u32, + + spsr: [5]PSR, + + const Kind = enum(u1) { + R13 = 0, + R14, + }; + + pub fn create() Bank { + return .{ + .r = [_]u32{0x00} ** 12, + .fiq = [_]u32{0x00} ** 10, + .spsr = [_]PSR{.{ .raw = 0x0000_0000 }} ** 5, + }; + } + + // public so that we can set up fast-boot + pub inline fn regIdx(mode: Mode, kind: Kind) usize { + const idx: usize = switch (mode) { + .User, .System => 0, + .Supervisor => 1, + .Abort => 2, + .Undefined => 3, + .Irq => 4, + .Fiq => 5, + }; + + return (idx * 2) + if (kind == .R14) @as(usize, 1) else 0; + } + + inline fn spsrIdx(mode: Mode) usize { + return switch (mode) { + .Supervisor => 0, + .Abort => 1, + .Undefined => 2, + .Irq => 3, + .Fiq => 4, + else => std.debug.panic("[CPU/Mode] {} does not have a SPSR Register", .{mode}), + }; + } + + inline fn fiqIdx(i: usize, mode: Mode) usize { + return (i * 2) + if (mode == .Fiq) @as(usize, 1) else 0; + } + }; + + pub fn init(scheduler: Scheduler, bus: Bus) Self { + return Self{ + .sched = scheduler, + .bus = bus, + .cpsr = .{ .raw = 0x0000_001F }, + .spsr = .{ .raw = 0x0000_0000 }, + }; + } + + // FIXME: Resetting disables logging (if enabled) + pub fn reset(self: *Self) void { + self.* = .{ + .sched = self.sched, + .bus = self.bus, + .cpsr = .{ .raw = 0x0000_001F }, + .spsr = .{ .raw = 0x0000_0000 }, + }; + } + + pub inline fn hasSPSR(self: *const Self) bool { + const mode = Mode.getChecked(self, self.cpsr.mode.read()); + return switch (mode) { + .System, .User => false, + else => true, + }; + } + + pub inline fn isPrivileged(self: *const Self) bool { + const mode = Mode.getChecked(self, self.cpsr.mode.read()); + return switch (mode) { + .User => false, + else => true, + }; + } + + pub fn setCpsr(self: *Self, value: u32) void { + if (value & 0x1F != self.cpsr.raw & 0x1F) self.changeModeFromIdx(@truncate(u5, value & 0x1F)); + self.cpsr.raw = value; + } + + fn changeModeFromIdx(self: *Self, next: u5) void { + self.changeMode(Mode.getChecked(self, next)); + } + + pub fn setUserModeRegister(self: *Self, idx: usize, value: u32) void { + const current = Mode.getChecked(self, self.cpsr.mode.read()); + + switch (idx) { + 8...12 => { + if (current == .Fiq) { + self.bank.fiq[Bank.fiqIdx(idx - 8, .User)] = value; + } else self.r[idx] = value; + }, + 13, 14 => switch (current) { + .User, .System => self.r[idx] = value, + else => { + const kind = std.meta.intToEnum(Bank.Kind, idx - 13) catch unreachable; + self.bank.r[Bank.regIdx(.User, kind)] = value; + }, + }, + else => self.r[idx] = value, // R0 -> R7 and R15 + } + } + + pub fn getUserModeRegister(self: *Self, idx: usize) u32 { + const current = Mode.getChecked(self, self.cpsr.mode.read()); + + return switch (idx) { + 8...12 => if (current == .Fiq) self.bank.fiq[Bank.fiqIdx(idx - 8, .User)] else self.r[idx], + 13, 14 => switch (current) { + .User, .System => self.r[idx], + else => blk: { + const kind = std.meta.intToEnum(Bank.Kind, idx - 13) catch unreachable; + break :blk self.bank.r[Bank.regIdx(.User, kind)]; + }, + }, + else => self.r[idx], // R0 -> R7 and R15 + }; + } + + pub fn changeMode(self: *Self, next: Mode) void { + const now = Mode.getChecked(self, self.cpsr.mode.read()); + + // Bank R8 -> r12 + for (0..5) |i| { + self.bank.fiq[Bank.fiqIdx(i, now)] = self.r[8 + i]; + } + + // Bank r13, r14, SPSR + switch (now) { + .User, .System => { + self.bank.r[Bank.regIdx(now, .R13)] = self.r[13]; + self.bank.r[Bank.regIdx(now, .R14)] = self.r[14]; + }, + else => { + self.bank.r[Bank.regIdx(now, .R13)] = self.r[13]; + self.bank.r[Bank.regIdx(now, .R14)] = self.r[14]; + self.bank.spsr[Bank.spsrIdx(now)] = self.spsr; + }, + } + + // Grab R8 -> R12 + for (0..5) |i| { + self.r[8 + i] = self.bank.fiq[Bank.fiqIdx(i, next)]; + } + + // Grab r13, r14, SPSR + switch (next) { + .User, .System => { + self.r[13] = self.bank.r[Bank.regIdx(next, .R13)]; + self.r[14] = self.bank.r[Bank.regIdx(next, .R14)]; + }, + else => { + self.r[13] = self.bank.r[Bank.regIdx(next, .R13)]; + self.r[14] = self.bank.r[Bank.regIdx(next, .R14)]; + self.spsr = self.bank.spsr[Bank.spsrIdx(next)]; + }, + } + + self.cpsr.mode.write(@enumToInt(next)); + } + + pub fn step(self: *Self) void { + defer { + if (!self.pipe.flushed) self.r[15] += if (self.cpsr.t.read()) 2 else @as(u32, 4); + self.pipe.flushed = false; + } + + if (self.cpsr.t.read()) { + const opcode = @truncate(u16, self.pipe.step(self, u16) orelse return); + thumb.lut[thumb.idx(opcode)](self, self.bus, opcode); + } else { + const opcode = self.pipe.step(self, u32) orelse return; + + if (self.cpsr.check(@truncate(u4, opcode >> 28))) { + arm.lut[arm.idx(opcode)](self, self.bus, opcode); + } + } + } + + inline fn fetch(self: *Self, comptime T: type, address: u32) T { + comptime std.debug.assert(T == u32 or T == u16); // Opcode may be 32-bit (ARM) or 16-bit (THUMB) + + // Bus.read will advance the scheduler. There are different timings for CPU fetches, + // so we want to undo what Bus.read will apply. We can do this by caching the current tick + // This is very dumb. + // + // FIXME: Please rework this + // FIXME: Please Re-enable this + // const tick_cache = self.sched.tick; + // defer self.sched.tick = tick_cache + Bus.fetch_timings[@boolToInt(T == u32)][@truncate(u4, address >> 24)]; + + return self.bus.read(T, address); + } + + pub fn panic(self: *const Self, comptime format: []const u8, args: anytype) noreturn { + var i: usize = 0; + while (i < 16) : (i += 4) { + const i_1 = i + 1; + const i_2 = i + 2; + const i_3 = i + 3; + std.debug.print("R{}: 0x{X:0>8}\tR{}: 0x{X:0>8}\tR{}: 0x{X:0>8}\tR{}: 0x{X:0>8}\n", .{ i, self.r[i], i_1, self.r[i_1], i_2, self.r[i_2], i_3, self.r[i_3] }); + } + std.debug.print("cpsr: 0x{X:0>8} ", .{self.cpsr.raw}); + self.cpsr.toString(); + + std.debug.print("spsr: 0x{X:0>8} ", .{self.spsr.raw}); + self.spsr.toString(); + + std.debug.print("pipeline: {??X:0>8}\n", .{self.pipe.stage}); + + if (self.cpsr.t.read()) { + const opcode = self.bus.dbgRead(u16, self.r[15] - 4); + const id = thumb.idx(opcode); + std.debug.print("opcode: ID: 0x{b:0>10} 0x{X:0>4}\n", .{ id, opcode }); + } else { + const opcode = self.bus.dbgRead(u32, self.r[15] - 4); + const id = arm.idx(opcode); + std.debug.print("opcode: ID: 0x{X:0>3} 0x{X:0>8}\n", .{ id, opcode }); + } + + std.debug.print("tick: {}\n\n", .{self.sched.now()}); + + std.debug.panic(format, args); + } + + pub fn interface(self: *Self) Interpreter { + return switch (arch) { + .v4t => .{ .v4t = self }, + .v5te => .{ .v5te = self }, + }; + } + }; +} + +pub const Mode = enum(u5) { + User = 0b10000, + Fiq = 0b10001, + Irq = 0b10010, + Supervisor = 0b10011, + Abort = 0b10111, + Undefined = 0b11011, + System = 0b11111, + + pub fn toString(self: Mode) []const u8 { + return switch (self) { + .User => "usr", + .Fiq => "fiq", + .Irq => "irq", + .Supervisor => "svc", + .Abort => "abt", + .Undefined => "und", + .System => "sys", + }; + } + + fn get(bits: u5) ?Mode { + return std.meta.intToEnum(Mode, bits) catch null; + } + + fn getChecked(cpu: anytype, bits: u5) Mode { + return get(bits) orelse cpu.panic("[CPU/CPSR] 0b{b:0>5} is an invalid CPU mode", .{bits}); + } +}; + +pub const PSR = extern union { + mode: Bitfield(u32, 0, 5), + t: Bit(u32, 5), + f: Bit(u32, 6), + i: Bit(u32, 7), + v: Bit(u32, 28), + c: Bit(u32, 29), + z: Bit(u32, 30), + n: Bit(u32, 31), + raw: u32, + + fn toString(self: @This()) void { + std.debug.print("[", .{}); + + if (self.n.read()) std.debug.print("N", .{}) else std.debug.print("-", .{}); + if (self.z.read()) std.debug.print("Z", .{}) else std.debug.print("-", .{}); + if (self.c.read()) std.debug.print("C", .{}) else std.debug.print("-", .{}); + if (self.v.read()) std.debug.print("V", .{}) else std.debug.print("-", .{}); + if (self.i.read()) std.debug.print("I", .{}) else std.debug.print("-", .{}); + if (self.f.read()) std.debug.print("F", .{}) else std.debug.print("-", .{}); + if (self.t.read()) std.debug.print("T", .{}) else std.debug.print("-", .{}); + std.debug.print("|", .{}); + if (Mode.get(self.mode.read())) |m| std.debug.print("{s}", .{m.toString()}) else std.debug.print("---", .{}); + + std.debug.print("]\n", .{}); + } + + pub inline fn check(self: @This(), cond: u4) bool { + const flags = @truncate(u4, self.raw >> 28); + + return condition_lut[cond] & (@as(u16, 1) << flags) != 0; + } +}; diff --git a/src/arm/cpu/arm/block_data_transfer.zig b/src/arm/cpu/arm/block_data_transfer.zig new file mode 100644 index 0000000..71a5997 --- /dev/null +++ b/src/arm/cpu/arm/block_data_transfer.zig @@ -0,0 +1,111 @@ +const Bus = @import("../../../lib.zig").Bus; + +pub fn blockDataTransfer(comptime InstrFn: type, comptime P: bool, comptime U: bool, comptime S: bool, comptime W: bool, comptime L: bool) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, bus: Bus, opcode: u32) void { + const rn = @truncate(u4, opcode >> 16 & 0xF); + const rlist = opcode & 0xFFFF; + const r15 = rlist >> 15 & 1 == 1; + + var count: u32 = 0; + var i: u5 = 0; + var first: u4 = 0; + var write_to_base = true; + + while (i < 16) : (i += 1) { + const r = @truncate(u4, 15 - i); + if (rlist >> r & 1 == 1) { + first = r; + count += 1; + } + } + + var start = cpu.r[rn]; + if (U) { + start += if (P) 4 else 0; + } else { + start = start - (4 * count) + if (!P) 4 else 0; + } + + var end = cpu.r[rn]; + if (U) { + end = end + (4 * count) - if (!P) 4 else 0; + } else { + end -= if (P) 4 else 0; + } + + var new_base = cpu.r[rn]; + if (U) { + new_base += 4 * count; + } else { + new_base -= 4 * count; + } + + var address = start; + + if (rlist == 0) { + var und_addr = cpu.r[rn]; + if (U) { + und_addr += if (P) 4 else 0; + } else { + und_addr -= 0x40 - if (!P) 4 else 0; + } + + if (L) { + cpu.r[15] = bus.read(u32, und_addr); + cpu.pipe.reload(cpu); + } else { + bus.write(u32, und_addr, cpu.r[15] + 4); + } + + cpu.r[rn] = if (U) cpu.r[rn] + 0x40 else cpu.r[rn] - 0x40; + return; + } + + i = first; + while (i < 16) : (i += 1) { + if (rlist >> i & 1 == 1) { + transfer(cpu, bus, r15, i, address); + address += 4; + + if (W and !L and write_to_base) { + cpu.r[rn] = new_base; + write_to_base = false; + } + } + } + + if (W and L and rlist >> rn & 1 == 0) cpu.r[rn] = new_base; + } + + fn transfer(cpu: Arm32, bus: Bus, r15_present: bool, i: u5, address: u32) void { + if (L) { + if (S and !r15_present) { + // Always Transfer User mode Registers + cpu.setUserModeRegister(i, bus.read(u32, address)); + } else { + const value = bus.read(u32, address); + + cpu.r[i] = value; + if (i == 0xF) { + cpu.r[i] &= ~@as(u32, 3); // Align r15 + cpu.pipe.reload(cpu); + + if (S) cpu.setCpsr(cpu.spsr.raw); + } + } + } else { + if (S) { + // Always Transfer User mode Registers + // This happens regardless if r15 is in the list + const value = cpu.getUserModeRegister(i); + bus.write(u32, address, value + if (i == 0xF) 4 else @as(u32, 0)); // PC is already 8 ahead to make 12 + } else { + bus.write(u32, address, cpu.r[i] + if (i == 0xF) 4 else @as(u32, 0)); + } + } + } + }.inner; +} diff --git a/src/arm/cpu/arm/branch.zig b/src/arm/cpu/arm/branch.zig new file mode 100644 index 0000000..a43b316 --- /dev/null +++ b/src/arm/cpu/arm/branch.zig @@ -0,0 +1,31 @@ +const Bus = @import("../../../lib.zig").Bus; +const sext = @import("zba-util").sext; + +pub fn branch(comptime InstrFn: type, comptime L: bool) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, _: Bus, opcode: u32) void { + if (L) cpu.r[14] = cpu.r[15] - 4; + + cpu.r[15] +%= sext(u32, u24, opcode) << 2; + cpu.pipe.reload(cpu); + } + }.inner; +} + +pub fn branchAndExchange(comptime InstrFn: type) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + pub fn inner(cpu: Arm32, _: Bus, opcode: u32) void { + const rn = opcode & 0xF; + + const thumb = cpu.r[rn] & 1 == 1; + cpu.r[15] = cpu.r[rn] & if (thumb) ~@as(u32, 1) else ~@as(u32, 3); + + cpu.cpsr.t.write(thumb); + cpu.pipe.reload(cpu); + } + }.inner; +} diff --git a/src/arm/cpu/arm/data_processing.zig b/src/arm/cpu/arm/data_processing.zig new file mode 100644 index 0000000..c233c91 --- /dev/null +++ b/src/arm/cpu/arm/data_processing.zig @@ -0,0 +1,185 @@ +const Bus = @import("../../../lib.zig").Bus; + +const exec = @import("../barrel_shifter.zig").exec; +const ror = @import("../barrel_shifter.zig").ror; + +pub fn dataProcessing(comptime InstrFn: type, comptime I: bool, comptime S: bool, comptime kind: u4) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, _: Bus, opcode: u32) void { + const rd = @truncate(u4, opcode >> 12 & 0xF); + const rn = opcode >> 16 & 0xF; + const old_carry = @boolToInt(cpu.cpsr.c.read()); + + // If certain conditions are met, PC is 12 ahead instead of 8 + // TODO: Why these conditions? + if (!I and opcode >> 4 & 1 == 1) cpu.r[15] += 4; + const op1 = cpu.r[rn]; + + const amount = @truncate(u8, (opcode >> 8 & 0xF) << 1); + const op2 = if (I) ror(S, &cpu.cpsr, opcode & 0xFF, amount) else exec(S, cpu, opcode); + + // Undo special condition from above + if (!I and opcode >> 4 & 1 == 1) cpu.r[15] -= 4; + + var result: u32 = undefined; + var overflow: u1 = undefined; + + // Perform Data Processing Logic + switch (kind) { + 0x0 => result = op1 & op2, // AND + 0x1 => result = op1 ^ op2, // EOR + 0x2 => result = op1 -% op2, // SUB + 0x3 => result = op2 -% op1, // RSB + 0x4 => result = add(&overflow, op1, op2), // ADD + 0x5 => result = adc(&overflow, op1, op2, old_carry), // ADC + 0x6 => result = sbc(op1, op2, old_carry), // SBC + 0x7 => result = sbc(op2, op1, old_carry), // RSC + 0x8 => { + // TST + if (rd == 0xF) + return undefinedTestBehaviour(cpu); + + result = op1 & op2; + }, + 0x9 => { + // TEQ + if (rd == 0xF) + return undefinedTestBehaviour(cpu); + + result = op1 ^ op2; + }, + 0xA => { + // CMP + if (rd == 0xF) + return undefinedTestBehaviour(cpu); + + result = op1 -% op2; + }, + 0xB => { + // CMN + if (rd == 0xF) + return undefinedTestBehaviour(cpu); + + const tmp = @addWithOverflow(op1, op2); + result = tmp[0]; + overflow = tmp[1]; + }, + 0xC => result = op1 | op2, // ORR + 0xD => result = op2, // MOV + 0xE => result = op1 & ~op2, // BIC + 0xF => result = ~op2, // MVN + } + + // Write to Destination Register + switch (kind) { + 0x8, 0x9, 0xA, 0xB => {}, // Test Operations + else => { + cpu.r[rd] = result; + if (rd == 0xF) { + if (S) cpu.setCpsr(cpu.spsr.raw); + cpu.pipe.reload(cpu); + } + }, + } + + // Write Flags + switch (kind) { + 0x0, 0x1, 0xC, 0xD, 0xE, 0xF => if (S and rd != 0xF) { + // Logic Operation Flags + cpu.cpsr.n.write(result >> 31 & 1 == 1); + cpu.cpsr.z.write(result == 0); + // C set by Barrel Shifter, V is unaffected + + }, + 0x2, 0x3 => if (S and rd != 0xF) { + // SUB, RSB Flags + cpu.cpsr.n.write(result >> 31 & 1 == 1); + cpu.cpsr.z.write(result == 0); + + if (kind == 0x2) { + // SUB specific + cpu.cpsr.c.write(op2 <= op1); + cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1); + } else { + // RSB Specific + cpu.cpsr.c.write(op1 <= op2); + cpu.cpsr.v.write(((op2 ^ result) & (~op1 ^ result)) >> 31 & 1 == 1); + } + }, + 0x4, 0x5 => if (S and rd != 0xF) { + // ADD, ADC Flags + cpu.cpsr.n.write(result >> 31 & 1 == 1); + cpu.cpsr.z.write(result == 0); + cpu.cpsr.c.write(overflow == 0b1); + cpu.cpsr.v.write(((op1 ^ result) & (op2 ^ result)) >> 31 & 1 == 1); + }, + 0x6, 0x7 => if (S and rd != 0xF) { + // SBC, RSC Flags + cpu.cpsr.n.write(result >> 31 & 1 == 1); + cpu.cpsr.z.write(result == 0); + + if (kind == 0x6) { + // SBC specific + const subtrahend = @as(u64, op2) -% old_carry +% 1; + cpu.cpsr.c.write(subtrahend <= op1); + cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1); + } else { + // RSC Specific + const subtrahend = @as(u64, op1) -% old_carry +% 1; + cpu.cpsr.c.write(subtrahend <= op2); + cpu.cpsr.v.write(((op2 ^ result) & (~op1 ^ result)) >> 31 & 1 == 1); + } + }, + 0x8, 0x9, 0xA, 0xB => { + // Test Operation Flags + cpu.cpsr.n.write(result >> 31 & 1 == 1); + cpu.cpsr.z.write(result == 0); + + if (kind == 0xA) { + // CMP specific + cpu.cpsr.c.write(op2 <= op1); + cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1); + } else if (kind == 0xB) { + // CMN specific + cpu.cpsr.c.write(overflow == 0b1); + cpu.cpsr.v.write(((op1 ^ result) & (op2 ^ result)) >> 31 & 1 == 1); + } else { + // TST, TEQ specific + // Barrel Shifter should always calc CPSR C in TST + if (!S) _ = exec(true, cpu, opcode); + } + }, + } + } + + fn undefinedTestBehaviour(cpu: Arm32) void { + @setCold(true); + cpu.setCpsr(cpu.spsr.raw); + } + }.inner; +} + +pub fn sbc(left: u32, right: u32, old_carry: u1) u32 { + // TODO: Make your own version (thanks peach.bot) + const subtrahend = @as(u64, right) -% old_carry +% 1; + const ret = @truncate(u32, left -% subtrahend); + + return ret; +} + +pub fn add(overflow: *u1, left: u32, right: u32) u32 { + const ret = @addWithOverflow(left, right); + overflow.* = ret[1]; + + return ret[0]; +} + +pub fn adc(overflow: *u1, left: u32, right: u32, old_carry: u1) u32 { + const tmp = @addWithOverflow(left, right); + const ret = @addWithOverflow(tmp[0], old_carry); + overflow.* = tmp[1] | ret[1]; + + return ret[0]; +} diff --git a/src/arm/cpu/arm/half_signed_data_transfer.zig b/src/arm/cpu/arm/half_signed_data_transfer.zig new file mode 100644 index 0000000..90ca3c1 --- /dev/null +++ b/src/arm/cpu/arm/half_signed_data_transfer.zig @@ -0,0 +1,53 @@ +const Bus = @import("../../../lib.zig").Bus; + +const sext = @import("zba-util").sext; +const rotr = @import("zba-util").rotr; + +pub fn halfAndSignedDataTransfer(comptime InstrFn: type, comptime P: bool, comptime U: bool, comptime I: bool, comptime W: bool, comptime L: bool) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, bus: Bus, opcode: u32) void { + const rn = opcode >> 16 & 0xF; + const rd = opcode >> 12 & 0xF; + const rm = opcode & 0xF; + const imm_offset_high = opcode >> 8 & 0xF; + + const base = cpu.r[rn] + if (!L and rn == 0xF) 4 else @as(u32, 0); + const offset = if (I) imm_offset_high << 4 | rm else cpu.r[rm]; + + const modified_base = if (U) base +% offset else base -% offset; + var address = if (P) modified_base else base; + + var result: u32 = undefined; + if (L) { + switch (@truncate(u2, opcode >> 5)) { + 0b01 => { + // LDRH + const value = bus.read(u16, address); + result = rotr(u32, value, 8 * (address & 1)); + }, + 0b10 => { + // LDRSB + result = sext(u32, u8, bus.read(u8, address)); + }, + 0b11 => { + // LDRSH + const value = bus.read(u16, address); + result = if (address & 1 == 1) sext(u32, u8, @truncate(u8, value >> 8)) else sext(u32, u16, value); + }, + 0b00 => unreachable, // SWP + } + } else { + if (opcode >> 5 & 0x01 == 0x01) { + // STRH + bus.write(u16, address, @truncate(u16, cpu.r[rd])); + } else unreachable; // SWP + } + + address = modified_base; + if (W and P or !P) cpu.r[rn] = address; + if (L) cpu.r[rd] = result; // // This emulates the LDR rd == rn behaviour + } + }.inner; +} diff --git a/src/arm/cpu/arm/multiply.zig b/src/arm/cpu/arm/multiply.zig new file mode 100644 index 0000000..0211851 --- /dev/null +++ b/src/arm/cpu/arm/multiply.zig @@ -0,0 +1,59 @@ +const Bus = @import("../../../lib.zig").Bus; + +pub fn multiply(comptime InstrFn: type, comptime A: bool, comptime S: bool) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, _: Bus, opcode: u32) void { + const rd = opcode >> 16 & 0xF; + const rn = opcode >> 12 & 0xF; + const rs = opcode >> 8 & 0xF; + const rm = opcode & 0xF; + + const temp: u64 = @as(u64, cpu.r[rm]) * @as(u64, cpu.r[rs]) + if (A) cpu.r[rn] else 0; + const result = @truncate(u32, temp); + 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; +} + +pub fn multiplyLong(comptime InstrFn: type, comptime U: bool, comptime A: bool, comptime S: bool) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, _: Bus, opcode: u32) void { + const rd_hi = opcode >> 16 & 0xF; + const rd_lo = opcode >> 12 & 0xF; + const rs = opcode >> 8 & 0xF; + const rm = opcode & 0xF; + + if (U) { + // Signed (WHY IS IT U THEN?) + var result: i64 = @as(i64, @bitCast(i32, cpu.r[rm])) * @as(i64, @bitCast(i32, cpu.r[rs])); + if (A) result +%= @bitCast(i64, @as(u64, cpu.r[rd_hi]) << 32 | @as(u64, cpu.r[rd_lo])); + + cpu.r[rd_hi] = @bitCast(u32, @truncate(i32, result >> 32)); + cpu.r[rd_lo] = @bitCast(u32, @truncate(i32, result)); + } else { + // Unsigned + var result: u64 = @as(u64, cpu.r[rm]) * @as(u64, cpu.r[rs]); + if (A) result +%= @as(u64, cpu.r[rd_hi]) << 32 | @as(u64, cpu.r[rd_lo]); + + cpu.r[rd_hi] = @truncate(u32, result >> 32); + cpu.r[rd_lo] = @truncate(u32, result); + } + + if (S) { + cpu.cpsr.z.write(cpu.r[rd_hi] == 0 and cpu.r[rd_lo] == 0); + cpu.cpsr.n.write(cpu.r[rd_hi] >> 31 & 1 == 1); + // C and V are set to meaningless values + } + } + }.inner; +} diff --git a/src/arm/cpu/arm/psr_transfer.zig b/src/arm/cpu/arm/psr_transfer.zig new file mode 100644 index 0000000..9aaffa6 --- /dev/null +++ b/src/arm/cpu/arm/psr_transfer.zig @@ -0,0 +1,59 @@ +const std = @import("std"); + +const Bus = @import("../../../lib.zig").Bus; +const PSR = @import("../../../arm.zig").PSR; + +const log = std.log.scoped(.PsrTransfer); + +const rotr = @import("zba-util").rotr; + +pub fn psrTransfer(comptime InstrFn: type, comptime I: bool, comptime R: bool, comptime kind: u2) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, _: Bus, opcode: u32) void { + switch (kind) { + 0b00 => { + // MRS + const rd = opcode >> 12 & 0xF; + + if (R and !cpu.hasSPSR()) log.err("Tried to read SPSR from User/System Mode", .{}); + cpu.r[rd] = if (R) cpu.spsr.raw else cpu.cpsr.raw; + }, + 0b10 => { + // MSR + const field_mask = @truncate(u4, opcode >> 16 & 0xF); + const rm_idx = opcode & 0xF; + const right = if (I) rotr(u32, opcode & 0xFF, (opcode >> 8 & 0xF) * 2) else cpu.r[rm_idx]; + + if (R and !cpu.hasSPSR()) log.err("Tried to write to SPSR in User/System Mode", .{}); + + if (R) { + // arm.gba seems to expect the SPSR to do somethign in SYS mode, + // so we just assume that despite writing to the SPSR in USR or SYS mode + // being UNPREDICTABLE, it just magically has a working SPSR somehow + cpu.spsr.raw = fieldMask(&cpu.spsr, field_mask, right); + } else { + if (cpu.isPrivileged()) cpu.setCpsr(fieldMask(&cpu.cpsr, field_mask, right)); + } + }, + else => cpu.panic("[CPU/PSR Transfer] Bits 21:220 of {X:0>8} are undefined", .{opcode}), + } + } + }.inner; +} + +fn fieldMask(psr: *const PSR, field_mask: u4, right: u32) u32 { + // This bitwise ORs bits 3 and 0 of the field mask into a u2 + // We do this because we only care about bits 7:0 and 31:28 of the CPSR + const bits = @truncate(u2, (field_mask >> 2 & 0x2) | (field_mask & 1)); + + const mask: u32 = switch (bits) { + 0b00 => 0x0000_0000, + 0b01 => 0x0000_00FF, + 0b10 => 0xF000_0000, + 0b11 => 0xF000_00FF, + }; + + return (psr.raw & ~mask) | (right & mask); +} diff --git a/src/arm/cpu/arm/single_data_swap.zig b/src/arm/cpu/arm/single_data_swap.zig new file mode 100644 index 0000000..d64d5be --- /dev/null +++ b/src/arm/cpu/arm/single_data_swap.zig @@ -0,0 +1,29 @@ +const Bus = @import("../../../lib.zig").Bus; + +const rotr = @import("zba-util").rotr; + +pub fn singleDataSwap(comptime InstrFn: type, comptime B: bool) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, bus: Bus, opcode: u32) void { + const rn = opcode >> 16 & 0xF; + const rd = opcode >> 12 & 0xF; + const rm = opcode & 0xF; + + const address = cpu.r[rn]; + + if (B) { + // SWPB + const value = bus.read(u8, address); + bus.write(u8, address, @truncate(u8, cpu.r[rm])); + cpu.r[rd] = value; + } else { + // SWP + const value = rotr(u32, bus.read(u32, address), 8 * (address & 0x3)); + bus.write(u32, address, cpu.r[rm]); + cpu.r[rd] = value; + } + } + }.inner; +} diff --git a/src/arm/cpu/arm/single_data_transfer.zig b/src/arm/cpu/arm/single_data_transfer.zig new file mode 100644 index 0000000..88163fc --- /dev/null +++ b/src/arm/cpu/arm/single_data_transfer.zig @@ -0,0 +1,55 @@ +const shifter = @import("../barrel_shifter.zig"); +const Bus = @import("../../../lib.zig").Bus; + +const rotr = @import("zba-util").rotr; + +pub fn singleDataTransfer(comptime InstrFn: type, comptime I: bool, comptime P: bool, comptime U: bool, comptime B: bool, comptime W: bool, comptime L: bool) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, bus: Bus, opcode: u32) void { + const rn = opcode >> 16 & 0xF; + const rd = opcode >> 12 & 0xF; + + const base = cpu.r[rn]; + const offset = if (I) shifter.immediate(false, cpu, opcode) else opcode & 0xFFF; + + const modified_base = if (U) base +% offset else base -% offset; + var address = if (P) modified_base else base; + + var result: u32 = undefined; + if (L) { + if (B) { + // LDRB + result = bus.read(u8, address); + } else { + // LDR + const value = bus.read(u32, address); + result = rotr(u32, value, 8 * (address & 0x3)); + } + } else { + if (B) { + // STRB + const value = cpu.r[rd] + if (rd == 0xF) 4 else @as(u32, 0); // PC is 12 ahead + bus.write(u8, address, @truncate(u8, value)); + } else { + // STR + const value = cpu.r[rd] + if (rd == 0xF) 4 else @as(u32, 0); + bus.write(u32, address, value); + } + } + + address = modified_base; + if (W and P or !P) { + cpu.r[rn] = address; + if (rn == 0xF) cpu.pipe.reload(cpu); + } + + if (L) { + // This emulates the LDR rd == rn behaviour + cpu.r[rd] = result; + if (rd == 0xF) cpu.pipe.reload(cpu); + } + } + }.inner; +} diff --git a/src/arm/cpu/arm/software_interrupt.zig b/src/arm/cpu/arm/software_interrupt.zig new file mode 100644 index 0000000..c8d703f --- /dev/null +++ b/src/arm/cpu/arm/software_interrupt.zig @@ -0,0 +1,23 @@ +const Bus = @import("../../../lib.zig").Bus; + +pub fn armSoftwareInterrupt(comptime InstrFn: type) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, _: Bus, _: u32) void { + // Copy Values from Current Mode + const ret_addr = cpu.r[15] - 4; + const cpsr = cpu.cpsr.raw; + + // Switch Mode + cpu.changeMode(.Supervisor); + cpu.cpsr.t.write(false); // Force ARM Mode + cpu.cpsr.i.write(true); // Disable normal interrupts + + cpu.r[14] = ret_addr; // Resume Execution + cpu.spsr.raw = cpsr; // Previous mode CPSR + cpu.r[15] = 0x0000_0008; + cpu.pipe.reload(cpu); + } + }.inner; +} diff --git a/src/arm/cpu/barrel_shifter.zig b/src/arm/cpu/barrel_shifter.zig new file mode 100644 index 0000000..394930a --- /dev/null +++ b/src/arm/cpu/barrel_shifter.zig @@ -0,0 +1,146 @@ +const CPSR = @import("../../arm.zig").PSR; + +const rotr = @import("zba-util").rotr; + +pub fn exec(comptime S: bool, cpu: anytype, opcode: u32) u32 { + var result: u32 = undefined; + if (opcode >> 4 & 1 == 1) { + result = register(S, cpu, opcode); + } else { + result = immediate(S, cpu, opcode); + } + + return result; +} + +fn register(comptime S: bool, cpu: anytype, opcode: u32) u32 { + const rs_idx = opcode >> 8 & 0xF; + const rm = cpu.r[opcode & 0xF]; + const rs = @truncate(u8, cpu.r[rs_idx]); + + return switch (@truncate(u2, opcode >> 5)) { + 0b00 => lsl(S, &cpu.cpsr, rm, rs), + 0b01 => lsr(S, &cpu.cpsr, rm, rs), + 0b10 => asr(S, &cpu.cpsr, rm, rs), + 0b11 => ror(S, &cpu.cpsr, rm, rs), + }; +} + +pub fn immediate(comptime S: bool, cpu: anytype, opcode: u32) u32 { + const amount = @truncate(u8, opcode >> 7 & 0x1F); + const rm = cpu.r[opcode & 0xF]; + + var result: u32 = undefined; + if (amount == 0) { + switch (@truncate(u2, opcode >> 5)) { + 0b00 => { + // LSL #0 + result = rm; + }, + 0b01 => { + // LSR #0 aka LSR #32 + if (S) cpu.cpsr.c.write(rm >> 31 & 1 == 1); + result = 0x0000_0000; + }, + 0b10 => { + // ASR #0 aka ASR #32 + result = @bitCast(u32, @bitCast(i32, rm) >> 31); + if (S) cpu.cpsr.c.write(result >> 31 & 1 == 1); + }, + 0b11 => { + // ROR #0 aka RRX + const carry: u32 = @boolToInt(cpu.cpsr.c.read()); + if (S) cpu.cpsr.c.write(rm & 1 == 1); + + result = (carry << 31) | (rm >> 1); + }, + } + } else { + switch (@truncate(u2, opcode >> 5)) { + 0b00 => result = lsl(S, &cpu.cpsr, rm, amount), + 0b01 => result = lsr(S, &cpu.cpsr, rm, amount), + 0b10 => result = asr(S, &cpu.cpsr, rm, amount), + 0b11 => result = ror(S, &cpu.cpsr, rm, amount), + } + } + + return result; +} + +pub fn lsl(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u8) u32 { + const amount = @truncate(u5, total_amount); + const bit_count: u8 = @typeInfo(u32).Int.bits; + + var result: u32 = 0x0000_0000; + if (total_amount < bit_count) { + // We can perform a well-defined shift here + result = rm << amount; + + if (S and total_amount != 0) { + const carry_bit = @truncate(u5, bit_count - amount); + cpsr.c.write(rm >> carry_bit & 1 == 1); + } + } else { + if (S) { + if (total_amount == bit_count) { + // Shifted all bits out, carry bit is bit 0 of rm + cpsr.c.write(rm & 1 == 1); + } else { + cpsr.c.write(false); + } + } + } + + return result; +} + +pub fn lsr(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u32) u32 { + const amount = @truncate(u5, total_amount); + const bit_count: u8 = @typeInfo(u32).Int.bits; + + var result: u32 = 0x0000_0000; + if (total_amount < bit_count) { + // We can perform a well-defined shift + result = rm >> amount; + if (S and total_amount != 0) cpsr.c.write(rm >> (amount - 1) & 1 == 1); + } else { + if (S) { + if (total_amount == bit_count) { + // LSR #32 + cpsr.c.write(rm >> 31 & 1 == 1); + } else { + // All bits have been shifted out, including carry bit + cpsr.c.write(false); + } + } + } + + return result; +} + +pub fn asr(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u8) u32 { + const amount = @truncate(u5, total_amount); + const bit_count: u8 = @typeInfo(u32).Int.bits; + + var result: u32 = 0x0000_0000; + if (total_amount < bit_count) { + result = @bitCast(u32, @bitCast(i32, rm) >> amount); + if (S and total_amount != 0) cpsr.c.write(rm >> (amount - 1) & 1 == 1); + } else { + // ASR #32 and ASR #>32 have the same result + result = @bitCast(u32, @bitCast(i32, rm) >> 31); + if (S) cpsr.c.write(result >> 31 & 1 == 1); + } + + return result; +} + +pub fn ror(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u8) u32 { + const result = rotr(u32, rm, total_amount); + + if (S and total_amount != 0) { + cpsr.c.write(result >> 31 & 1 == 1); + } + + return result; +} diff --git a/src/arm/cpu/thumb/alu.zig b/src/arm/cpu/thumb/alu.zig new file mode 100644 index 0000000..ba8e966 --- /dev/null +++ b/src/arm/cpu/thumb/alu.zig @@ -0,0 +1,108 @@ +const Bus = @import("../../../lib.zig").Bus; + +const adc = @import("../arm/data_processing.zig").adc; +const sbc = @import("../arm/data_processing.zig").sbc; + +const lsl = @import("../barrel_shifter.zig").lsl; +const lsr = @import("../barrel_shifter.zig").lsr; +const asr = @import("../barrel_shifter.zig").asr; +const ror = @import("../barrel_shifter.zig").ror; + +pub fn fmt4(comptime InstrFn: type, comptime op: u4) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, _: Bus, opcode: u16) void { + const rs = opcode >> 3 & 0x7; + const rd = opcode & 0x7; + const carry = @boolToInt(cpu.cpsr.c.read()); + + const op1 = cpu.r[rd]; + const op2 = cpu.r[rs]; + + var result: u32 = undefined; + var overflow: u1 = undefined; + + switch (op) { + 0x0 => result = op1 & op2, // AND + 0x1 => result = op1 ^ op2, // EOR + 0x2 => result = lsl(true, &cpu.cpsr, op1, @truncate(u8, op2)), // LSL + 0x3 => result = lsr(true, &cpu.cpsr, op1, @truncate(u8, op2)), // LSR + 0x4 => result = asr(true, &cpu.cpsr, op1, @truncate(u8, op2)), // ASR + 0x5 => result = adc(&overflow, op1, op2, carry), // ADC + 0x6 => result = sbc(op1, op2, carry), // SBC + 0x7 => result = ror(true, &cpu.cpsr, op1, @truncate(u8, op2)), // ROR + 0x8 => result = op1 & op2, // TST + 0x9 => result = 0 -% op2, // NEG + 0xA => result = op1 -% op2, // CMP + 0xB => { + // CMN + const tmp = @addWithOverflow(op1, op2); + result = tmp[0]; + overflow = tmp[1]; + }, + 0xC => result = op1 | op2, // ORR + 0xD => result = @truncate(u32, @as(u64, op2) * @as(u64, op1)), + 0xE => result = op1 & ~op2, + 0xF => result = ~op2, + } + + // Write to Destination Register + switch (op) { + 0x8, 0xA, 0xB => {}, + else => cpu.r[rd] = result, + } + + // Write Flags + switch (op) { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x7, 0xC, 0xE, 0xF => { + // Logic Operations + cpu.cpsr.n.write(result >> 31 & 1 == 1); + cpu.cpsr.z.write(result == 0); + // C set by Barrel Shifter, V is unaffected + }, + 0x8, 0xA => { + // Test Flags + // CMN (0xB) is handled with ADC + cpu.cpsr.n.write(result >> 31 & 1 == 1); + cpu.cpsr.z.write(result == 0); + + if (op == 0xA) { + // CMP specific + cpu.cpsr.c.write(op2 <= op1); + cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1); + } + }, + 0x5, 0xB => { + // ADC, CMN + cpu.cpsr.n.write(result >> 31 & 1 == 1); + cpu.cpsr.z.write(result == 0); + cpu.cpsr.c.write(overflow == 0b1); + cpu.cpsr.v.write(((op1 ^ result) & (op2 ^ result)) >> 31 & 1 == 1); + }, + 0x6 => { + // SBC + cpu.cpsr.n.write(result >> 31 & 1 == 1); + cpu.cpsr.z.write(result == 0); + + const subtrahend = @as(u64, op2) -% carry +% 1; + cpu.cpsr.c.write(subtrahend <= op1); + cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1); + }, + 0x9 => { + // NEG + cpu.cpsr.n.write(result >> 31 & 1 == 1); + cpu.cpsr.z.write(result == 0); + cpu.cpsr.c.write(op2 <= 0); + cpu.cpsr.v.write(((0 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1); + }, + 0xD => { + // Multiplication + 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 + }, + } + } + }.inner; +} diff --git a/src/arm/cpu/thumb/block_data_transfer.zig b/src/arm/cpu/thumb/block_data_transfer.zig new file mode 100644 index 0000000..3ff0b11 --- /dev/null +++ b/src/arm/cpu/thumb/block_data_transfer.zig @@ -0,0 +1,102 @@ +const Bus = @import("../../../lib.zig").Bus; + +pub fn fmt14(comptime InstrFn: type, comptime L: bool, comptime R: bool) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, bus: Bus, opcode: u16) void { + const count = @boolToInt(R) + countRlist(opcode); + const start = cpu.r[13] - if (!L) count * 4 else 0; + + var end = cpu.r[13]; + if (L) { + end += count * 4; + } else { + end -= 4; + } + + var address = start; + + var i: u4 = 0; + while (i < 8) : (i += 1) { + if (opcode >> i & 1 == 1) { + if (L) { + cpu.r[i] = bus.read(u32, address); + } else { + bus.write(u32, address, cpu.r[i]); + } + + address += 4; + } + } + + if (R) { + if (L) { + const value = bus.read(u32, address); + cpu.r[15] = value & ~@as(u32, 1); + cpu.pipe.reload(cpu); + } else { + bus.write(u32, address, cpu.r[14]); + } + address += 4; + } + + cpu.r[13] = if (L) end else start; + } + }.inner; +} + +pub fn fmt15(comptime InstrFn: type, comptime L: bool, comptime rb: u3) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, bus: Bus, opcode: u16) void { + var address = cpu.r[rb]; + const end_address = cpu.r[rb] + 4 * countRlist(opcode); + + if (opcode & 0xFF == 0) { + if (L) { + cpu.r[15] = bus.read(u32, address); + cpu.pipe.reload(cpu); + } else { + bus.write(u32, address, cpu.r[15] + 2); + } + + cpu.r[rb] += 0x40; + return; + } + + var i: u4 = 0; + var first_write = true; + + while (i < 8) : (i += 1) { + if (opcode >> i & 1 == 1) { + if (L) { + cpu.r[i] = bus.read(u32, address); + } else { + bus.write(u32, address, cpu.r[i]); + } + + if (!L and first_write) { + cpu.r[rb] = end_address; + first_write = false; + } + + address += 4; + } + } + + if (L and opcode >> rb & 1 != 1) cpu.r[rb] = address; + } + }.inner; +} + +inline fn countRlist(opcode: u16) u32 { + var count: u32 = 0; + + inline for (0..8) |i| { + if (opcode >> (7 - i) & 1 == 1) count += 1; + } + + return count; +} diff --git a/src/arm/cpu/thumb/branch.zig b/src/arm/cpu/thumb/branch.zig new file mode 100644 index 0000000..4477bff --- /dev/null +++ b/src/arm/cpu/thumb/branch.zig @@ -0,0 +1,57 @@ +const Bus = @import("../../../lib.zig").Bus; + +const sext = @import("zba-util").sext; + +pub fn fmt16(comptime InstrFn: type, comptime cond: u4) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, _: Bus, opcode: u16) void { + // B + if (cond == 0xE or cond == 0xF) + cpu.panic("[CPU/THUMB.16] Undefined conditional branch with condition {}", .{cond}); + + if (!cpu.cpsr.check(cond)) return; + + cpu.r[15] +%= sext(u32, u8, opcode & 0xFF) << 1; + cpu.pipe.reload(cpu); + } + }.inner; +} + +pub fn fmt18(comptime InstrFn: type) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + // B but conditional + fn inner(cpu: Arm32, _: Bus, opcode: u16) void { + cpu.r[15] +%= sext(u32, u11, opcode & 0x7FF) << 1; + cpu.pipe.reload(cpu); + } + }.inner; +} + +pub fn fmt19(comptime InstrFn: type, comptime is_low: bool) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, _: Bus, opcode: u16) void { + // BL + const offset = opcode & 0x7FF; + + if (is_low) { + // Instruction 2 + const next_opcode = cpu.r[15] - 2; + + cpu.r[15] = cpu.r[14] +% (offset << 1); + cpu.r[14] = next_opcode | 1; + + cpu.pipe.reload(cpu); + } else { + // Instruction 1 + const lr_offset = sext(u32, u11, offset) << 12; + cpu.r[14] = (cpu.r[15] +% lr_offset) & ~@as(u32, 1); + } + } + }.inner; +} diff --git a/src/arm/cpu/thumb/data_processing.zig b/src/arm/cpu/thumb/data_processing.zig new file mode 100644 index 0000000..400a3c8 --- /dev/null +++ b/src/arm/cpu/thumb/data_processing.zig @@ -0,0 +1,209 @@ +const Bus = @import("../../../lib.zig").Bus; + +const add = @import("../arm/data_processing.zig").add; + +const lsl = @import("../barrel_shifter.zig").lsl; +const lsr = @import("../barrel_shifter.zig").lsr; +const asr = @import("../barrel_shifter.zig").asr; + +pub fn fmt1(comptime InstrFn: type, comptime op: u2, comptime offset: u5) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, _: Bus, opcode: u16) void { + const rs = opcode >> 3 & 0x7; + const rd = opcode & 0x7; + + const result = switch (op) { + 0b00 => blk: { + // LSL + if (offset == 0) { + break :blk cpu.r[rs]; + } else { + break :blk lsl(true, &cpu.cpsr, cpu.r[rs], offset); + } + }, + 0b01 => blk: { + // LSR + if (offset == 0) { + cpu.cpsr.c.write(cpu.r[rs] >> 31 & 1 == 1); + break :blk @as(u32, 0); + } else { + break :blk lsr(true, &cpu.cpsr, cpu.r[rs], offset); + } + }, + 0b10 => blk: { + // ASR + if (offset == 0) { + cpu.cpsr.c.write(cpu.r[rs] >> 31 & 1 == 1); + break :blk @bitCast(u32, @bitCast(i32, cpu.r[rs]) >> 31); + } else { + break :blk asr(true, &cpu.cpsr, cpu.r[rs], offset); + } + }, + else => cpu.panic("[CPU/THUMB.1] 0b{b:0>2} is not a valid op", .{op}), + }; + + // Equivalent to an ARM MOVS + cpu.r[rd] = result; + + // Write Flags + cpu.cpsr.n.write(result >> 31 & 1 == 1); + cpu.cpsr.z.write(result == 0); + } + }.inner; +} + +pub fn fmt5(comptime InstrFn: type, comptime op: u2, comptime h1: u1, comptime h2: u1) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, _: Bus, opcode: u16) void { + const rs = @as(u4, h2) << 3 | (opcode >> 3 & 0x7); + const rd = @as(u4, h1) << 3 | (opcode & 0x7); + + const op1 = cpu.r[rd]; + const op2 = cpu.r[rs]; + + var result: u32 = undefined; + var overflow: u1 = undefined; + switch (op) { + 0b00 => result = add(&overflow, op1, op2), // ADD + 0b01 => result = op1 -% op2, // CMP + 0b10 => result = op2, // MOV + 0b11 => {}, + } + + // Write to Destination Register + switch (op) { + 0b01 => {}, // Test Instruction + 0b11 => { + // BX + const is_thumb = op2 & 1 == 1; + cpu.r[15] = op2 & ~@as(u32, 1); + + cpu.cpsr.t.write(is_thumb); + cpu.pipe.reload(cpu); + }, + else => { + cpu.r[rd] = result; + if (rd == 0xF) { + cpu.r[15] &= ~@as(u32, 1); + cpu.pipe.reload(cpu); + } + }, + } + + // Write Flags + switch (op) { + 0b01 => { + // CMP + cpu.cpsr.n.write(result >> 31 & 1 == 1); + cpu.cpsr.z.write(result == 0); + cpu.cpsr.c.write(op2 <= op1); + cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1); + }, + 0b00, 0b10, 0b11 => {}, // MOV and Branch Instruction + } + } + }.inner; +} + +pub fn fmt2(comptime InstrFn: type, comptime I: bool, is_sub: bool, rn: u3) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, _: Bus, opcode: u16) void { + const rs = opcode >> 3 & 0x7; + const rd = @truncate(u3, opcode); + const op1 = cpu.r[rs]; + const op2: u32 = if (I) rn else cpu.r[rn]; + + if (is_sub) { + // SUB + const result = op1 -% op2; + cpu.r[rd] = result; + + cpu.cpsr.n.write(result >> 31 & 1 == 1); + cpu.cpsr.z.write(result == 0); + cpu.cpsr.c.write(op2 <= op1); + cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1); + } else { + // ADD + var overflow: u1 = undefined; + const result = add(&overflow, op1, op2); + cpu.r[rd] = result; + + cpu.cpsr.n.write(result >> 31 & 1 == 1); + cpu.cpsr.z.write(result == 0); + cpu.cpsr.c.write(overflow == 0b1); + cpu.cpsr.v.write(((op1 ^ result) & (op2 ^ result)) >> 31 & 1 == 1); + } + } + }.inner; +} + +pub fn fmt3(comptime InstrFn: type, comptime op: u2, comptime rd: u3) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, _: Bus, opcode: u16) void { + const op1 = cpu.r[rd]; + const op2: u32 = opcode & 0xFF; // Offset + + var overflow: u1 = undefined; + const result: u32 = switch (op) { + 0b00 => op2, // MOV + 0b01 => op1 -% op2, // CMP + 0b10 => add(&overflow, op1, op2), // ADD + 0b11 => op1 -% op2, // SUB + }; + + // Write to Register + if (op != 0b01) cpu.r[rd] = result; + + // Write Flags + cpu.cpsr.n.write(result >> 31 & 1 == 1); + cpu.cpsr.z.write(result == 0); + + switch (op) { + 0b00 => {}, // MOV | C set by Barrel Shifter, V is unaffected + 0b01, 0b11 => { + // SUB, CMP + cpu.cpsr.c.write(op2 <= op1); + cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1); + }, + 0b10 => { + // ADD + cpu.cpsr.c.write(overflow == 0b1); + cpu.cpsr.v.write(((op1 ^ result) & (op2 ^ result)) >> 31 & 1 == 1); + }, + } + } + }.inner; +} + +pub fn fmt12(comptime InstrFn: type, comptime isSP: bool, comptime rd: u3) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, _: Bus, opcode: u16) void { + // ADD + const left = if (isSP) cpu.r[13] else cpu.r[15] & ~@as(u32, 2); + const right = (opcode & 0xFF) << 2; + cpu.r[rd] = left + right; + } + }.inner; +} + +pub fn fmt13(comptime InstrFn: type, comptime S: bool) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, _: Bus, opcode: u16) void { + // ADD + const offset = (opcode & 0x7F) << 2; + cpu.r[13] = if (S) cpu.r[13] - offset else cpu.r[13] + offset; + } + }.inner; +} diff --git a/src/arm/cpu/thumb/data_transfer.zig b/src/arm/cpu/thumb/data_transfer.zig new file mode 100644 index 0000000..547b611 --- /dev/null +++ b/src/arm/cpu/thumb/data_transfer.zig @@ -0,0 +1,153 @@ +const Bus = @import("../../../lib.zig").Bus; + +const rotr = @import("zba-util").rotr; +const sext = @import("zba-util").sext; + +pub fn fmt6(comptime InstrFn: type, comptime rd: u3) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, bus: Bus, opcode: u16) void { + // LDR + const offset = (opcode & 0xFF) << 2; + + // Bit 1 of the PC intentionally ignored + cpu.r[rd] = bus.read(u32, (cpu.r[15] & ~@as(u32, 2)) + offset); + } + }.inner; +} + +pub fn fmt78(comptime InstrFn: type, comptime op: u2, comptime T: bool) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, bus: Bus, opcode: u16) void { + const ro = opcode >> 6 & 0x7; + const rb = opcode >> 3 & 0x7; + const rd = opcode & 0x7; + + const address = cpu.r[rb] +% cpu.r[ro]; + + if (T) { + // Format 8 + switch (op) { + 0b00 => { + // STRH + bus.write(u16, address, @truncate(u16, cpu.r[rd])); + }, + 0b01 => { + // LDSB + cpu.r[rd] = sext(u32, u8, bus.read(u8, address)); + }, + 0b10 => { + // LDRH + const value = bus.read(u16, address); + cpu.r[rd] = rotr(u32, value, 8 * (address & 1)); + }, + 0b11 => { + // LDRSH + const value = bus.read(u16, address); + cpu.r[rd] = if (address & 1 == 1) sext(u32, u8, @truncate(u8, value >> 8)) else sext(u32, u16, value); + }, + } + } else { + // Format 7 + switch (op) { + 0b00 => { + // STR + bus.write(u32, address, cpu.r[rd]); + }, + 0b01 => { + // STRB + bus.write(u8, address, @truncate(u8, cpu.r[rd])); + }, + 0b10 => { + // LDR + const value = bus.read(u32, address); + cpu.r[rd] = rotr(u32, value, 8 * (address & 0x3)); + }, + 0b11 => { + // LDRB + cpu.r[rd] = bus.read(u8, address); + }, + } + } + } + }.inner; +} + +pub fn fmt9(comptime InstrFn: type, comptime B: bool, comptime L: bool, comptime offset: u5) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, 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.read(u8, address); + } else { + // LDR + const address = cpu.r[rb] + (@as(u32, offset) << 2); + const value = bus.read(u32, address); + cpu.r[rd] = rotr(u32, value, 8 * (address & 0x3)); + } + } else { + if (B) { + // STRB + const address = cpu.r[rb] + offset; + bus.write(u8, address, @truncate(u8, cpu.r[rd])); + } else { + // STR + const address = cpu.r[rb] + (@as(u32, offset) << 2); + bus.write(u32, address, cpu.r[rd]); + } + } + } + }.inner; +} + +pub fn fmt10(comptime InstrFn: type, comptime L: bool, comptime offset: u5) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, bus: Bus, opcode: u16) void { + const rb = opcode >> 3 & 0x7; + const rd = opcode & 0x7; + + const address = cpu.r[rb] + (@as(u6, offset) << 1); + + if (L) { + // LDRH + const value = bus.read(u16, address); + cpu.r[rd] = rotr(u32, value, 8 * (address & 1)); + } else { + // STRH + bus.write(u16, address, @truncate(u16, cpu.r[rd])); + } + } + }.inner; +} + +pub fn fmt11(comptime InstrFn: type, comptime L: bool, comptime rd: u3) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, bus: Bus, opcode: u16) void { + const offset = (opcode & 0xFF) << 2; + const address = cpu.r[13] + offset; + + if (L) { + // LDR + const value = bus.read(u32, address); + cpu.r[rd] = rotr(u32, value, 8 * (address & 0x3)); + } else { + // STR + bus.write(u32, address, cpu.r[rd]); + } + } + }.inner; +} diff --git a/src/arm/cpu/thumb/software_interrupt.zig b/src/arm/cpu/thumb/software_interrupt.zig new file mode 100644 index 0000000..6ac1ab0 --- /dev/null +++ b/src/arm/cpu/thumb/software_interrupt.zig @@ -0,0 +1,23 @@ +const Bus = @import("../../../lib.zig").Bus; + +pub fn fmt17(comptime InstrFn: type) InstrFn { + const Arm32 = @typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?; + + return struct { + fn inner(cpu: Arm32, _: Bus, _: u16) void { + // Copy Values from Current Mode + const ret_addr = cpu.r[15] - 2; + const cpsr = cpu.cpsr.raw; + + // Switch Mode + cpu.changeMode(.Supervisor); + cpu.cpsr.t.write(false); // Force ARM Mode + cpu.cpsr.i.write(true); // Disable normal interrupts + + cpu.r[14] = ret_addr; // Resume Execution + cpu.spsr.raw = cpsr; // Previous mode CPSR + cpu.r[15] = 0x0000_0008; + cpu.pipe.reload(cpu); + } + }.inner; +} diff --git a/src/arm/v4t.zig b/src/arm/v4t.zig new file mode 100644 index 0000000..007e9a0 --- /dev/null +++ b/src/arm/v4t.zig @@ -0,0 +1,229 @@ +const Bus = @import("../lib.zig").Bus; + +pub fn arm(comptime Arm32: type) type { + return struct { + pub const InstrFn = *const fn (*Arm32, Bus, u32) void; + pub const lut: [0x1000]InstrFn = populate(); + + const processing = @import("cpu/arm/data_processing.zig").dataProcessing; + const psrTransfer = @import("cpu/arm/psr_transfer.zig").psrTransfer; + const transfer = @import("cpu/arm/single_data_transfer.zig").singleDataTransfer; + const halfSignedTransfer = @import("cpu/arm/half_signed_data_transfer.zig").halfAndSignedDataTransfer; + const blockTransfer = @import("cpu/arm/block_data_transfer.zig").blockDataTransfer; + const branch = @import("cpu/arm/branch.zig").branch; + const branchExchange = @import("cpu/arm/branch.zig").branchAndExchange; + const swi = @import("cpu/arm/software_interrupt.zig").armSoftwareInterrupt; + const swap = @import("cpu/arm/single_data_swap.zig").singleDataSwap; + + const multiply = @import("cpu/arm/multiply.zig").multiply; + const multiplyLong = @import("cpu/arm/multiply.zig").multiplyLong; + + /// Determine index into ARM InstrFn LUT + pub fn idx(opcode: u32) u12 { + return @truncate(u12, opcode >> 20 & 0xFF) << 4 | @truncate(u12, opcode >> 4 & 0xF); + } + + // Undefined ARM Instruction handler + fn und(cpu: *Arm32, _: Bus, opcode: u32) void { + const id = idx(opcode); + cpu.panic("[CPU/Decode] ID: 0x{X:0>3} 0x{X:0>8} is an illegal opcode", .{ id, opcode }); + } + + fn populate() [0x1000]InstrFn { + return comptime comptime_blk: { + @setEvalBranchQuota(0xE000); + var table = [_]InstrFn{und} ** 0x1000; + + for (&table, 0..) |*handler, i| { + handler.* = switch (@as(u2, i >> 10)) { + 0b00 => if (i == 0x121) blk: { + break :blk branchExchange(InstrFn); + } else if (i & 0xFCF == 0x009) blk: { + const A = i >> 5 & 1 == 1; + const S = i >> 4 & 1 == 1; + break :blk multiply(InstrFn, A, S); + } else if (i & 0xFBF == 0x109) blk: { + const B = i >> 6 & 1 == 1; + break :blk swap(InstrFn, B); + } else if (i & 0xF8F == 0x089) blk: { + const U = i >> 6 & 1 == 1; + const A = i >> 5 & 1 == 1; + const S = i >> 4 & 1 == 1; + break :blk multiplyLong(InstrFn, U, A, S); + } else if (i & 0xE49 == 0x009 or i & 0xE49 == 0x049) blk: { + const P = i >> 8 & 1 == 1; + const U = i >> 7 & 1 == 1; + const I = i >> 6 & 1 == 1; + const W = i >> 5 & 1 == 1; + const L = i >> 4 & 1 == 1; + break :blk halfSignedTransfer(InstrFn, P, U, I, W, L); + } else if (i & 0xD90 == 0x100) blk: { + const I = i >> 9 & 1 == 1; + const R = i >> 6 & 1 == 1; + const kind = i >> 4 & 0x3; + break :blk psrTransfer(InstrFn, I, R, kind); + } else blk: { + const I = i >> 9 & 1 == 1; + const S = i >> 4 & 1 == 1; + const instrKind = i >> 5 & 0xF; + break :blk processing(InstrFn, I, S, instrKind); + }, + 0b01 => if (i >> 9 & 1 == 1 and i & 1 == 1) und else blk: { + const I = i >> 9 & 1 == 1; + const P = i >> 8 & 1 == 1; + const U = i >> 7 & 1 == 1; + const B = i >> 6 & 1 == 1; + const W = i >> 5 & 1 == 1; + const L = i >> 4 & 1 == 1; + break :blk transfer(InstrFn, I, P, U, B, W, L); + }, + else => switch (@as(u2, i >> 9 & 0x3)) { + // MSB is guaranteed to be 1 + 0b00 => blk: { + const P = i >> 8 & 1 == 1; + const U = i >> 7 & 1 == 1; + const S = i >> 6 & 1 == 1; + const W = i >> 5 & 1 == 1; + const L = i >> 4 & 1 == 1; + break :blk blockTransfer(InstrFn, P, U, S, W, L); + }, + 0b01 => blk: { + const L = i >> 8 & 1 == 1; + break :blk branch(InstrFn, L); + }, + 0b10 => und, // COP Data Transfer + 0b11 => if (i >> 8 & 1 == 1) swi(InstrFn) else und, // COP Data Operation + Register Transfer + }, + }; + } + + break :comptime_blk table; + }; + } + }; +} + +pub fn thumb(comptime Arm32: type) type { + return struct { + pub const InstrFn = *const fn (*Arm32, Bus, u16) void; + pub const lut: [0x400]InstrFn = populate(); + + const processing = @import("cpu/thumb/data_processing.zig"); + const alu = @import("cpu/thumb/alu.zig").fmt4; + const transfer = @import("cpu/thumb/data_transfer.zig"); + const block_transfer = @import("cpu/thumb/block_data_transfer.zig"); + const swi = @import("cpu/thumb/software_interrupt.zig").fmt17; + const branch = @import("cpu/thumb/branch.zig"); + + /// Determine index into THUMB InstrFn LUT + pub fn idx(opcode: u16) u10 { + return @truncate(u10, opcode >> 6); + } + + /// Undefined THUMB Instruction Handler + fn und(cpu: *Arm32, _: Bus, opcode: u16) void { + const id = idx(opcode); + cpu.panic("[CPU/Decode] ID: 0b{b:0>10} 0x{X:0>2} is an illegal opcode", .{ id, opcode }); + } + + fn populate() [0x400]InstrFn { + return comptime comptime_blk: { + @setEvalBranchQuota(5025); // This is exact + var table = [_]InstrFn{und} ** 0x400; + + for (&table, 0..) |*handler, i| { + handler.* = switch (@as(u3, i >> 7 & 0x7)) { + 0b000 => if (i >> 5 & 0x3 == 0b11) blk: { + const I = i >> 4 & 1 == 1; + const is_sub = i >> 3 & 1 == 1; + const rn = i & 0x7; + break :blk processing.fmt2(InstrFn, I, is_sub, rn); + } else blk: { + const op = i >> 5 & 0x3; + const offset = i & 0x1F; + break :blk processing.fmt1(InstrFn, op, offset); + }, + 0b001 => blk: { + const op = i >> 5 & 0x3; + const rd = i >> 2 & 0x7; + break :blk processing.fmt3(InstrFn, op, rd); + }, + 0b010 => switch (@as(u2, i >> 5 & 0x3)) { + 0b00 => if (i >> 4 & 1 == 1) blk: { + const op = i >> 2 & 0x3; + const h1 = i >> 1 & 1; + const h2 = i & 1; + break :blk processing.fmt5(InstrFn, op, h1, h2); + } else blk: { + const op = i & 0xF; + break :blk alu(InstrFn, op); + }, + 0b01 => blk: { + const rd = i >> 2 & 0x7; + break :blk transfer.fmt6(InstrFn, rd); + }, + else => blk: { + const op = i >> 4 & 0x3; + const T = i >> 3 & 1 == 1; + break :blk transfer.fmt78(InstrFn, op, T); + }, + }, + 0b011 => blk: { + const B = i >> 6 & 1 == 1; + const L = i >> 5 & 1 == 1; + const offset = i & 0x1F; + break :blk transfer.fmt9(InstrFn, B, L, offset); + }, + else => switch (@as(u3, i >> 6 & 0x7)) { + // MSB is guaranteed to be 1 + 0b000 => blk: { + const L = i >> 5 & 1 == 1; + const offset = i & 0x1F; + break :blk transfer.fmt10(InstrFn, L, offset); + }, + 0b001 => blk: { + const L = i >> 5 & 1 == 1; + const rd = i >> 2 & 0x7; + break :blk transfer.fmt11(InstrFn, L, rd); + }, + 0b010 => blk: { + const isSP = i >> 5 & 1 == 1; + const rd = i >> 2 & 0x7; + break :blk processing.fmt12(InstrFn, isSP, rd); + }, + 0b011 => if (i >> 4 & 1 == 1) blk: { + const L = i >> 5 & 1 == 1; + const R = i >> 2 & 1 == 1; + break :blk block_transfer.fmt14(InstrFn, L, R); + } else blk: { + const S = i >> 1 & 1 == 1; + break :blk processing.fmt13(InstrFn, S); + }, + 0b100 => blk: { + const L = i >> 5 & 1 == 1; + const rb = i >> 2 & 0x7; + + break :blk block_transfer.fmt15(InstrFn, L, rb); + }, + 0b101 => if (i >> 2 & 0xF == 0b1111) blk: { + break :blk swi(InstrFn); + } else blk: { + const cond = i >> 2 & 0xF; + break :blk branch.fmt16(InstrFn, cond); + }, + 0b110 => branch.fmt18( + InstrFn, + ), + 0b111 => blk: { + const is_low = i >> 5 & 1 == 1; + break :blk branch.fmt19(InstrFn, is_low); + }, + }, + }; + } + + break :comptime_blk table; + }; + } + }; +} diff --git a/src/lib.zig b/src/lib.zig index ecfeade..162db44 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -1,10 +1,261 @@ const std = @import("std"); const testing = std.testing; -export fn add(a: i32, b: i32) i32 { - return a + b; +const Arm32 = @import("arm.zig").Arm32; + +pub const Arm7tdmi = Arm32(.v4t); +pub const Arm946es = Arm32(.v5te); + +pub const Interpreter = union(enum) { + const Self = @This(); + + v4t: *Arm7tdmi, + v5te: *Arm946es, + + pub fn reset(self: Self) void { + switch (self) { + inline else => |s| s.reset(), + } + } + + pub fn step(self: Self) void { + switch (self) { + inline else => |s| s.step(), + } + } + + pub fn panic(self: Self, comptime format: []const u8, comptime args: anytype) noreturn { + switch (self) { + inline else => |s| s.panic(format, args), + } + } +}; + +pub const Bus = struct { + ptr: *anyopaque, + vtable: *const Vtable, + + const Vtable = struct { + read8: *const fn (ptr: *anyopaque, address: u32) u8, + read16: *const fn (ptr: *anyopaque, address: u32) u16, + read32: *const fn (ptr: *anyopaque, address: u32) u32, + + write8: *const fn (ptr: *anyopaque, address: u32, value: u8) void, + write16: *const fn (ptr: *anyopaque, address: u32, value: u16) void, + write32: *const fn (ptr: *anyopaque, address: u32, value: u32) void, + + reset: *const fn (ptr: *anyopaque) void, + }; + + pub fn init(obj: anytype) @This() { + const P = @TypeOf(obj); + const info = @typeInfo(P); + + std.debug.assert(info == .Pointer); // `anytype` is a Pointer + std.debug.assert(info.Pointer.size == .One); // Single-Item Pointer + std.debug.assert(@typeInfo(info.Pointer.child) == .Struct); // Pointer Child is a `struct` + + const alignment = info.Pointer.alignment; + + const impl = struct { + fn read8(ptr: *anyopaque, address: u32) u8 { + const self = @ptrCast(P, @alignCast(alignment, ptr)); + return self.read(u8, address); + } + + fn read16(ptr: *anyopaque, address: u32) u16 { + const self = @ptrCast(P, @alignCast(alignment, ptr)); + return self.read(u16, address); + } + + fn read32(ptr: *anyopaque, address: u32) u32 { + const self = @ptrCast(P, @alignCast(alignment, ptr)); + return self.read(u32, address); + } + + fn write8(ptr: *anyopaque, address: u32, value: u8) void { + const self = @ptrCast(P, @alignCast(alignment, ptr)); + self.write(u8, address, value); + } + + fn write16(ptr: *anyopaque, address: u32, value: u16) void { + const self = @ptrCast(P, @alignCast(alignment, ptr)); + self.write(u16, address, value); + } + + fn write32(ptr: *anyopaque, address: u32, value: u32) void { + const self = @ptrCast(P, @alignCast(alignment, ptr)); + self.write(u32, address, value); + } + + fn reset(ptr: *anyopaque) void { + const self = @ptrCast(P, @alignCast(alignment, ptr)); + self.reset(); + } + }; + + return .{ + .ptr = obj, + .vtable = &.{ + .read8 = impl.read8, + .read16 = impl.read16, + .read32 = impl.read32, + + .write8 = impl.write8, + .write16 = impl.write16, + .write32 = impl.write32, + + .reset = impl.reset, + }, + }; + } + + pub fn read(self: @This(), comptime T: type, address: u32) T { + return switch (T) { + u32 => self.vtable.read32(self.ptr, address), + u16 => self.vtable.read16(self.ptr, address), + u8 => self.vtable.read8(self.ptr, address), + else => @compileError("TODO: Fill this in"), + }; + } + + pub fn write(self: @This(), comptime T: type, address: u32, value: T) void { + switch (T) { + u32 => self.vtable.write32(self.ptr, address, value), + u16 => self.vtable.write16(self.ptr, address, value), + u8 => self.vtable.write8(self.ptr, address, value), + else => @compileError("TODO: Fill this in"), + } + } + + pub fn dbgRead(self: @This(), comptime T: type, address: u32) T { + _ = address; + _ = self; + @panic("TODO: Implement Debug Reads via Bus Interface"); + } + + pub fn dbgWrite(self: @This(), comptime T: type, address: u32, value: T) void { + _ = self; + _ = value; + _ = address; + @panic("TODO: Implement Debug Writes via Bus Interface"); + } + + pub fn reset(self: @This()) void { + self.vtable.reset(self.ptr); + } +}; + +pub const Scheduler = struct { + ptr: *anyopaque, + + // VTable + nowFn: *const fn (ptr: *anyopaque) u64, + resetFn: *const fn (ptr: *anyopaque) void, + + pub fn init(obj: anytype) @This() { + const P = @TypeOf(obj); + const info = @typeInfo(P); + + std.debug.assert(info == .Pointer); // `anytype` is a Pointer + std.debug.assert(info.Pointer.size == .One); // Single-Item Pointer + std.debug.assert(@typeInfo(info.Pointer.child) == .Struct); // Pointer Child is a `struct` + + const alignment = info.Pointer.alignment; + + const impl = struct { + fn now(ptr: *anyopaque) u64 { + const self = @ptrCast(P, @alignCast(alignment, ptr)); + return self.now(); + } + + fn reset(ptr: *anyopaque) void { + const self = @ptrCast(P, @alignCast(alignment, ptr)); + self.reset(); + } + }; + + return .{ .ptr = obj, .nowFn = impl.now, .resetFn = impl.reset }; + } + + pub fn now(self: @This()) u64 { + return self.nowFn(self.ptr); + } + + pub fn reset(self: @This()) void { + self.resetFn(self.ptr); + } +}; + +// --- +// TESTING +// --- + +const ExampleBus = struct { + _: u32 = 0, // Note: need this field so that the ptr align of *lib.ExampleBus != 0 + + pub fn read(self: *@This(), comptime T: type, address: u32) T { + _ = self; + _ = address; + return 0; + } + + pub fn write(self: *@This(), comptime T: type, address: u32, value: T) void { + _ = self; + _ = value; + _ = address; + } + + pub fn reset(self: *@This()) void { + self._ = 0; + } +}; + +const ExampleScheduler = struct { + tick: u64 = 0, + + pub fn now(self: *const @This()) u64 { + return self.tick; + } + + pub fn reset(self: *@This()) void { + self.tick = 0; + } +}; + +test "create ARMv4T interface" { + var bus_impl = ExampleBus{}; + var scheduler_impl = ExampleScheduler{}; + + const bus_interface = Bus.init(&bus_impl); + const scheduler_interface = Scheduler.init(&scheduler_impl); + + var arm7tdmi = Arm7tdmi{ + .sched = scheduler_interface, + .bus = bus_interface, + .cpsr = .{ .raw = 0x0000_001F }, + .spsr = .{ .raw = 0x0000_0000 }, + }; + + var icpu = arm7tdmi.interface(); + + icpu.reset(); + icpu.step(); } -test "basic add functionality" { - try testing.expect(add(3, 7) == 10); +test "create ARMv5TE interface" { + var bus_impl = ExampleBus{}; + var scheduler_impl = ExampleScheduler{}; + + const bus_interface = Bus.init(&bus_impl); + const scheduler_interface = Scheduler.init(&scheduler_impl); + + var arm946es = Arm946es{ + .sched = scheduler_interface, + .bus = bus_interface, + .cpsr = .{ .raw = 0x0000_001F }, + .spsr = .{ .raw = 0x0000_0000 }, + }; + + _ = arm946es.interface(); }