Compare commits
16 Commits
a3eefa6432
...
main
Author | SHA1 | Date | |
---|---|---|---|
814d081ea0 | |||
0010029783 | |||
6f0e271360 | |||
bdc4bfc642 | |||
580e7baca9 | |||
aad3bdc9ea | |||
dcff3fd588 | |||
8d2a3f1b67 | |||
1bd96304ba | |||
481271ba2a | |||
2c5d474c56 | |||
4d3814db36 | |||
502647806c | |||
3c5d4acc5f | |||
37b3fe3d10 | |||
106820b444 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
zig-out/
|
||||
zig-cache/
|
||||
.zig-cache/
|
||||
|
62
build.zig
62
build.zig
@@ -15,63 +15,33 @@ pub fn build(b: *std.Build) void {
|
||||
// set a preferred release mode, allowing the user to decide how to optimize.
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const util_dep = b.dependency("zba-util", .{});
|
||||
const bitfield_mod = b.createModule(.{ .source_file = .{ .path = "lib/bitfield.zig" }, .dependencies = &.{} });
|
||||
const util_dep = b.dependency("zba-util", .{}); // https://git.musuka.dev/paoda/zba-util
|
||||
const bitfield_mod = b.createModule(.{ .root_source_file = b.path("lib/bitfield.zig") }); // https://github.com/FlorenceOS/Florence
|
||||
|
||||
_ = b.addModule("arm32", .{
|
||||
.source_file = .{ .path = "src/lib.zig" },
|
||||
.dependencies = &.{
|
||||
.{
|
||||
.name = "zba-util",
|
||||
.module = util_dep.module("zba-util"),
|
||||
},
|
||||
.{
|
||||
.name = "bitfield",
|
||||
.module = bitfield_mod,
|
||||
},
|
||||
.root_source_file = b.path("src/lib.zig"),
|
||||
.imports = &.{
|
||||
.{ .name = "zba-util", .module = util_dep.module("zba-util") },
|
||||
.{ .name = "bitfield", .module = bitfield_mod },
|
||||
},
|
||||
});
|
||||
|
||||
// Creates a step for unit testing. This only builds the test executable
|
||||
// but does not run it.
|
||||
const lib_tests = b.addTest(.{
|
||||
.root_source_file = .{ .path = "src/lib.zig" },
|
||||
const lib_unit_tests = b.addTest(.{
|
||||
.root_source_file = b.path("src/lib.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
lib_tests.addModule("zba-util", util_dep.module("zba-util")); // https://git.musuka.dev/paoda/zba-util
|
||||
lib_tests.addModule("bitfield", bitfield_mod);
|
||||
lib_unit_tests.root_module.addImport("zba-util", util_dep.module("zba-util"));
|
||||
lib_unit_tests.root_module.addImport("bitfield", bitfield_mod);
|
||||
|
||||
const run_lib_tests = b.addRunArtifact(lib_tests);
|
||||
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
|
||||
|
||||
// This creates a build step. It will be visible in the `zig build --help` menu,
|
||||
// and can be selected like this: `zig build test`
|
||||
// This will evaluate the `test` step rather than the default, which is "install".
|
||||
const test_step = b.step("test", "Run library tests");
|
||||
test_step.dependOn(&run_lib_tests.step);
|
||||
}
|
||||
|
||||
/// `arm32` will expect the depender to supply the `zba-util` library via the package maanger
|
||||
pub fn module(b: *std.Build) *std.Build.Module {
|
||||
const bitfield = b.createModule(.{ .source_file = .{ .path = path("/lib/bitfield.zig") }, .dependencies = &.{} });
|
||||
|
||||
const zba_util = b.dependency("zba-util", .{}).module("zba-util");
|
||||
|
||||
return b.createModule(.{
|
||||
.source_file = .{ .path = path("/src/lib.zig") },
|
||||
.dependencies = &.{
|
||||
.{ .name = "zba-util", .module = zba_util },
|
||||
.{ .name = "bitfield", .module = bitfield },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// https://github.com/MasterQ32/SDL.zig/blob/4d565b54227b862c1540719e0e21a36d649e87d5/build.zig#L114-L120
|
||||
fn path(comptime suffix: []const u8) []const u8 {
|
||||
if (suffix[0] != '/') @compileError("relToPath requires an absolute path!");
|
||||
return comptime blk: {
|
||||
const root_dir = std.fs.path.dirname(@src().file) orelse ".";
|
||||
break :blk root_dir ++ suffix;
|
||||
};
|
||||
// Similar to creating the run step earlier, this exposes a `test` step to
|
||||
// the `zig build --help` menu, providing a way for the user to request
|
||||
// running the unit tests.
|
||||
const test_step = b.step("test", "Run unit tests");
|
||||
test_step.dependOn(&run_lib_unit_tests.step);
|
||||
}
|
||||
|
@@ -1,10 +1,77 @@
|
||||
.{
|
||||
.name = "arm32",
|
||||
// This is the default name used by packages depending on this one. For
|
||||
// example, when a user runs `zig fetch --save <url>`, this field is used
|
||||
// as the key in the `dependencies` table. Although the user can choose a
|
||||
// different name, most users will stick with this provided value.
|
||||
//
|
||||
// It is redundant to include "zig" in this name because it is already
|
||||
// within the Zig package namespace.
|
||||
.name = "zba-util",
|
||||
|
||||
// This is a [Semantic Version](https://semver.org/).
|
||||
// In a future version of Zig it will be used for package deduplication.
|
||||
.version = "0.1.0",
|
||||
|
||||
// This field is optional.
|
||||
// This is currently advisory only; Zig does not yet do anything
|
||||
// with this value.
|
||||
//.minimum_zig_version = "0.11.0",
|
||||
|
||||
// This field is optional.
|
||||
// Each dependency must either provide a `url` and `hash`, or a `path`.
|
||||
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
|
||||
// Once all dependencies are fetched, `zig build` no longer requires
|
||||
// internet connectivity.
|
||||
.dependencies = .{
|
||||
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
|
||||
//.example = .{
|
||||
// // When updating this field to a new URL, be sure to delete the corresponding
|
||||
// // `hash`, otherwise you are communicating that you expect to find the old hash at
|
||||
// // the new URL.
|
||||
// .url = "https://example.com/foo.tar.gz",
|
||||
//
|
||||
// // This is computed from the file contents of the directory of files that is
|
||||
// // obtained after fetching `url` and applying the inclusion rules given by
|
||||
// // `paths`.
|
||||
// //
|
||||
// // This field is the source of truth; packages do not come from a `url`; they
|
||||
// // come from a `hash`. `url` is just one of many possible mirrors for how to
|
||||
// // obtain a package matching this `hash`.
|
||||
// //
|
||||
// // Uses the [multihash](https://multiformats.io/multihash/) format.
|
||||
// .hash = "...",
|
||||
//
|
||||
// // When this is provided, the package is found in a directory relative to the
|
||||
// // build root. In this case the package's hash is irrelevant and therefore not
|
||||
// // computed. This field and `url` are mutually exclusive.
|
||||
// .path = "foo",
|
||||
|
||||
// // When this is set to `true`, a package is declared to be lazily
|
||||
// // fetched. This makes the dependency only get fetched if it is
|
||||
// // actually used.
|
||||
// .lazy = false,
|
||||
//},
|
||||
.@"zba-util" = .{
|
||||
.url = "https://git.musuka.dev/paoda/zba-util/archive/322c798e384a0d24cc84ffcfa2e4a3ca807798a0.tar.gz",
|
||||
.hash = "12209ce0e729460b997706e47a53a32f1842672cd120189e612f4871731780a30ed0",
|
||||
.url = "https://git.musuka.dev/paoda/zba-util/archive/bf0e744047ce1ec90172dbcc0c72bfcc29a063e3.tar.gz",
|
||||
.hash = "1220d044ecfbeacc3b3cebeff131d587e24167d61435a3cb96dffd4d4521bb06aed0",
|
||||
},
|
||||
},
|
||||
|
||||
// Specifies the set of files and directories that are included in this package.
|
||||
// Only files and directories listed here are included in the `hash` that
|
||||
// is computed for this package. Only files listed here will remain on disk
|
||||
// when using the zig package manager. As a rule of thumb, one should list
|
||||
// files required for compilation plus any license(s).
|
||||
// Paths are relative to the build root. Use the empty string (`""`) to refer to
|
||||
// the build root itself.
|
||||
// A directory listed here means that all files within, recursively, are included.
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"src",
|
||||
"lib/bitfield.zig",
|
||||
// For example...
|
||||
//"LICENSE",
|
||||
//"README.md",
|
||||
},
|
||||
}
|
||||
|
116
src/arm.zig
116
src/arm.zig
@@ -142,7 +142,7 @@ pub fn Arm32(comptime isa: Architecture) type {
|
||||
return (idx * 2) + if (kind == .R14) @as(usize, 1) else 0;
|
||||
}
|
||||
|
||||
inline fn spsrIdx(mode: Mode) usize {
|
||||
pub inline fn spsrIdx(mode: Mode) usize {
|
||||
return switch (mode) {
|
||||
.Supervisor => 0,
|
||||
.Abort => 1,
|
||||
@@ -217,37 +217,39 @@ pub fn Arm32(comptime isa: Architecture) type {
|
||||
}
|
||||
};
|
||||
|
||||
pub fn dbgRead(self: *const Self, comptime T: type, address: u32) T {
|
||||
if (is_v5te) {
|
||||
if (self.itcm.read(T, address)) |val| return val;
|
||||
if (self.dtcm.read(T, address)) |val| return val;
|
||||
}
|
||||
|
||||
return self.bus.dbgRead(T, address);
|
||||
}
|
||||
|
||||
pub fn dbgWrite(self: *Self, comptime T: type, address: u32, value: T) void {
|
||||
if (is_v5te) {
|
||||
if (self.itcm.write(T, address, value)) return;
|
||||
if (self.dtcm.write(T, address, value)) return;
|
||||
}
|
||||
|
||||
return self.bus.dbgWrite(T, address, value);
|
||||
}
|
||||
|
||||
// CPU needs it's own read/write fns due to ICTM and DCTM present in v5te
|
||||
// I considered implementing Bus.cpu_read and Bus.cpu_write but ended up considering that a bit too leaky
|
||||
pub fn read(self: *Self, comptime T: type, address: u32) T {
|
||||
const readInt = std.mem.readIntSliceLittle;
|
||||
|
||||
if (is_v5te) {
|
||||
const dtcm_base = self.dtcm.base_address;
|
||||
const dtcm_size = self.dtcm.virt.size;
|
||||
|
||||
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 & self.dtcm.virt.mask ..][0..@sizeOf(T)]);
|
||||
if (self.itcm.read(T, address)) |val| return val;
|
||||
if (self.dtcm.read(T, address)) |val| return val;
|
||||
}
|
||||
|
||||
return self.bus.read(T, address);
|
||||
}
|
||||
|
||||
pub fn write(self: *Self, comptime T: type, address: u32, value: T) void {
|
||||
const writeInt = std.mem.writeIntSliceLittle;
|
||||
|
||||
if (is_v5te) {
|
||||
const dtcm_base = self.dtcm.base_address;
|
||||
const dtcm_size = self.dtcm.virt.size;
|
||||
|
||||
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 & self.dtcm.virt.mask ..][0..@sizeOf(T)], value);
|
||||
if (self.itcm.write(T, address, value)) return;
|
||||
if (self.dtcm.write(T, address, value)) return;
|
||||
}
|
||||
|
||||
return self.bus.write(T, address, value);
|
||||
@@ -371,7 +373,7 @@ pub fn Arm32(comptime isa: Architecture) type {
|
||||
|
||||
if (self.cpsr.check(Self.arch, cond)) {
|
||||
if (isa == .v5te and cond == 0b1111) {
|
||||
self.panic("TODO: Unconditional Instruction Extension Space\nopcode: 0x{X:0>8} | idx: 0x{X:} | ptr: {any}", .{ opcode, arm.idx(opcode), arm.lut[arm.idx(opcode)] });
|
||||
std.log.debug("TODO: Unconditional Instruction Extension Space\nopcode: 0x{X:0>8} | idx: 0x{X:} | ptr: {any}", .{ opcode, arm.idx(opcode), arm.lut[arm.idx(opcode)] });
|
||||
}
|
||||
|
||||
arm.lut[arm.idx(opcode)](self, opcode);
|
||||
@@ -425,6 +427,29 @@ pub fn Arm32(comptime isa: Architecture) type {
|
||||
std.debug.panic(format, args);
|
||||
}
|
||||
|
||||
// TODO: Rename
|
||||
pub fn undefinedInstructionTrap(self: *Self) void {
|
||||
// Copy Values from Current Mode
|
||||
const ret_addr = self.r[15] - @as(u32, if (self.cpsr.t.read()) 2 else 4);
|
||||
const cpsr = self.cpsr.raw;
|
||||
|
||||
// Switch Mode
|
||||
self.changeMode(.Undefined);
|
||||
self.cpsr.t.write(false); // Force ARM Mode
|
||||
self.cpsr.i.write(true); // Disable normal interrupts
|
||||
|
||||
self.r[14] = ret_addr; // Resume Execution
|
||||
self.spsr.raw = cpsr; // Previous mode CPSR
|
||||
self.r[15] = switch (Self.arch) {
|
||||
.v4t => 0x0000_0004,
|
||||
.v5te => blk: {
|
||||
const ctrl = self.cp15.read(0, 1, 0, 0);
|
||||
break :blk if (ctrl >> 13 & 1 == 1) 0xFFFF_0004 else 0x0000_0004;
|
||||
},
|
||||
};
|
||||
self.pipe.reload(self);
|
||||
}
|
||||
|
||||
pub fn interface(self: *Self) Interpreter {
|
||||
return switch (isa) {
|
||||
.v4t => .{ .v4t = self },
|
||||
@@ -441,6 +466,53 @@ fn Tcm(comptime count: usize, comptime default_addr: u32) type {
|
||||
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 },
|
||||
|
||||
enabled: bool = true,
|
||||
load_mode: bool = false,
|
||||
|
||||
/// Read from TCM
|
||||
///
|
||||
/// Returns `T` on success, which is the value we have read from TCM.
|
||||
/// Returns `null` on failure for one of the following reasons:
|
||||
/// - TCM is disabled
|
||||
/// - TCM is in load mode (TODO: What about SWP and SWPB)
|
||||
/// - TCM Address is not mapped to TCM
|
||||
///
|
||||
/// The caller doesn't particularly care about "why" though.
|
||||
pub fn read(self: *const @This(), comptime T: type, address: u32) ?T {
|
||||
if (!self.enabled) return null;
|
||||
if (self.load_mode) return null;
|
||||
|
||||
const start_addr = self.base_address;
|
||||
const end_addr = self.base_address + self.virt.size;
|
||||
|
||||
if (start_addr <= address and address < end_addr) {
|
||||
return std.mem.readInt(T, self.buf[address & self.virt.mask ..][0..@sizeOf(T)], .little);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Write to TCM
|
||||
///
|
||||
/// Returns `true` on success. Will return `false` for one of the following reasons:
|
||||
/// - TCM is disabled
|
||||
/// - Address is not mapped to TCM
|
||||
///
|
||||
/// The caller doesn't particularly care about "why" though.
|
||||
pub fn write(self: *@This(), comptime T: type, address: u32, value: T) bool {
|
||||
if (!self.enabled) return false;
|
||||
|
||||
const start_addr = self.base_address;
|
||||
const end_addr = self.base_address + self.virt.size;
|
||||
|
||||
if (start_addr <= address and address < end_addr) {
|
||||
std.mem.writeInt(T, self.buf[address & self.virt.mask ..][0..@sizeOf(T)], value, .little);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -12,18 +12,18 @@ pub fn blockDataTransfer(comptime InstrFn: type, comptime P: bool, comptime U: b
|
||||
// U determines whether the LDM/STM transfer is made upwards (U == 1)
|
||||
// or downwards (U == 0).
|
||||
|
||||
const base_addr = cpu.r[rn];
|
||||
|
||||
const start_addr: u32 = if (U) blk: {
|
||||
break :blk cpu.r[rn] + if (P) 4 else 0;
|
||||
break :blk base_addr + if (P) 4 else 0;
|
||||
} else blk: {
|
||||
break :blk cpu.r[rn] - (4 * reg_count) + if (!P) 4 else 0;
|
||||
break :blk base_addr - (4 * reg_count) + if (!P) 4 else 0;
|
||||
};
|
||||
|
||||
// FIXME : why 4 * reg_count?
|
||||
|
||||
const new_base_addr: u32 = if (U) blk: {
|
||||
break :blk cpu.r[rn] + 4 * reg_count;
|
||||
break :blk base_addr + 4 * reg_count;
|
||||
} else blk: {
|
||||
break :blk cpu.r[rn] - 4 * reg_count;
|
||||
break :blk base_addr - 4 * reg_count;
|
||||
};
|
||||
|
||||
var address = start_addr;
|
||||
@@ -36,9 +36,9 @@ pub fn blockDataTransfer(comptime InstrFn: type, comptime P: bool, comptime U: b
|
||||
if (rlist == 0) {
|
||||
if (Arm32.arch == .v4t) {
|
||||
const undefined_addr: u32 = if (U) blk: {
|
||||
break :blk cpu.r[rn] + if (P) 4 else 0;
|
||||
break :blk base_addr + if (P) 4 else 0;
|
||||
} else blk: {
|
||||
break :blk cpu.r[rn] - (0x40 - if (!P) 4 else 0);
|
||||
break :blk base_addr - (0x40 - if (!P) 4 else 0);
|
||||
};
|
||||
|
||||
if (L) {
|
||||
@@ -49,41 +49,34 @@ pub fn blockDataTransfer(comptime InstrFn: type, comptime P: bool, comptime U: b
|
||||
}
|
||||
}
|
||||
|
||||
cpu.r[rn] = if (U) cpu.r[rn] + 0x40 else cpu.r[rn] - 0x40;
|
||||
cpu.r[rn] = if (U) base_addr + 0x40 else base_addr - 0x40;
|
||||
return;
|
||||
}
|
||||
|
||||
// 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_present, i, address);
|
||||
address += 4;
|
||||
|
||||
if (W and !L and write_to_base) {
|
||||
cpu.r[rn] = new_base_addr;
|
||||
write_to_base = false;
|
||||
if (L) {
|
||||
load(cpu, i, rlist, address);
|
||||
} else {
|
||||
store(cpu, rn, i, rlist, address, .{ .old_addr = base_addr, .new_addr = new_base_addr });
|
||||
}
|
||||
|
||||
address += 4;
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
cpu.r[rn] = new_base_addr;
|
||||
|
||||
if (W and L) {
|
||||
if (rlist >> rn & 1 == 0) {
|
||||
// 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 (rlist >> rn & 1 == 0) { // rn is not in rlist
|
||||
cpu.r[rn] = new_base_addr;
|
||||
return;
|
||||
}
|
||||
@@ -101,32 +94,60 @@ pub fn blockDataTransfer(comptime InstrFn: type, comptime P: bool, comptime U: b
|
||||
}
|
||||
}
|
||||
|
||||
fn transfer(cpu: *Arm32, r15_present: bool, i: u5, address: u32) void {
|
||||
if (L) {
|
||||
if (S and !r15_present) {
|
||||
// Always Transfer User mode Registers
|
||||
cpu.setUserModeRegister(i, cpu.read(u32, address));
|
||||
} else {
|
||||
const value = cpu.read(u32, address);
|
||||
fn load(cpu: *Arm32, ri: u4, rlist: u16, address: u32) void {
|
||||
const has_r15 = rlist >> 15 & 1 == 1;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (S and !has_r15) {
|
||||
// Always Transfer User mode Registers
|
||||
cpu.setUserModeRegister(ri, cpu.read(u32, address));
|
||||
} else {
|
||||
if (S) {
|
||||
// Always Transfer User mode Registers
|
||||
// This happens regardless if r15 is in the list
|
||||
const value = cpu.getUserModeRegister(i);
|
||||
cpu.write(u32, address, value + if (i == 0xF) 4 else @as(u32, 0)); // PC is already 8 ahead to make 12
|
||||
} else {
|
||||
cpu.write(u32, address, cpu.r[i] + if (i == 0xF) 4 else @as(u32, 0));
|
||||
const value = cpu.read(u32, address);
|
||||
cpu.r[ri] = value;
|
||||
|
||||
if (ri == 0xF) {
|
||||
const mask: u32 = if (Arm32.arch == .v5te) 1 else 3;
|
||||
cpu.r[ri] &= ~mask;
|
||||
|
||||
if (Arm32.arch == .v5te) cpu.cpsr.t.write(value & 1 == 1);
|
||||
if (S) cpu.setCpsr(cpu.spsr.raw); // FIXME: before or after the reload?
|
||||
|
||||
cpu.pipe.reload(cpu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const BaseAddrs = struct { old_addr: u32, new_addr: u32 };
|
||||
|
||||
fn store(cpu: *Arm32, rn: u4, ri: u4, rlist: u16, address: u32, base: BaseAddrs) void {
|
||||
const value = if (S) blk: {
|
||||
// if S == true:
|
||||
// Always Transfer User mode Registers
|
||||
// This happens regardless if r15 is in the list
|
||||
|
||||
break :blk cpu.getUserModeRegister(ri);
|
||||
} else blk: {
|
||||
if (ri == rn) {
|
||||
// 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
|
||||
|
||||
if (rlist >> rn & 1 == 0)
|
||||
break :blk base.new_addr;
|
||||
|
||||
const mask = @as(u16, 1) << rn;
|
||||
const is_first = @popCount(rlist & (mask - 1)) == 0;
|
||||
|
||||
break :blk switch (Arm32.arch) {
|
||||
.v4t => if (is_first) base.old_addr else base.new_addr,
|
||||
.v5te => base.old_addr,
|
||||
};
|
||||
}
|
||||
|
||||
break :blk cpu.r[ri];
|
||||
};
|
||||
|
||||
cpu.write(u32, address, value + if (ri == 0xF) 4 else @as(u32, 0));
|
||||
}
|
||||
}.inner;
|
||||
}
|
||||
|
@@ -5,9 +5,24 @@ pub fn branch(comptime InstrFn: type, comptime L: bool) InstrFn {
|
||||
|
||||
return struct {
|
||||
fn inner(cpu: *Arm32, opcode: u32) void {
|
||||
if (L) cpu.r[14] = cpu.r[15] - 4;
|
||||
const cond: u4 = @truncate(opcode >> 28);
|
||||
switch (cond) {
|
||||
0b1111 => { // BLX
|
||||
const H = L;
|
||||
const offset = sext(u32, u24, opcode) << 2 | @as(u32, @intFromBool(H)) << 1;
|
||||
|
||||
cpu.r[14] = cpu.r[15] - 4;
|
||||
cpu.cpsr.t.set();
|
||||
|
||||
cpu.r[15] +%= offset;
|
||||
},
|
||||
else => {
|
||||
if (L) cpu.r[14] = cpu.r[15] - 4;
|
||||
|
||||
cpu.r[15] +%= sext(u32, u24, opcode) << 2;
|
||||
},
|
||||
}
|
||||
|
||||
cpu.r[15] +%= sext(u32, u24, opcode) << 2;
|
||||
cpu.pipe.reload(cpu);
|
||||
}
|
||||
}.inner;
|
||||
|
@@ -1,4 +1,5 @@
|
||||
const std = @import("std");
|
||||
const Bit = @import("bitfield").Bit;
|
||||
|
||||
const log = std.log.scoped(.coprocessor_handler);
|
||||
|
||||
@@ -50,14 +51,13 @@ pub fn dataTransfer(
|
||||
// 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 });
|
||||
cpu.panic("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 });
|
||||
cpu.panic("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;
|
||||
@@ -70,10 +70,10 @@ pub fn dataTransfer(
|
||||
|
||||
if (L) {
|
||||
// MRRC
|
||||
log.debug("TODO: mrrc p{}, {}, r{}, r{}, c{}", .{ cp_num, cp_opcode, rd, rn, crm });
|
||||
cpu.panic("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 });
|
||||
cpu.panic("TODO: mcrr p{}, {}, r{}, r{}, c{}", .{ cp_num, cp_opcode, rd, rn, crm });
|
||||
}
|
||||
}
|
||||
}.inner;
|
||||
@@ -89,7 +89,11 @@ pub fn registerTransfer(comptime InstrFn: type, comptime opcode1: u3, comptime L
|
||||
const cp_num = opcode >> 8 & 0xF;
|
||||
const crm: u4 = @intCast(opcode & 0xF);
|
||||
|
||||
std.debug.assert(cp_num == 0xF); // There's no other coprocessor on NDS9;
|
||||
switch (cp_num) {
|
||||
14 => return,
|
||||
15 => if (Arm32.arch == .v4t) return cpu.undefinedInstructionTrap(),
|
||||
else => cpu.panic("MRC: unexpected coprocessor #: {}", .{cp_num}),
|
||||
}
|
||||
|
||||
if (L) {
|
||||
// MRC
|
||||
@@ -118,6 +122,13 @@ pub fn registerTransfer(comptime InstrFn: type, comptime opcode1: u3, comptime L
|
||||
// TODO: there has to be a better way.....
|
||||
|
||||
// ICTM / DTCM Stuff
|
||||
const ctrl: cp15.Control = @bitCast(cpu.cp15.read(0, 1, 0, 0));
|
||||
cpu.dtcm.enabled = ctrl.dtcm_enable.read();
|
||||
cpu.dtcm.load_mode = ctrl.dtcm_load_mode.read();
|
||||
|
||||
cpu.itcm.enabled = ctrl.itcm_enable.read();
|
||||
cpu.itcm.load_mode = ctrl.itcm_load_mode.read();
|
||||
|
||||
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
|
||||
|
||||
@@ -140,9 +151,26 @@ pub fn dataProcessing(comptime InstrFn: type, comptime opcode1: u4, comptime opc
|
||||
|
||||
return struct {
|
||||
fn inner(cpu: *Arm32, opcode: u32) void {
|
||||
_ = cpu;
|
||||
|
||||
log.err("TODO: handle 0x{X:0>8} which is a coprocessor data processing instr", .{opcode});
|
||||
cpu.panic("TODO: handle 0x{X:0>8} which is a coprocessor data processing instr", .{opcode});
|
||||
}
|
||||
}.inner;
|
||||
}
|
||||
|
||||
const cp15 = struct {
|
||||
// Only the bits that are R/W on the NDS (for now)
|
||||
const Control = extern union {
|
||||
pu_enable: Bit(u32, 0),
|
||||
unified_cache: Bit(u32, 2),
|
||||
endian: Bit(u32, 7),
|
||||
instruction_cache: Bit(u32, 12),
|
||||
exception_vectors: Bit(u32, 13),
|
||||
cache_replacement: Bit(u32, 14),
|
||||
pre_armv5_mode: Bit(u32, 15),
|
||||
dtcm_enable: Bit(u32, 16),
|
||||
dtcm_load_mode: Bit(u32, 17),
|
||||
itcm_enable: Bit(u32, 18),
|
||||
itcm_load_mode: Bit(u32, 19),
|
||||
|
||||
raw: u32,
|
||||
};
|
||||
};
|
||||
|
@@ -1,6 +1,9 @@
|
||||
const std = @import("std");
|
||||
const sext = @import("zba-util").sext;
|
||||
const rotr = @import("zba-util").rotr;
|
||||
|
||||
const log = std.log.scoped(.half_and_signed_data_transfer);
|
||||
|
||||
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(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child;
|
||||
|
||||
@@ -24,8 +27,10 @@ pub fn halfAndSignedDataTransfer(comptime InstrFn: type, comptime P: bool, compt
|
||||
switch (op) {
|
||||
0b01 => {
|
||||
// LDRH
|
||||
const value = cpu.read(u16, address);
|
||||
result = rotr(u32, value, 8 * (address & 1));
|
||||
result = switch (Arm32.arch) {
|
||||
.v4t => rotr(u32, cpu.read(u16, address), 8 * (address & 1)),
|
||||
.v5te => cpu.read(u16, address),
|
||||
};
|
||||
},
|
||||
0b10 => {
|
||||
// LDRSB
|
||||
@@ -33,10 +38,17 @@ pub fn halfAndSignedDataTransfer(comptime InstrFn: type, comptime P: bool, compt
|
||||
},
|
||||
0b11 => {
|
||||
// LDRSH
|
||||
const value = cpu.read(u16, address);
|
||||
result = switch (Arm32.arch) {
|
||||
.v4t => blk: {
|
||||
const value = cpu.read(u16, address);
|
||||
|
||||
// FIXME: I shouldn't have to use @as(u8, ...) here
|
||||
result = if (address & 1 == 1) sext(u32, u8, @as(u8, @truncate(value >> 8))) else sext(u32, u16, value);
|
||||
break :blk switch (address & 1 == 1) {
|
||||
true => sext(u32, u8, @as(u8, @truncate(value >> 8))),
|
||||
false => sext(u32, u16, value),
|
||||
};
|
||||
},
|
||||
.v5te => sext(u32, u16, cpu.read(u16, address)),
|
||||
};
|
||||
},
|
||||
0b00 => unreachable,
|
||||
}
|
||||
@@ -66,9 +78,9 @@ pub fn halfAndSignedDataTransfer(comptime InstrFn: type, comptime P: bool, compt
|
||||
// FIXME: I shouldn't have to use @as(u16, ...) here
|
||||
cpu.write(u16, address, @as(u16, @truncate(cpu.r[rd])));
|
||||
},
|
||||
0b10 => {
|
||||
0b10 => blk: {
|
||||
// LDRD
|
||||
if (Arm32.arch != .v5te) cpu.panic("LDRD: unsupported on arm{s}", .{@tagName(Arm32.arch)});
|
||||
if (Arm32.arch == .v4t) break :blk;
|
||||
if (rd & 0 != 0) cpu.panic("LDRD: UNDEFINED behaviour when Rd is not even", .{});
|
||||
if (rd == 0xE) cpu.panic("LDRD: UNPREDICTABLE behaviour when rd == 14", .{});
|
||||
if (address & 0x7 != 0b000) cpu.panic("LDRD: UNPREDICTABLE when address (0x{X:0>8} is not double (64-bit) aligned", .{address});
|
||||
|
@@ -30,6 +30,7 @@ pub fn control(comptime InstrFn: type, comptime I: bool, comptime op: u6) InstrF
|
||||
},
|
||||
0b01_0001 => cpu.panic("TODO: implement v5TE BX", .{}),
|
||||
0b11_0001 => { // CLZ
|
||||
if (Arm32.arch == .v4t) return cpu.undefinedInstructionTrap();
|
||||
const rd = opcode >> 12 & 0xF;
|
||||
const rm = opcode & 0xF;
|
||||
|
||||
@@ -49,37 +50,43 @@ pub fn control(comptime InstrFn: type, comptime I: bool, comptime op: u6) InstrF
|
||||
|
||||
cpu.pipe.reload(cpu);
|
||||
},
|
||||
0b00_0101, 0b01_0101 => { // QADD / QSUB
|
||||
0b00_0101, 0b01_0101, 0b10_0101, 0b11_0101 => { // QADD / QDADD / QSUB / QDSUB
|
||||
if (Arm32.arch == .v4t) return cpu.undefinedInstructionTrap();
|
||||
const U = op >> 4 & 1 == 1;
|
||||
const D = op >> 5 & 1 == 1;
|
||||
|
||||
const rm = opcode & 0xF;
|
||||
const rd = opcode >> 12 & 0xF;
|
||||
const rn = opcode >> 16 & 0xF;
|
||||
|
||||
const left: i32 = @bitCast(cpu.r[rm]);
|
||||
const right: i32 = @bitCast(cpu.r[rn]);
|
||||
const right: i32 = blk: {
|
||||
if (!D) break :blk @bitCast(cpu.r[rn]);
|
||||
|
||||
cpu.r[rd] = @bitCast(if (U) left -| right else left +| right);
|
||||
const ret = @mulWithOverflow(@as(i32, @bitCast(cpu.r[rn])), 2);
|
||||
var product: i32 = ret[0];
|
||||
|
||||
if (cpu.r[rd] == 0x8000_0000 or cpu.r[rd] == 0x7FFF_FFFF) cpu.cpsr.q.set();
|
||||
},
|
||||
0b10_0101, 0b11_0101 => { // QDADD / QDSUB
|
||||
const U = op >> 4 & 1 == 1;
|
||||
if (ret[1] == 0b1) {
|
||||
product = if (product < 0) std.math.maxInt(i32) else std.math.minInt(i32);
|
||||
cpu.cpsr.q.set();
|
||||
}
|
||||
|
||||
const rm = opcode & 0xF;
|
||||
const rd = opcode >> 8 & 0xF;
|
||||
const rn = opcode >> 16 & 0xF;
|
||||
break :blk product;
|
||||
};
|
||||
|
||||
const product = @as(i32, @bitCast(cpu.r[rn])) *| 2;
|
||||
if (product == 0x7FFF_FFFF) cpu.cpsr.q.set();
|
||||
const ret = if (U) @subWithOverflow(left, right) else @addWithOverflow(left, right);
|
||||
var result: i32 = ret[0];
|
||||
|
||||
const left: i32 = @bitCast(cpu.r[rm]);
|
||||
if (ret[1] == 0b1) {
|
||||
result = if (result < 0) std.math.maxInt(i32) else std.math.minInt(i32);
|
||||
cpu.cpsr.q.set();
|
||||
}
|
||||
|
||||
cpu.r[rd] = @bitCast(if (U) left -| product else left +| product);
|
||||
if (cpu.r[rd] == 0x8000_0000 or cpu.r[rd] == 0x7FFF_FFFF) cpu.cpsr.q.set();
|
||||
cpu.r[rd] = @bitCast(result);
|
||||
},
|
||||
0b01_0111 => cpu.panic("TODO: handle BKPT", .{}),
|
||||
0b00_1000, 0b00_1010, 0b00_1100, 0b00_1110 => { // SMLA<x><y>
|
||||
if (Arm32.arch == .v4t) return; // no-op
|
||||
const X = op >> 1 & 1;
|
||||
const Y = op >> 2 & 1;
|
||||
|
||||
@@ -104,26 +111,21 @@ pub fn control(comptime InstrFn: type, comptime I: bool, comptime op: u6) InstrF
|
||||
|
||||
const rm = opcode & 0xF;
|
||||
const rs = opcode >> 8 & 0xF;
|
||||
const rd_lo = opcode >> 12 & 0xF;
|
||||
const rd_hi = opcode >> 16 & 0xF;
|
||||
const rdlo = opcode >> 12 & 0xF;
|
||||
const rdhi = opcode >> 16 & 0xF;
|
||||
|
||||
const left: i32 = @as(i16, @bitCast(@as(u16, @truncate(cpu.r[rm] >> 16 * X))));
|
||||
const right: i32 = @as(i16, @bitCast(@as(u16, @truncate(cpu.r[rs] >> 16 * Y))));
|
||||
|
||||
// TODO: de-clutter this lmao
|
||||
const rdlo_val: i32 = @bitCast(cpu.r[rd_lo]);
|
||||
const left: i64 = @as(i16, @bitCast(@as(u16, @truncate(cpu.r[rm] >> 16 * X))));
|
||||
const right: i64 = @as(i16, @bitCast(@as(u16, @truncate(cpu.r[rs] >> 16 * Y))));
|
||||
const product = left * right;
|
||||
|
||||
// WHY DOESN'T THIS WORK????????
|
||||
const rdhi_val: i32 = @bitCast(cpu.r[rdhi]);
|
||||
const rdlo_val: i32 = @bitCast(cpu.r[rdlo]);
|
||||
|
||||
cpu.r[rd_lo] = @bitCast(rdlo_val + product);
|
||||
cpu.r[rd_hi] = blk: {
|
||||
// FIXME: I think this has to do with correcting sign values?
|
||||
const offset_thing: i32 = @bitCast(if (product < 0) 0xFFFF_FFFF else @as(u32, 0));
|
||||
const accumulate = @as(i64, rdhi_val) << 32 | rdlo_val;
|
||||
const sum = product +% accumulate;
|
||||
|
||||
const rdhi_val: i32 = @bitCast(cpu.r[rd_hi]);
|
||||
break :blk @bitCast(rdhi_val + offset_thing + @addWithOverflow(rdlo_val, product)[1]);
|
||||
};
|
||||
cpu.r[rdhi] = @bitCast(@as(i32, @truncate(sum >> 32)));
|
||||
cpu.r[rdlo] = @bitCast(@as(i32, @truncate(sum)));
|
||||
},
|
||||
0b01_1000, 0b01_1100 => { // SMLAW<y>
|
||||
const Y = op >> 2 & 1;
|
||||
@@ -174,16 +176,6 @@ pub fn control(comptime InstrFn: type, comptime I: bool, comptime op: u6) InstrF
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make generic on any integer?
|
||||
inline fn carryFrom(left: i32, right: i32) u1 {
|
||||
const _left: u32 = @bitCast(left);
|
||||
const _right: u32 = @bitCast(right);
|
||||
|
||||
const sum = @as(u64, _left) + @as(u64, _right);
|
||||
|
||||
return @intCast(sum >> 32 & 1);
|
||||
}
|
||||
|
||||
inline fn msr(comptime R: bool, comptime imm: bool, cpu: *Arm32, opcode: u32) void {
|
||||
const field_mask: u4 = @truncate(opcode >> 16 & 0xF);
|
||||
const rm_idx = opcode & 0xF;
|
||||
|
@@ -49,7 +49,15 @@ pub fn singleDataTransfer(comptime InstrFn: type, comptime I: bool, comptime P:
|
||||
if (L) {
|
||||
// This emulates the LDR rd == rn behaviour
|
||||
cpu.r[rd] = result;
|
||||
if (rd == 0xF) cpu.pipe.reload(cpu);
|
||||
|
||||
if (rd == 0xF) {
|
||||
if (Arm32.arch == .v5te) {
|
||||
cpu.r[rd] &= ~@as(u32, 1);
|
||||
cpu.cpsr.t.write(result & 1 == 1);
|
||||
}
|
||||
|
||||
cpu.pipe.reload(cpu);
|
||||
}
|
||||
}
|
||||
}
|
||||
}.inner;
|
||||
|
@@ -14,7 +14,13 @@ pub fn armSoftwareInterrupt(comptime InstrFn: type) InstrFn {
|
||||
|
||||
cpu.r[14] = ret_addr; // Resume Execution
|
||||
cpu.spsr.raw = cpsr; // Previous mode CPSR
|
||||
cpu.r[15] = 0x0000_0008;
|
||||
cpu.r[15] = switch (Arm32.arch) {
|
||||
.v4t => 0x0000_0008,
|
||||
.v5te => blk: {
|
||||
const ctrl = cpu.cp15.read(0, 1, 0, 0);
|
||||
break :blk if (ctrl >> 13 & 1 == 1) 0xFFFF_0008 else 0x0000_0008;
|
||||
},
|
||||
};
|
||||
cpu.pipe.reload(cpu);
|
||||
}
|
||||
}.inner;
|
||||
|
@@ -32,6 +32,8 @@ pub fn fmt14(comptime InstrFn: type, comptime L: bool, comptime R: bool) InstrFn
|
||||
if (L) {
|
||||
const value = cpu.read(u32, address);
|
||||
cpu.r[15] = value & ~@as(u32, 1);
|
||||
|
||||
if (Arm32.arch == .v5te) cpu.cpsr.t.write(value & 1 == 1);
|
||||
cpu.pipe.reload(cpu);
|
||||
} else {
|
||||
cpu.write(u32, address, cpu.r[14]);
|
||||
|
@@ -17,38 +17,37 @@ pub fn fmt16(comptime InstrFn: type, comptime cond: u4) InstrFn {
|
||||
}.inner;
|
||||
}
|
||||
|
||||
pub fn fmt18(comptime InstrFn: type) InstrFn {
|
||||
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child;
|
||||
|
||||
return struct {
|
||||
// B but conditional
|
||||
fn inner(cpu: *Arm32, 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 {
|
||||
pub fn linkExchange(comptime InstrFn: type, comptime H: u2) InstrFn {
|
||||
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child;
|
||||
|
||||
return struct {
|
||||
fn inner(cpu: *Arm32, opcode: u16) void {
|
||||
// BL
|
||||
const offset = opcode & 0x7FF;
|
||||
|
||||
if (is_low) {
|
||||
// Instruction 2
|
||||
const next_opcode = cpu.r[15] - 2;
|
||||
switch (H) {
|
||||
0b00 => { // Unconditional Branch
|
||||
cpu.r[15] +%= sext(u32, u11, opcode & 0x7FF) << 1;
|
||||
cpu.pipe.reload(cpu);
|
||||
},
|
||||
0b01 => { // BLX Pt. 2
|
||||
if (Arm32.arch == .v4t) cpu.panic("attempted to execute THUMB BLX(1), despite ARMv4T CPU", .{});
|
||||
const next_addr = cpu.r[15] - 2;
|
||||
|
||||
cpu.r[15] = cpu.r[14] +% (offset << 1);
|
||||
cpu.r[14] = next_opcode | 1;
|
||||
cpu.r[15] = (cpu.r[14] +% (offset << 1)) & ~@as(u32, 0x3);
|
||||
cpu.r[14] = next_addr | 1;
|
||||
cpu.cpsr.t.unset();
|
||||
|
||||
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);
|
||||
cpu.pipe.reload(cpu);
|
||||
},
|
||||
0b10 => cpu.r[14] = cpu.r[15] +% (sext(u32, u11, offset) << 12), // BL / BLX Pt. 1
|
||||
0b11 => { // BL Pt. 2
|
||||
const next_addr = cpu.r[15] - 2;
|
||||
|
||||
cpu.r[15] = cpu.r[14] +% (offset << 1);
|
||||
cpu.r[14] = next_addr | 1;
|
||||
|
||||
cpu.pipe.reload(cpu);
|
||||
},
|
||||
}
|
||||
}
|
||||
}.inner;
|
||||
|
@@ -60,6 +60,17 @@ pub fn fmt5(comptime InstrFn: type, comptime op: u2, comptime h1: u1, comptime h
|
||||
const rs = @as(u4, h2) << 3 | (opcode >> 3 & 0x7);
|
||||
const rd = @as(u4, h1) << 3 | (opcode & 0x7);
|
||||
|
||||
if (Arm32.arch == .v5te and op == 0b11 and h1 == 0b1) {
|
||||
// BLX
|
||||
const rm = rs;
|
||||
|
||||
cpu.r[14] = (cpu.r[15] - 2) | 1;
|
||||
cpu.cpsr.t.write(cpu.r[rm] & 1 == 1);
|
||||
|
||||
cpu.r[15] = cpu.r[rm] & ~@as(u32, 1);
|
||||
cpu.pipe.reload(cpu);
|
||||
}
|
||||
|
||||
const op1 = cpu.r[rd];
|
||||
const op2 = cpu.r[rs];
|
||||
|
||||
@@ -205,3 +216,13 @@ pub fn fmt13(comptime InstrFn: type, comptime S: bool) InstrFn {
|
||||
}
|
||||
}.inner;
|
||||
}
|
||||
|
||||
pub fn bkpt(comptime InstrFn: type) InstrFn {
|
||||
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child;
|
||||
|
||||
return struct {
|
||||
fn inner(cpu: *Arm32, _: u16) void {
|
||||
cpu.panic("TODO: handle THUMB BKPT", .{});
|
||||
}
|
||||
}.inner;
|
||||
}
|
||||
|
@@ -41,15 +41,24 @@ pub fn fmt78(comptime InstrFn: type, comptime op: u2, comptime T: bool) InstrFn
|
||||
},
|
||||
0b10 => {
|
||||
// LDRH
|
||||
const value = cpu.read(u16, address);
|
||||
cpu.r[rd] = rotr(u32, value, 8 * (address & 1));
|
||||
cpu.r[rd] = switch (Arm32.arch) {
|
||||
.v4t => rotr(u32, cpu.read(u16, address), 8 * (address & 1)),
|
||||
.v5te => cpu.read(u16, address),
|
||||
};
|
||||
},
|
||||
0b11 => {
|
||||
// LDRSH
|
||||
const value = cpu.read(u16, address);
|
||||
cpu.r[rd] = switch (Arm32.arch) {
|
||||
.v4t => blk: {
|
||||
const value = cpu.read(u16, address);
|
||||
|
||||
// FIXME: I shouldn't have to use @as(u8, ...) here
|
||||
cpu.r[rd] = if (address & 1 == 1) sext(u32, u8, @as(u8, @truncate(value >> 8))) else sext(u32, u16, value);
|
||||
break :blk switch (address & 1 == 1) {
|
||||
true => sext(u32, u8, @as(u8, @truncate(value >> 8))),
|
||||
false => sext(u32, u16, value),
|
||||
};
|
||||
},
|
||||
.v5te => sext(u32, u16, cpu.read(u16, address)),
|
||||
};
|
||||
},
|
||||
}
|
||||
} else {
|
||||
@@ -128,8 +137,10 @@ pub fn fmt10(comptime InstrFn: type, comptime L: bool, comptime offset: u5) Inst
|
||||
|
||||
if (L) {
|
||||
// LDRH
|
||||
const value = cpu.read(u16, address);
|
||||
cpu.r[rd] = rotr(u32, value, 8 * (address & 1));
|
||||
cpu.r[rd] = switch (Arm32.arch) {
|
||||
.v4t => rotr(u32, cpu.read(u16, address), 8 * (address & 1)),
|
||||
.v5te => cpu.read(u16, address),
|
||||
};
|
||||
} else {
|
||||
// STRH
|
||||
|
||||
|
@@ -14,7 +14,13 @@ pub fn fmt17(comptime InstrFn: type) InstrFn {
|
||||
|
||||
cpu.r[14] = ret_addr; // Resume Execution
|
||||
cpu.spsr.raw = cpsr; // Previous mode CPSR
|
||||
cpu.r[15] = 0x0000_0008;
|
||||
cpu.r[15] = switch (Arm32.arch) {
|
||||
.v4t => 0x0000_0008,
|
||||
.v5te => blk: {
|
||||
const ctrl = cpu.cp15.read(0, 1, 0, 0);
|
||||
break :blk if (ctrl >> 13 & 1 == 1) 0xFFFF_0008 else 0x0000_0008;
|
||||
},
|
||||
};
|
||||
cpu.pipe.reload(cpu);
|
||||
}
|
||||
}.inner;
|
||||
|
@@ -20,6 +20,8 @@ pub const arm = struct {
|
||||
/// Arithmetic Instruction Extension Space
|
||||
const multiplyExt = @import("cpu/arm/multiply.zig").multiply;
|
||||
|
||||
const cop = @import("cpu/arm/coprocessor.zig");
|
||||
|
||||
/// Determine index into ARM InstrFn LUT
|
||||
pub fn idx(opcode: u32) u12 {
|
||||
// FIXME: omit these?
|
||||
@@ -87,8 +89,29 @@ pub const arm = struct {
|
||||
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
|
||||
0b10 => blk: {
|
||||
const P = i >> 8 & 1 == 1;
|
||||
const U = i >> 7 & 1 == 1;
|
||||
const N = i >> 6 & 1 == 1;
|
||||
const W = i >> 5 & 1 == 1;
|
||||
const L = i >> 4 & 1 == 1;
|
||||
|
||||
break :blk cop.dataTransfer(InstrFn, P, U, N, W, L);
|
||||
},
|
||||
0b11 => blk: {
|
||||
if (i >> 8 & 1 == 1) break :blk swi(InstrFn);
|
||||
|
||||
const data_opcode1 = i >> 4 & 0xF; // bits 20 -> 23
|
||||
const reg_opcode1 = i >> 5 & 0x7; // bits 21 -> 23
|
||||
const opcode2 = i >> 1 & 0x7; // bits 5 -> 7
|
||||
const L = i >> 4 & 1 == 1; // bit 20
|
||||
|
||||
// Bit 4 (index pos of 0) distinguishes between these classes of instructions
|
||||
break :blk switch (i & 1 == 1) {
|
||||
true => cop.registerTransfer(InstrFn, reg_opcode1, L, opcode2),
|
||||
false => cop.dataProcessing(InstrFn, data_opcode1, opcode2),
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -205,12 +228,9 @@ pub const thumb = struct {
|
||||
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);
|
||||
0b110, 0b111 => blk: {
|
||||
const H = i >> 5 & 0x3;
|
||||
break :blk branch.linkExchange(InstrFn, H);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@@ -149,6 +149,8 @@ pub const thumb = struct {
|
||||
return comptime comptime_blk: {
|
||||
@setEvalBranchQuota(5025); // This is exact
|
||||
var table = [_]InstrFn{und} ** 0x400;
|
||||
// 9 8 7 6 5 4 3 2 1 0
|
||||
// 15 14 13 12 11 10 9 8 7 6
|
||||
|
||||
for (&table, 0..) |*handler, i| {
|
||||
handler.* = switch (@as(u3, i >> 7 & 0x7)) {
|
||||
@@ -210,13 +212,18 @@ pub const thumb = struct {
|
||||
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);
|
||||
0b011 => switch (@as(u2, @truncate(i >> 3 & 0x3))) {
|
||||
0b10 => blk: {
|
||||
// PUSH / POP
|
||||
const L = i >> 5 & 1 == 1;
|
||||
const R = i >> 2 & 1 == 1;
|
||||
break :blk block_transfer.fmt14(InstrFn, L, R);
|
||||
},
|
||||
0b11 => processing.bkpt(InstrFn),
|
||||
else => blk: {
|
||||
const S = i >> 1 & 1 == 1;
|
||||
break :blk processing.fmt13(InstrFn, S);
|
||||
},
|
||||
},
|
||||
0b100 => blk: {
|
||||
const L = i >> 5 & 1 == 1;
|
||||
@@ -230,12 +237,9 @@ pub const thumb = struct {
|
||||
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);
|
||||
0b110, 0b111 => blk: {
|
||||
const H = i >> 5 & 0x3;
|
||||
break :blk branch.linkExchange(InstrFn, H);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
Reference in New Issue
Block a user