From 30cf951d2a4ccba3ff7ce70ceeae44780af5f1a1 Mon Sep 17 00:00:00 2001 From: Rekai Musuka Date: Fri, 15 Sep 2023 14:18:40 -0500 Subject: [PATCH] feat: integrate cp15 and TCM code --- src/arm.zig | 27 +++---- src/arm/cpu/arm/coprocessor.zig | 125 ++++++++++++++++++++++++++++---- src/arm/v5te.zig | 2 + 3 files changed, 125 insertions(+), 29 deletions(-) diff --git a/src/arm.zig b/src/arm.zig index 51686c2..5a622bd 100644 --- a/src/arm.zig +++ b/src/arm.zig @@ -221,17 +221,14 @@ pub fn Arm32(comptime isa: Architecture) type { const readInt = std.mem.readIntSliceLittle; if (is_v5te) { - const itcm_base: u32 = self.itcm.base_address; - const itcm_size = self.itcm.buf.len; - const dtcm_base: u32 = self.dtcm.base_address; - const dtcm_size = self.dtcm.buf.len; + const dtcm_base = self.dtcm.base_address; + const dtcm_size = self.dtcm.virt.size; - // FIXME: verify correctness + can this be faster? - if (itcm_base < address and address < itcm_base + itcm_size) - return readInt(T, self.itcm.buf[address..][0..@sizeOf(T)]); + if (address < 0x0000_0000 + self.itcm.virt.size) + return readInt(T, self.itcm.buf[address & self.itcm.virt.mask ..][0..@sizeOf(T)]); if (dtcm_base < address and address < dtcm_base + dtcm_size) - return readInt(T, self.dtcm.buf[address..][0..@sizeOf(T)]); + return readInt(T, self.dtcm.buf[address & self.dtcm.virt.mask ..][0..@sizeOf(T)]); } return self.bus.read(T, address); @@ -241,17 +238,14 @@ pub fn Arm32(comptime isa: Architecture) type { const writeInt = std.mem.writeIntSliceLittle; if (is_v5te) { - const itcm_base: u32 = self.itcm.base_address; - const itcm_size = self.itcm.buf.len; - const dtcm_base: u32 = self.dtcm.base_address; - const dtcm_size = self.dtcm.buf.len; + const dtcm_base = self.dtcm.base_address; + const dtcm_size = self.dtcm.virt.size; - // FIXME: verify correctness + can this be faster? - if (itcm_base < address and address < itcm_base + itcm_size) - return writeInt(T, self.itcm.buf[address..][0..@sizeOf(T)], value); + if (address < 0x0000_0000 + self.itcm.virt.size) + return writeInt(T, self.itcm.buf[address & self.itcm.virt.mask ..][0..@sizeOf(T)], value); if (dtcm_base < address and address < dtcm_base + dtcm_size) - return writeInt(T, self.dtcm.buf[address..][0..@sizeOf(T)], value); + return writeInt(T, self.dtcm.buf[address & self.dtcm.virt.mask ..][0..@sizeOf(T)], value); } return self.bus.write(T, address, value); @@ -439,6 +433,7 @@ fn Tcm(comptime count: usize, comptime default_addr: u32) type { return struct { buf: [count * KiB]u8 = [_]u8{0x00} ** (count * KiB), base_address: u32 = default_addr, + virt: struct { size: u32, mask: u32 } = .{ .size = count * KiB, .mask = (count * KiB) - 1 }, }; } diff --git a/src/arm/cpu/arm/coprocessor.zig b/src/arm/cpu/arm/coprocessor.zig index 6b27d4a..6dea4f7 100644 --- a/src/arm/cpu/arm/coprocessor.zig +++ b/src/arm/cpu/arm/coprocessor.zig @@ -2,34 +2,133 @@ const std = @import("std"); const log = std.log.scoped(.coprocessor_handler); -pub fn dataTransfer(comptime InstrFn: type, comptime P: bool, comptime U: bool, comptime N: bool, comptime W: bool, comptime L: bool) InstrFn { - _ = L; - _ = W; - _ = N; - _ = U; - _ = P; +pub fn dataTransfer( + comptime InstrFn: type, + comptime P: bool, + comptime U: bool, + comptime N: bool, + comptime W: bool, + comptime L: bool, +) InstrFn { const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; return struct { fn inner(cpu: *Arm32, opcode: u32) void { - _ = cpu; + if (!P and !W and !U) return copExt(cpu, opcode); // Coprocessor Extension Space - log.err("TODO: handle 0x{X:0>8} which is a coprocessor data transfer instr", .{opcode}); + const rn = opcode >> 16 & 0xF; + const crd = opcode >> 12 & 0xF; + const cp_num = opcode >> 8 & 0xF; + const offset = (opcode & 0xFF) << 2; + + // TODO: Make sure this is comptime + const addr_mode: u2 = comptime @as(u2, @intFromBool(P)) << 1 | @intFromBool(W); + + const start_address: u32 = switch (addr_mode) { + 0b00 => blk: { + // Unindexed Addressing + std.debug.assert(U == true); + + break :blk cpu.r[rn]; + }, + 0b01 => blk: { + // Immediate Post-Indexed Addressing + const addr = cpu.r[rn]; + cpu.r[rn] = if (U) cpu.r[rn] + offset else cpu.r[rn] - offset; + + break :blk addr; + }, + 0b10 => if (U) cpu.r[rn] + offset else cpu.r[rn] - offset, // Immediate Offset Addressing + 0b11 => blk: { + // Immediate Pre-Indexed Addressing + cpu.r[rn] = if (U) cpu.r[rn] + offset else cpu.r[rn] - offset; + + break :blk cpu.r[rn]; + }, + }; + + // TODO: Increment address + 4 (and perform op) until coprocessor says stop + + if (L) { + log.debug("TODO: ldc{s} p{}, c{}, 0x{X:0>8}", .{ [_]u8{if (N) 'l' else ' '}, cp_num, crd, start_address }); + } else { + log.debug("TODO: stc{s} p{}, c{}, 0x{X:0>8}", .{ [_]u8{if (N) 'l' else ' '}, cp_num, crd, start_address }); + } + } + + fn copExt(cpu: *Arm32, opcode: u32) void { + _ = cpu; + const cp_num = opcode >> 8 & 0xF; + const rd = opcode >> 12 & 0xF; + const rn = opcode >> 16 & 0xF; + const crm = opcode & 0xF; + + const cp_opcode = opcode >> 4 & 0xF; // FIXME: We could get this value at comptime + + std.debug.assert(rd != 15); // UNPREDICTABLE + std.debug.assert(rn != 15); // UNPREDICTABLE + + if (L) { + // MRRC + log.debug("TODO: mrrc p{}, {}, r{}, r{}, c{}", .{ cp_num, cp_opcode, rd, rn, crm }); + } else { + // MCRR + log.debug("TODO: mcrr p{}, {}, r{}, r{}, c{}", .{ cp_num, cp_opcode, rd, rn, crm }); + } } }.inner; } pub fn registerTransfer(comptime InstrFn: type, comptime opcode1: u3, comptime L: bool, comptime opcode2: u3) InstrFn { - _ = opcode2; - _ = L; - _ = opcode1; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; return struct { fn inner(cpu: *Arm32, opcode: u32) void { - _ = cpu; + const crn: u4 = @intCast(opcode >> 16 & 0xF); + const rd = opcode >> 12 & 0xF; + const cp_num = opcode >> 8 & 0xF; + const crm: u4 = @intCast(opcode & 0xF); - log.err("TODO: handle 0x{X:0>8} which is a coprocessor register transfer instr", .{opcode}); + std.debug.assert(cp_num == 0xF); // There's no other coprocessor on NDS9; + + if (L) { + // MRC + const value = cpu.cp15.read(opcode1, crn, crm, opcode2); + + if (rd != 0xF) { + cpu.r[rd] = value; + return; + } + + // TODO: I can probably do this with a mask and the like + cpu.cpsr.n.write(value >> 31 & 1 == 1); + cpu.cpsr.z.write(value >> 30 & 1 == 1); + cpu.cpsr.c.write(value >> 29 & 1 == 1); + cpu.cpsr.v.write(value >> 28 & 1 == 1); + } else { + // MCR + std.debug.assert(rd != 0xF); // UNPREDICTABLE + + cpu.cp15.write(opcode1, crn, crm, opcode2, cpu.r[rd]); + + { + // OK so the idea is that I don't want to pass the coprocessor a reference to the CPU, + // so there's some side effects that we need to deal with. Right now I think I'll just process + // all side affects on every MCR write and hope this isn't too awful + // TODO: there has to be a better way..... + + // ICTM / DTCM Stuff + const dtcm_size_base = cpu.cp15.read(0, 9, 1, 0); // mrc 0, c9, c1, 0 + const itcm_size_base = cpu.cp15.read(0, 9, 1, 1); // mrc 0, c9, c1, 1 + + cpu.dtcm.base_address = dtcm_size_base & 0xFFFF_F000; + cpu.dtcm.virt.size = @as(u32, 0x200) << @truncate(std.math.clamp(dtcm_size_base >> 1 & 0x1F, 3, 23)); + cpu.dtcm.virt.mask = std.math.clamp(cpu.dtcm.virt.size, 0, @as(u32, @intCast(cpu.dtcm.buf.len))) - 1; + + cpu.itcm.virt.size = @as(u32, 0x200) << @truncate(std.math.clamp(itcm_size_base >> 1 & 0x1F, 3, 23)); + cpu.itcm.virt.mask = std.math.clamp(cpu.itcm.virt.size, 0, @as(u32, @intCast(cpu.itcm.buf.len))) - 1; + } + } } }.inner; } diff --git a/src/arm/v5te.zig b/src/arm/v5te.zig index 7f648f3..fc91c1a 100644 --- a/src/arm/v5te.zig +++ b/src/arm/v5te.zig @@ -38,6 +38,8 @@ pub const arm = struct { return comptime comptime_blk: { @setEvalBranchQuota(0xE000); var table = [_]InstrFn{und} ** 0x1000; + // op : 27 26 25 24 23 22 21 20 07 06 05 04 + // idx: 11 10 09 08 07 06 05 04 03 02 01 00 for (&table, 0..) |*handler, i| { handler.* = switch (@as(u2, i >> 10)) {