diff --git a/src/arm.zig b/src/arm.zig index 62a6de6..65f246d 100644 --- a/src/arm.zig +++ b/src/arm.zig @@ -27,11 +27,12 @@ const condition_lut = [_]u16{ 0x0000, // NV - never }; -pub fn Arm32(comptime arch: Architecture) type { - const is_v5te = arch == .v5te; +pub fn Arm32(comptime isa: Architecture) type { + const is_v5te = isa == .v5te; return struct { const Self = @This(); + pub const arch = isa; r: [16]u32 = [_]u32{0x00} ** 16, pipe: Pipeline = Pipeline.init(), @@ -46,12 +47,12 @@ pub fn Arm32(comptime arch: Architecture) type { itcm: if (is_v5te) Itcm else void, dtcm: if (is_v5te) Dtcm else void, - const arm = switch (arch) { + const arm = switch (isa) { .v4t => @import("arm/v4t.zig").arm, .v5te => @import("arm/v5te.zig").arm, }; - const thumb = switch (arch) { + const thumb = switch (isa) { .v4t => @import("arm/v4t.zig").thumb, .v5te => @import("arm/v5te.zig").thumb, }; @@ -388,7 +389,7 @@ pub fn Arm32(comptime arch: Architecture) type { } pub fn interface(self: *Self) Interpreter { - return switch (arch) { + return switch (isa) { .v4t => .{ .v4t = self }, .v5te => .{ .v5te = self }, }; diff --git a/src/arm/cpu/arm/block_data_transfer.zig b/src/arm/cpu/arm/block_data_transfer.zig index 42fbbbf..c1d3283 100644 --- a/src/arm/cpu/arm/block_data_transfer.zig +++ b/src/arm/cpu/arm/block_data_transfer.zig @@ -4,78 +4,101 @@ pub fn blockDataTransfer(comptime InstrFn: type, comptime P: bool, comptime U: b return struct { fn inner(cpu: *Arm32, opcode: u32) void { const rn: u4 = @truncate(opcode >> 16 & 0xF); - const rlist = opcode & 0xFFFF; - const r15 = rlist >> 15 & 1 == 1; + const rlist: u16 = @intCast(opcode & 0xFFFF); - var count: u32 = 0; - var i: u5 = 0; - var first: u4 = 0; - var write_to_base = true; + const reg_count: u32 = @popCount(rlist); + const first_in_list: u4 = @truncate(@ctz(rlist)); // note that @ctz(0x0000) and @ctz(0x0001) collide - while (i < 16) : (i += 1) { - const r: u4 = @truncate(15 - i); - if (rlist >> r & 1 == 1) { - first = r; - count += 1; - } - } + // U determines whether the LDM/STM transfer is made upwards (U == 1) + // or downwards (U == 0). - var start = cpu.r[rn]; - if (U) { - start += if (P) 4 else 0; - } else { - start = start - (4 * count) + if (!P) 4 else 0; - } + const start_addr: u32 = if (U) blk: { + break :blk cpu.r[rn] + if (P) 4 else 0; + } else blk: { + break :blk cpu.r[rn] - (4 * reg_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; - } + // FIXME : why 4 * reg_count? - var new_base = cpu.r[rn]; - if (U) { - new_base += 4 * count; - } else { - new_base -= 4 * count; - } + const new_base_addr: u32 = if (U) blk: { + break :blk cpu.r[rn] + 4 * reg_count; + } else blk: { + break :blk cpu.r[rn] - 4 * reg_count; + }; - var address = start; + var address = start_addr; + + // On Empty List: + // + // ARMv4 Only: R15 is Loaded/Stored + // ARMv4/Armv5: Rn = Rn +/- 0x40 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 (Arm32.arch == .v4t) { + const undefined_addr: u32 = if (U) blk: { + break :blk cpu.r[rn] + if (P) 4 else 0; + } else blk: { + break :blk cpu.r[rn] - 0x40 - if (!P) 4 else 0; + }; - if (L) { - cpu.r[15] = cpu.read(u32, und_addr); - cpu.pipe.reload(cpu); - } else { - cpu.write(u32, und_addr, cpu.r[15] + 4); + if (L) { + cpu.r[15] = cpu.read(u32, undefined_addr); + cpu.pipe.reload(cpu); + } else { + cpu.write(u32, undefined_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) { + // What happens when W is set and Rn is in the rlist? (STM) + // + // Armv4: Store OLD Base if Rb is FIRST entry in Rlist, otherwise store NEW base + // Armv5: Always store OLD Base + + // FIXME: This absolutely needs revisiting :skull: + + const r15_present = rlist >> 15 & 1 == 1; + var write_to_base = true; + + for (first_in_list..16) |idx| { + const i: u4 = @intCast(idx); + if (rlist >> i & 1 == 1) { - transfer(cpu, r15, i, address); + transfer(cpu, r15_present, i, address); address += 4; if (W and !L and write_to_base) { - cpu.r[rn] = new_base; + cpu.r[rn] = new_base_addr; write_to_base = false; } } } - if (W and L and rlist >> rn & 1 == 0) cpu.r[rn] = new_base; + // What happens when W is set and Rn is in the rlist? (LDM) + // + // ARMv4: No writeback + // ARMv5: writeback if Rn is "the ONLY register" or NOT the LAST register + + if (W and L) { + if (rlist >> rn & 1 == 0) { + cpu.r[rn] = new_base_addr; + return; + } + + switch (Arm32.arch) { + .v4t => {}, // No Writeback + .v5te => { + const rn_is_last = (15 - @clz(rlist)) <= rn; + + if (reg_count == 1 or !rn_is_last) { + cpu.r[rn] = new_base_addr; + } + }, + } + } } fn transfer(cpu: *Arm32, r15_present: bool, i: u5, address: u32) void {