Compare commits

...

17 Commits

Author SHA1 Message Date
5c5e8fb81b chore: replace set() and unset() with write() 2025-09-22 19:41:06 -05:00
bdab441430 chore: upgrade to zig v0.15.1 2025-09-22 19:41:06 -05:00
814d081ea0 chore: update to Zig v0.13.0 2024-07-03 20:13:01 -05:00
0010029783 fix: LDR(S)H behaviour differs between ARMv4/ARMv5TE 2024-03-11 10:52:28 -05:00
6f0e271360 fix: update to 0.12.0-dev.2063+804cee3b9 2024-02-08 23:09:04 -06:00
bdc4bfc642 dbg: add dbgRead and dbgWrite fns to cpu struct 2024-01-13 16:10:05 -06:00
580e7baca9 fix: make Bank.spsrIndex public 2023-12-27 18:52:28 -06:00
aad3bdc9ea feat: pass nds arm7wrestler
- impl behaviour of running v5te instrs on v4t cpu
- impl undefined instruction exception handler
- panic on what I think are still unimplemented v5te opcodes
2023-12-27 00:13:06 -06:00
dcff3fd588 fix(v5te): account for high vectors in swi 2023-10-09 19:28:04 -05:00
8d2a3f1b67 feat(v5te): let cp15 affect more tcm behaviours
cp15 can now enable/disable ITCM/DTCM. cp15 can also now enable/disable load mode.
TODO: load mode doesn't effect SWP/SWPB for some reason?
2023-09-29 23:34:46 -05:00
1bd96304ba fix: off-by-one when handling TCM addresses 2023-09-29 02:35:21 -05:00
481271ba2a fix(v4t/v5te): resolve critical error in ldm/stm obscure behaviour 2023-09-26 20:46:30 -05:00
2c5d474c56 fix(v5te): fix off by one in DTCM handler 2023-09-20 00:29:49 -05:00
4d3814db36 fix(v5te): set T on select load instrs when rd == 15 2023-09-20 00:29:47 -05:00
502647806c feat(v5te): stub THUMB BKPT 2023-09-20 00:29:44 -05:00
3c5d4acc5f feat(v5te): implement THUMB BLX(1), BLX(2), and ARM BLX 2023-09-20 00:29:42 -05:00
37b3fe3d10 fix(v5te): properly implement SMLAL<x><y> 2023-09-20 00:29:38 -05:00
24 changed files with 636 additions and 496 deletions

2
.gitignore vendored
View File

@@ -1,2 +1,2 @@
zig-out/ zig-out/
zig-cache/ .zig-cache/

122
build.zig
View File

@@ -1,77 +1,75 @@
const std = @import("std"); const std = @import("std");
// Although this function looks imperative, note that its job is to // Although this function looks imperative, it does not perform the build
// declaratively construct a build graph that will be executed by an external // directly and instead it mutates the build graph (`b`) that will be then
// runner. // executed by an external runner. The functions in `std.Build` implement a DSL
// for defining build steps and express dependencies between them, allowing the
// build runner to parallelize the build automatically (and the cache system to
// know when a step doesn't need to be re-run).
pub fn build(b: *std.Build) void { pub fn build(b: *std.Build) void {
// Standard target options allows the person running `zig build` to choose // Standard target options allow the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which // what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options // means any target is allowed, and the default is native. Other options
// for restricting supported target set are available. // for restricting supported target set are available.
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
// Standard optimization options allow the person running `zig build` to select // It's also possible to define more custom flags to toggle optional features
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not // of this build script using `b.option()`. All defined flags (including
// set a preferred release mode, allowing the user to decide how to optimize. // target and optimize options) will be listed when running `zig build --help`
const optimize = b.standardOptimizeOption(.{}); // in this directory.
const util_dep = b.dependency("zba-util", .{}); const zba_util = b.dependency("zba_util", .{});
const bitfield_mod = b.createModule(.{ .source_file = .{ .path = "lib/bitfield.zig" }, .dependencies = &.{} }); const bitjuggle = b.dependency("bitjuggle", .{});
_ = b.addModule("arm32", .{ // This creates a module, which represents a collection of source files alongside
.source_file = .{ .path = "src/lib.zig" }, // some compilation options, such as optimization mode and linked system libraries.
.dependencies = &.{ // Zig modules are the preferred way of making Zig code available to consumers.
.{ // addModule defines a module that we intend to make available for importing
.name = "zba-util", // to our consumers. We must give it a name because a Zig package can expose
.module = util_dep.module("zba-util"), // multiple modules and consumers will need to be able to specify which
}, // module they want to access.
.{ const mod = b.addModule("arm32", .{
.name = "bitfield", // The root source file is the "entry point" of this module. Users of
.module = bitfield_mod, // this module will only be able to access public declarations contained
}, // in this file, which means that if you have declarations that you
}, // intend to expose to consumers that were defined in other files part
}); // of this module, you will have to make sure to re-export them from
// the root file.
// Creates a step for unit testing. This only builds the test executable .root_source_file = b.path("src/lib.zig"),
// but does not run it. // Later on we'll use this module as the root module of a test executable
const lib_tests = b.addTest(.{ // which requires us to specify a target.
.root_source_file = .{ .path = "src/lib.zig" },
.target = target, .target = target,
.optimize = optimize, .imports = &.{
}); .{ .name = "zba_util", .module = zba_util.module("zba_util") },
.{ .name = "bitjuggle", .module = bitjuggle.module("bitjuggle") },
lib_tests.addModule("zba-util", util_dep.module("zba-util")); // https://git.musuka.dev/paoda/zba-util
lib_tests.addModule("bitfield", bitfield_mod);
const run_lib_tests = b.addRunArtifact(lib_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 // Creates an executable that will run `test` blocks from the provided module.
fn path(comptime suffix: []const u8) []const u8 { // Here `mod` needs to define a target, which is why earlier we made sure to
if (suffix[0] != '/') @compileError("relToPath requires an absolute path!"); // set the releative field.
return comptime blk: { const mod_tests = b.addTest(.{
const root_dir = std.fs.path.dirname(@src().file) orelse "."; .root_module = mod,
break :blk root_dir ++ suffix; });
};
// A run step that will run the test executable.
const run_mod_tests = b.addRunArtifact(mod_tests);
// A top level step for running all tests. dependOn can be called multiple
// times and since the two run steps do not depend on one another, this will
// make the two of them run in parallel.
const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_mod_tests.step);
// Just like flags, top level steps are also listed in the `--help` menu.
//
// The Zig build system is entirely implemented in userland, which means
// that it cannot hook into private compiler APIs. All compilation work
// orchestrated by the build system will result in other Zig compiler
// subcommands being invoked with the right flags defined. You can observe
// these invocations when one fails (or you pass a flag to increase
// verbosity) to validate assumptions and diagnose problems.
//
// Lastly, the Zig build system is relatively simple and self-contained,
// and reading its source code will allow you to master it.
} }

View File

@@ -1,10 +1,86 @@
.{ .{
.name = "arm32", // This is the default name used by packages depending on this one. For
.version = "0.1.0", // 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 = .arm32,
// This is a [Semantic Version](https://semver.org/).
// In a future version of Zig it will be used for package deduplication.
.version = "0.0.0",
// Together with name, this represents a globally unique package
// identifier. This field is generated by the Zig toolchain when the
// package is first created, and then *never changes*. This allows
// unambiguous detection of one package being an updated version of
// another.
//
// When forking a Zig project, this id should be regenerated (delete the
// field and run `zig build`) if the upstream project is still maintained.
// Otherwise, the fork is *hostile*, attempting to take control over the
// original project's identity. Thus it is recommended to leave the comment
// on the following line intact, so that it shows up in code reviews that
// modify the field.
.fingerprint = 0xdff5ad34d5d6f2fb, // Changing this has security and trust implications.
// Tracks the earliest Zig version that the package considers to be a
// supported use case.
.minimum_zig_version = "0.15.1",
// 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 = .{ .dependencies = .{
.@"zba-util" = .{ // See `zig fetch --save <url>` for a command-line interface for adding dependencies.
.url = "https://git.musuka.dev/paoda/zba-util/archive/322c798e384a0d24cc84ffcfa2e4a3ca807798a0.tar.gz", //.example = .{
.hash = "12209ce0e729460b997706e47a53a32f1842672cd120189e612f4871731780a30ed0", // // 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. If the contents of a URL change this will result in a hash mismatch
// // which will prevent zig from using it.
// .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 = .{ .path = "../zba-util" },
.bitjuggle = .{
.url = "git+https://github.com/leecannon/zig-bitjuggle#80111f4f8c672aaea94a8a189ae2a7c8bbaf883f",
.hash = "bitjuggle-2.0.0-SJdU76dvAAARompHEhqKDiwZ4FE4FZ8eHvPvmz5JUOS0",
}, },
}, },
// 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",
// For example...
//"LICENSE",
//"README.md",
},
} }

View File

@@ -1,146 +0,0 @@
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 @as(ValueType, @bitCast(@as(u1, @truncate(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 (@as(bool, @bitCast(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 @as(PtrCastPreserveCV(@This(), @TypeOf(self), FieldType), @ptrCast(self));
}
pub fn write(self: anytype, val: ValueType) void {
self.field().* &= ~self_mask;
self.field().* |= @as(FieldType, @intCast(val)) << shamt;
}
pub fn read(self: anytype) ValueType {
const val: FieldType = self.field().*;
return @as(ValueType, @intCast((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);
}

View File

@@ -6,8 +6,8 @@ const Bus = @import("lib.zig").Bus;
const Scheduler = @import("lib.zig").Scheduler; const Scheduler = @import("lib.zig").Scheduler;
const Coprocessor = @import("lib.zig").Coprocessor; const Coprocessor = @import("lib.zig").Coprocessor;
const Bitfield = @import("bitfield").Bitfield; const Bitfield = @import("bitjuggle").Bitfield;
const Bit = @import("bitfield").Bit; const Bit = @import("bitjuggle").Boolean;
fn condition_lut(comptime isa: Architecture) [16]u16 { fn condition_lut(comptime isa: Architecture) [16]u16 {
return [_]u16{ return [_]u16{
@@ -142,7 +142,7 @@ pub fn Arm32(comptime isa: Architecture) type {
return (idx * 2) + if (kind == .R14) @as(usize, 1) else 0; 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) { return switch (mode) {
.Supervisor => 0, .Supervisor => 0,
.Abort => 1, .Abort => 1,
@@ -158,96 +158,96 @@ pub fn Arm32(comptime isa: Architecture) type {
} }
}; };
// FIXME: Is this a hack or idiomatic? pub const init = if (is_v5te) init9 else init7;
// See https://github.com/ziglang/zig/blob/1a0e6bcdb140c844384d62b78a7f4247753f9ffd/lib/std/atomic/Atomic.zig#L156-L176 pub const reset = if (is_v5te) reset9 else reset7;
pub usingnamespace if (is_v5te) struct {
// FIXME: this is pretty NDS9 specific lol
pub fn init(scheduler: Scheduler, bus: Bus, cp15: Coprocessor) Self {
return .{
.sched = scheduler,
.bus = bus,
.cpsr = .{ .raw = 0x0000_001F },
.spsr = .{ .raw = 0x0000_0000 },
.cp15 = cp15, fn init9(scheduler: Scheduler, bus: Bus, cp15: Coprocessor) Self {
.dtcm = .{}, return .{
.itcm = .{}, .sched = scheduler,
}; .bus = bus,
.cpsr = .{ .raw = 0x0000_001F },
.spsr = .{ .raw = 0x0000_0000 },
.cp15 = cp15,
.dtcm = .{},
.itcm = .{},
};
}
fn init7(scheduler: Scheduler, bus: Bus) Self {
return .{
.sched = scheduler,
.bus = bus,
.cpsr = .{ .raw = 0x0000_001F },
.spsr = .{ .raw = 0x0000_0000 },
.cp15 = {},
.dtcm = {},
.itcm = {},
};
}
// FIXME: Resetting disables logging (if enabled)
fn reset9(self: *Self) void {
self.* = .{
.sched = self.sched,
.bus = self.bus,
.cpsr = .{ .raw = 0x0000_001F },
.spsr = .{ .raw = 0x0000_0000 },
.dtcm = .{},
.itcm = .{},
.cp15 = self.cp15,
};
}
// FIXME: Resetting disables logging (if enabled)
fn reset7(self: *Self) void {
self.* = .{
.sched = self.sched,
.bus = self.bus,
.cpsr = .{ .raw = 0x0000_001F },
.spsr = .{ .raw = 0x0000_0000 },
.dtcm = {},
.itcm = {},
.cp15 = {},
};
}
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;
} }
// FIXME: Resetting disables logging (if enabled) return self.bus.dbgRead(T, address);
pub fn reset(self: *Self) void { }
self.* = .{
.sched = self.sched,
.bus = self.bus,
.cpsr = .{ .raw = 0x0000_001F },
.spsr = .{ .raw = 0x0000_0000 },
.dtcm = .{}, pub fn dbgWrite(self: *Self, comptime T: type, address: u32, value: T) void {
.itcm = .{}, if (is_v5te) {
.cp15 = self.cp15, if (self.itcm.write(T, address, value)) return;
}; if (self.dtcm.write(T, address, value)) return;
}
} else struct {
pub fn init(scheduler: Scheduler, bus: Bus) Self {
return .{
.sched = scheduler,
.bus = bus,
.cpsr = .{ .raw = 0x0000_001F },
.spsr = .{ .raw = 0x0000_0000 },
.cp15 = {},
.dtcm = {},
.itcm = {},
};
} }
// FIXME: Resetting disables logging (if enabled) return self.bus.dbgWrite(T, address, value);
pub fn reset(self: *Self) void { }
self.* = .{
.sched = self.sched,
.bus = self.bus,
.cpsr = .{ .raw = 0x0000_001F },
.spsr = .{ .raw = 0x0000_0000 },
.dtcm = {},
.itcm = {},
.cp15 = {},
};
}
};
// CPU needs it's own read/write fns due to ICTM and DCTM present in v5te // 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 // 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 { pub fn read(self: *Self, comptime T: type, address: u32) T {
const readInt = std.mem.readIntSliceLittle;
if (is_v5te) { if (is_v5te) {
const dtcm_base = self.dtcm.base_address; if (self.itcm.read(T, address)) |val| return val;
const dtcm_size = self.dtcm.virt.size; if (self.dtcm.read(T, address)) |val| return val;
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)]);
} }
return self.bus.read(T, address); return self.bus.read(T, address);
} }
pub fn write(self: *Self, comptime T: type, address: u32, value: T) void { pub fn write(self: *Self, comptime T: type, address: u32, value: T) void {
const writeInt = std.mem.writeIntSliceLittle;
if (is_v5te) { if (is_v5te) {
const dtcm_base = self.dtcm.base_address; if (self.itcm.write(T, address, value)) return;
const dtcm_size = self.dtcm.virt.size; if (self.dtcm.write(T, address, value)) return;
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);
} }
return self.bus.write(T, address, value); return self.bus.write(T, address, value);
@@ -371,7 +371,7 @@ pub fn Arm32(comptime isa: Architecture) type {
if (self.cpsr.check(Self.arch, cond)) { if (self.cpsr.check(Self.arch, cond)) {
if (isa == .v5te and cond == 0b1111) { 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); arm.lut[arm.idx(opcode)](self, opcode);
@@ -408,7 +408,8 @@ pub fn Arm32(comptime isa: Architecture) type {
std.debug.print("spsr: 0x{X:0>8} ", .{self.spsr.raw}); std.debug.print("spsr: 0x{X:0>8} ", .{self.spsr.raw});
self.spsr.toString(); self.spsr.toString();
std.debug.print("pipeline: {??X:0>8}\n", .{self.pipe.stage}); // FIXME(2025-09-22): Formatting here is wrong
std.debug.print("pipeline: {any:0>8}\n", .{self.pipe.stage});
if (self.cpsr.t.read()) { if (self.cpsr.t.read()) {
const opcode = self.bus.dbgRead(u16, self.r[15] - 4); const opcode = self.bus.dbgRead(u16, self.r[15] - 4);
@@ -425,6 +426,29 @@ pub fn Arm32(comptime isa: Architecture) type {
std.debug.panic(format, args); 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 { pub fn interface(self: *Self) Interpreter {
return switch (isa) { return switch (isa) {
.v4t => .{ .v4t = self }, .v4t => .{ .v4t = self },
@@ -441,6 +465,53 @@ fn Tcm(comptime count: usize, comptime default_addr: u32) type {
buf: [count * KiB]u8 = [_]u8{0x00} ** (count * KiB), buf: [count * KiB]u8 = [_]u8{0x00} ** (count * KiB),
base_address: u32 = default_addr, base_address: u32 = default_addr,
virt: struct { size: u32, mask: u32 } = .{ .size = count * KiB, .mask = (count * KiB) - 1 }, 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;
}
}; };
} }

View File

@@ -1,5 +1,5 @@
pub fn blockDataTransfer(comptime InstrFn: type, comptime P: bool, comptime U: bool, comptime S: bool, comptime W: bool, comptime L: bool) InstrFn { 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(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u32) void { fn inner(cpu: *Arm32, opcode: u32) void {
@@ -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) // U determines whether the LDM/STM transfer is made upwards (U == 1)
// or downwards (U == 0). // or downwards (U == 0).
const base_addr = cpu.r[rn];
const start_addr: u32 = if (U) blk: { 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: { } 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: { 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: { } else blk: {
break :blk cpu.r[rn] - 4 * reg_count; break :blk base_addr - 4 * reg_count;
}; };
var address = start_addr; var address = start_addr;
@@ -36,9 +36,9 @@ pub fn blockDataTransfer(comptime InstrFn: type, comptime P: bool, comptime U: b
if (rlist == 0) { if (rlist == 0) {
if (Arm32.arch == .v4t) { if (Arm32.arch == .v4t) {
const undefined_addr: u32 = if (U) blk: { 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: { } 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) { 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; 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| { for (first_in_list..16) |idx| {
const i: u4 = @intCast(idx); const i: u4 = @intCast(idx);
if (rlist >> i & 1 == 1) { if (rlist >> i & 1 == 1) {
transfer(cpu, r15_present, i, address); if (L) {
address += 4; load(cpu, i, rlist, address);
} else {
if (W and !L and write_to_base) { store(cpu, rn, i, rlist, address, .{ .old_addr = base_addr, .new_addr = new_base_addr });
cpu.r[rn] = new_base_addr;
write_to_base = false;
} }
address += 4;
} }
} }
// What happens when W is set and Rn is in the rlist? (LDM) if (W and !L)
// cpu.r[rn] = new_base_addr;
// ARMv4: No writeback
// ARMv5: writeback if Rn is "the ONLY register" or NOT the LAST register
if (W and L) { 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; cpu.r[rn] = new_base_addr;
return; 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 { fn load(cpu: *Arm32, ri: u4, rlist: u16, address: u32) void {
if (L) { const has_r15 = rlist >> 15 & 1 == 1;
if (S and !r15_present) {
// Always Transfer User mode Registers
cpu.setUserModeRegister(i, cpu.read(u32, address));
} else {
const value = cpu.read(u32, address);
cpu.r[i] = value; if (S and !has_r15) {
if (i == 0xF) { // Always Transfer User mode Registers
cpu.r[i] &= ~@as(u32, 3); // Align r15 cpu.setUserModeRegister(ri, cpu.read(u32, address));
cpu.pipe.reload(cpu);
if (S) cpu.setCpsr(cpu.spsr.raw);
}
}
} else { } else {
if (S) { const value = cpu.read(u32, address);
// Always Transfer User mode Registers cpu.r[ri] = value;
// This happens regardless if r15 is in the list
const value = cpu.getUserModeRegister(i); if (ri == 0xF) {
cpu.write(u32, address, value + if (i == 0xF) 4 else @as(u32, 0)); // PC is already 8 ahead to make 12 const mask: u32 = if (Arm32.arch == .v5te) 1 else 3;
} else { cpu.r[ri] &= ~mask;
cpu.write(u32, address, cpu.r[i] + if (i == 0xF) 4 else @as(u32, 0));
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; }.inner;
} }

View File

@@ -1,20 +1,35 @@
const sext = @import("zba-util").sext; const sext = @import("zba_util").sext;
pub fn branch(comptime InstrFn: type, comptime L: bool) InstrFn { pub fn branch(comptime InstrFn: type, comptime L: bool) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u32) void { 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.write(true);
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); cpu.pipe.reload(cpu);
} }
}.inner; }.inner;
} }
pub fn branchAndExchange(comptime InstrFn: type) InstrFn { pub fn branchAndExchange(comptime InstrFn: type) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
pub fn inner(cpu: *Arm32, opcode: u32) void { pub fn inner(cpu: *Arm32, opcode: u32) void {

View File

@@ -1,4 +1,5 @@
const std = @import("std"); const std = @import("std");
const Bit = @import("bitjuggle").Boolean;
const log = std.log.scoped(.coprocessor_handler); const log = std.log.scoped(.coprocessor_handler);
@@ -10,7 +11,7 @@ pub fn dataTransfer(
comptime W: bool, comptime W: bool,
comptime L: bool, comptime L: bool,
) InstrFn { ) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u32) void { fn inner(cpu: *Arm32, opcode: u32) void {
@@ -50,14 +51,13 @@ pub fn dataTransfer(
// TODO: Increment address + 4 (and perform op) until coprocessor says stop // TODO: Increment address + 4 (and perform op) until coprocessor says stop
if (L) { 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 { } 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 { fn copExt(cpu: *Arm32, opcode: u32) void {
_ = cpu;
const cp_num = opcode >> 8 & 0xF; const cp_num = opcode >> 8 & 0xF;
const rd = opcode >> 12 & 0xF; const rd = opcode >> 12 & 0xF;
const rn = opcode >> 16 & 0xF; const rn = opcode >> 16 & 0xF;
@@ -70,17 +70,17 @@ pub fn dataTransfer(
if (L) { if (L) {
// MRRC // 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 { } else {
// MCRR // 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; }.inner;
} }
pub fn registerTransfer(comptime InstrFn: type, comptime opcode1: u3, comptime L: bool, comptime opcode2: u3) InstrFn { pub fn registerTransfer(comptime InstrFn: type, comptime opcode1: u3, comptime L: bool, comptime opcode2: u3) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u32) void { fn inner(cpu: *Arm32, opcode: u32) void {
@@ -89,7 +89,11 @@ pub fn registerTransfer(comptime InstrFn: type, comptime opcode1: u3, comptime L
const cp_num = opcode >> 8 & 0xF; const cp_num = opcode >> 8 & 0xF;
const crm: u4 = @intCast(opcode & 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) { if (L) {
// MRC // MRC
@@ -118,6 +122,13 @@ pub fn registerTransfer(comptime InstrFn: type, comptime opcode1: u3, comptime L
// TODO: there has to be a better way..... // TODO: there has to be a better way.....
// ICTM / DTCM Stuff // 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 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 const itcm_size_base = cpu.cp15.read(0, 9, 1, 1); // mrc 0, c9, c1, 1
@@ -136,13 +147,30 @@ pub fn registerTransfer(comptime InstrFn: type, comptime opcode1: u3, comptime L
pub fn dataProcessing(comptime InstrFn: type, comptime opcode1: u4, comptime opcode2: u3) InstrFn { pub fn dataProcessing(comptime InstrFn: type, comptime opcode1: u4, comptime opcode2: u3) InstrFn {
_ = opcode2; _ = opcode2;
_ = opcode1; _ = opcode1;
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u32) void { fn inner(cpu: *Arm32, opcode: u32) void {
_ = cpu; cpu.panic("TODO: handle 0x{X:0>8} which is a coprocessor data processing instr", .{opcode});
log.err("TODO: handle 0x{X:0>8} which is a coprocessor data processing instr", .{opcode});
} }
}.inner; }.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,
};
};

View File

@@ -2,7 +2,7 @@ const exec = @import("../barrel_shifter.zig").exec;
const ror = @import("../barrel_shifter.zig").ror; const ror = @import("../barrel_shifter.zig").ror;
pub fn dataProcessing(comptime InstrFn: type, comptime I: bool, comptime S: bool, comptime kind: u4) InstrFn { pub fn dataProcessing(comptime InstrFn: type, comptime I: bool, comptime S: bool, comptime kind: u4) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u32) void { fn inner(cpu: *Arm32, opcode: u32) void {
@@ -153,7 +153,7 @@ pub fn dataProcessing(comptime InstrFn: type, comptime I: bool, comptime S: bool
} }
fn undefinedTestBehaviour(cpu: *Arm32) void { fn undefinedTestBehaviour(cpu: *Arm32) void {
@setCold(true); @branchHint(.cold);
cpu.setCpsr(cpu.spsr.raw); cpu.setCpsr(cpu.spsr.raw);
} }
}.inner; }.inner;

View File

@@ -1,8 +1,11 @@
const sext = @import("zba-util").sext; const std = @import("std");
const rotr = @import("zba-util").rotr; 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 { 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; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u32) void { fn inner(cpu: *Arm32, opcode: u32) void {
@@ -24,8 +27,10 @@ pub fn halfAndSignedDataTransfer(comptime InstrFn: type, comptime P: bool, compt
switch (op) { switch (op) {
0b01 => { 0b01 => {
// LDRH // LDRH
const value = cpu.read(u16, address); result = switch (Arm32.arch) {
result = rotr(u32, value, 8 * (address & 1)); .v4t => rotr(u32, cpu.read(u16, address), 8 * (address & 1)),
.v5te => cpu.read(u16, address),
};
}, },
0b10 => { 0b10 => {
// LDRSB // LDRSB
@@ -33,10 +38,17 @@ pub fn halfAndSignedDataTransfer(comptime InstrFn: type, comptime P: bool, compt
}, },
0b11 => { 0b11 => {
// LDRSH // 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 break :blk switch (address & 1 == 1) {
result = if (address & 1 == 1) sext(u32, u8, @as(u8, @truncate(value >> 8))) else sext(u32, u16, value); true => sext(u32, u8, @as(u8, @truncate(value >> 8))),
false => sext(u32, u16, value),
};
},
.v5te => sext(u32, u16, cpu.read(u16, address)),
};
}, },
0b00 => unreachable, 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 // FIXME: I shouldn't have to use @as(u16, ...) here
cpu.write(u16, address, @as(u16, @truncate(cpu.r[rd]))); cpu.write(u16, address, @as(u16, @truncate(cpu.r[rd])));
}, },
0b10 => { 0b10 => blk: {
// LDRD // 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 & 0 != 0) cpu.panic("LDRD: UNDEFINED behaviour when Rd is not even", .{});
if (rd == 0xE) cpu.panic("LDRD: UNPREDICTABLE behaviour when rd == 14", .{}); 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}); if (address & 0x7 != 0b000) cpu.panic("LDRD: UNPREDICTABLE when address (0x{X:0>8} is not double (64-bit) aligned", .{address});

View File

@@ -1,5 +1,5 @@
pub fn multiply(comptime InstrFn: type, comptime L: bool, comptime U: bool, comptime A: bool, comptime S: bool) InstrFn { pub fn multiply(comptime InstrFn: type, comptime L: bool, comptime U: bool, comptime A: bool, comptime S: bool) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u32) void { fn inner(cpu: *Arm32, opcode: u32) void {

View File

@@ -1,12 +1,12 @@
const std = @import("std"); const std = @import("std");
const PSR = @import("../../../arm.zig").PSR; const PSR = @import("../../../arm.zig").PSR;
const rotr = @import("zba-util").rotr; const rotr = @import("zba_util").rotr;
const log = std.log.scoped(.ctrl_ext_space); const log = std.log.scoped(.ctrl_ext_space);
pub fn control(comptime InstrFn: type, comptime I: bool, comptime op: u6) InstrFn { pub fn control(comptime InstrFn: type, comptime I: bool, comptime op: u6) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u32) void { fn inner(cpu: *Arm32, opcode: u32) void {
@@ -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", .{}), 0b01_0001 => cpu.panic("TODO: implement v5TE BX", .{}),
0b11_0001 => { // CLZ 0b11_0001 => { // CLZ
if (Arm32.arch == .v4t) return cpu.undefinedInstructionTrap();
const rd = opcode >> 12 & 0xF; const rd = opcode >> 12 & 0xF;
const rm = opcode & 0xF; const rm = opcode & 0xF;
@@ -50,6 +51,7 @@ pub fn control(comptime InstrFn: type, comptime I: bool, comptime op: u6) InstrF
cpu.pipe.reload(cpu); cpu.pipe.reload(cpu);
}, },
0b00_0101, 0b01_0101, 0b10_0101, 0b11_0101 => { // QADD / QDADD / QSUB / QDSUB 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 U = op >> 4 & 1 == 1;
const D = op >> 5 & 1 == 1; const D = op >> 5 & 1 == 1;
@@ -66,7 +68,7 @@ pub fn control(comptime InstrFn: type, comptime I: bool, comptime op: u6) InstrF
if (ret[1] == 0b1) { if (ret[1] == 0b1) {
product = if (product < 0) std.math.maxInt(i32) else std.math.minInt(i32); product = if (product < 0) std.math.maxInt(i32) else std.math.minInt(i32);
cpu.cpsr.q.set(); cpu.cpsr.q.write(true);
} }
break :blk product; break :blk product;
@@ -77,13 +79,14 @@ pub fn control(comptime InstrFn: type, comptime I: bool, comptime op: u6) InstrF
if (ret[1] == 0b1) { if (ret[1] == 0b1) {
result = if (result < 0) std.math.maxInt(i32) else std.math.minInt(i32); result = if (result < 0) std.math.maxInt(i32) else std.math.minInt(i32);
cpu.cpsr.q.set(); cpu.cpsr.q.write(true);
} }
cpu.r[rd] = @bitCast(result); cpu.r[rd] = @bitCast(result);
}, },
0b01_0111 => cpu.panic("TODO: handle BKPT", .{}), 0b01_0111 => cpu.panic("TODO: handle BKPT", .{}),
0b00_1000, 0b00_1010, 0b00_1100, 0b00_1110 => { // SMLA<x><y> 0b00_1000, 0b00_1010, 0b00_1100, 0b00_1110 => { // SMLA<x><y>
if (Arm32.arch == .v4t) return; // no-op
const X = op >> 1 & 1; const X = op >> 1 & 1;
const Y = op >> 2 & 1; const Y = op >> 2 & 1;
@@ -99,7 +102,7 @@ pub fn control(comptime InstrFn: type, comptime I: bool, comptime op: u6) InstrF
const result = @addWithOverflow(left * right, accumulate); const result = @addWithOverflow(left * right, accumulate);
cpu.r[rd] = @bitCast(result[0]); cpu.r[rd] = @bitCast(result[0]);
if (result[1] == 0b1) cpu.cpsr.q.set(); if (result[1] == 0b1) cpu.cpsr.q.write(true);
}, },
0b10_1000, 0b10_1010, 0b10_1100, 0b10_1110 => { // SMLAL<x><y> 0b10_1000, 0b10_1010, 0b10_1100, 0b10_1110 => { // SMLAL<x><y>
@@ -108,26 +111,21 @@ pub fn control(comptime InstrFn: type, comptime I: bool, comptime op: u6) InstrF
const rm = opcode & 0xF; const rm = opcode & 0xF;
const rs = opcode >> 8 & 0xF; const rs = opcode >> 8 & 0xF;
const rd_lo = opcode >> 12 & 0xF; const rdlo = opcode >> 12 & 0xF;
const rd_hi = opcode >> 16 & 0xF; const rdhi = opcode >> 16 & 0xF;
const left: i32 = @as(i16, @bitCast(@as(u16, @truncate(cpu.r[rm] >> 16 * X)))); const left: i64 = @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)))); const right: i64 = @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 product = left * right; 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); const accumulate = @as(i64, rdhi_val) << 32 | rdlo_val;
cpu.r[rd_hi] = blk: { const sum = product +% accumulate;
// 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 rdhi_val: i32 = @bitCast(cpu.r[rd_hi]); cpu.r[rdhi] = @bitCast(@as(i32, @truncate(sum >> 32)));
break :blk @bitCast(rdhi_val + offset_thing + @addWithOverflow(rdlo_val, product)[1]); cpu.r[rdlo] = @bitCast(@as(i32, @truncate(sum)));
};
}, },
0b01_1000, 0b01_1100 => { // SMLAW<y> 0b01_1000, 0b01_1100 => { // SMLAW<y>
const Y = op >> 2 & 1; const Y = op >> 2 & 1;
@@ -145,7 +143,7 @@ pub fn control(comptime InstrFn: type, comptime I: bool, comptime op: u6) InstrF
const ret = @addWithOverflow(@as(i32, @truncate((left * right) >> 16)), accumulate); const ret = @addWithOverflow(@as(i32, @truncate((left * right) >> 16)), accumulate);
cpu.r[rd] = @bitCast(ret[0]); cpu.r[rd] = @bitCast(ret[0]);
if (ret[1] == 0b1) cpu.cpsr.q.set(); if (ret[1] == 0b1) cpu.cpsr.q.write(true);
}, },
0b01_1010, 0b01_1110 => { // SMULW<y> 0b01_1010, 0b01_1110 => { // SMULW<y>
@@ -178,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 { inline fn msr(comptime R: bool, comptime imm: bool, cpu: *Arm32, opcode: u32) void {
const field_mask: u4 = @truncate(opcode >> 16 & 0xF); const field_mask: u4 = @truncate(opcode >> 16 & 0xF);
const rm_idx = opcode & 0xF; const rm_idx = opcode & 0xF;

View File

@@ -1,9 +1,9 @@
const shifter = @import("../barrel_shifter.zig"); const shifter = @import("../barrel_shifter.zig");
const rotr = @import("zba-util").rotr; 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 { 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(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u32) void { fn inner(cpu: *Arm32, opcode: u32) void {
@@ -49,7 +49,15 @@ pub fn singleDataTransfer(comptime InstrFn: type, comptime I: bool, comptime P:
if (L) { if (L) {
// This emulates the LDR rd == rn behaviour // This emulates the LDR rd == rn behaviour
cpu.r[rd] = result; 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; }.inner;

View File

@@ -1,5 +1,5 @@
pub fn armSoftwareInterrupt(comptime InstrFn: type) InstrFn { pub fn armSoftwareInterrupt(comptime InstrFn: type) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, _: u32) void { fn inner(cpu: *Arm32, _: u32) void {
@@ -14,7 +14,13 @@ pub fn armSoftwareInterrupt(comptime InstrFn: type) InstrFn {
cpu.r[14] = ret_addr; // Resume Execution cpu.r[14] = ret_addr; // Resume Execution
cpu.spsr.raw = cpsr; // Previous mode CPSR 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); cpu.pipe.reload(cpu);
} }
}.inner; }.inner;

View File

@@ -1,6 +1,6 @@
const CPSR = @import("../../arm.zig").PSR; const CPSR = @import("../../arm.zig").PSR;
const rotr = @import("zba-util").rotr; const rotr = @import("zba_util").rotr;
pub fn exec(comptime S: bool, cpu: anytype, opcode: u32) u32 { pub fn exec(comptime S: bool, cpu: anytype, opcode: u32) u32 {
var result: u32 = undefined; var result: u32 = undefined;
@@ -70,7 +70,7 @@ pub fn immediate(comptime S: bool, cpu: anytype, opcode: u32) u32 {
pub fn lsl(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u8) u32 { pub fn lsl(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u8) u32 {
const amount: u5 = @truncate(total_amount); const amount: u5 = @truncate(total_amount);
const bit_count: u8 = @typeInfo(u32).Int.bits; const bit_count: u8 = @typeInfo(u32).int.bits;
var result: u32 = 0x0000_0000; var result: u32 = 0x0000_0000;
if (total_amount < bit_count) { if (total_amount < bit_count) {
@@ -97,7 +97,7 @@ pub fn lsl(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u8) u32 {
pub fn lsr(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u32) u32 { pub fn lsr(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u32) u32 {
const amount: u5 = @truncate(total_amount); const amount: u5 = @truncate(total_amount);
const bit_count: u8 = @typeInfo(u32).Int.bits; const bit_count: u8 = @typeInfo(u32).int.bits;
var result: u32 = 0x0000_0000; var result: u32 = 0x0000_0000;
if (total_amount < bit_count) { if (total_amount < bit_count) {
@@ -121,7 +121,7 @@ pub fn lsr(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u32) u32 {
pub fn asr(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u8) u32 { pub fn asr(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u8) u32 {
const amount: u5 = @truncate(total_amount); const amount: u5 = @truncate(total_amount);
const bit_count: u8 = @typeInfo(u32).Int.bits; const bit_count: u8 = @typeInfo(u32).int.bits;
var result: u32 = 0x0000_0000; var result: u32 = 0x0000_0000;
if (total_amount < bit_count) { if (total_amount < bit_count) {

View File

@@ -7,7 +7,7 @@ const asr = @import("../barrel_shifter.zig").asr;
const ror = @import("../barrel_shifter.zig").ror; const ror = @import("../barrel_shifter.zig").ror;
pub fn fmt4(comptime InstrFn: type, comptime op: u4) InstrFn { pub fn fmt4(comptime InstrFn: type, comptime op: u4) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u16) void { fn inner(cpu: *Arm32, opcode: u16) void {

View File

@@ -1,5 +1,5 @@
pub fn fmt14(comptime InstrFn: type, comptime L: bool, comptime R: bool) InstrFn { pub fn fmt14(comptime InstrFn: type, comptime L: bool, comptime R: bool) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u16) void { fn inner(cpu: *Arm32, opcode: u16) void {
@@ -32,6 +32,8 @@ pub fn fmt14(comptime InstrFn: type, comptime L: bool, comptime R: bool) InstrFn
if (L) { if (L) {
const value = cpu.read(u32, address); const value = cpu.read(u32, address);
cpu.r[15] = value & ~@as(u32, 1); cpu.r[15] = value & ~@as(u32, 1);
if (Arm32.arch == .v5te) cpu.cpsr.t.write(value & 1 == 1);
cpu.pipe.reload(cpu); cpu.pipe.reload(cpu);
} else { } else {
cpu.write(u32, address, cpu.r[14]); cpu.write(u32, address, cpu.r[14]);
@@ -45,7 +47,7 @@ pub fn fmt14(comptime InstrFn: type, comptime L: bool, comptime R: bool) InstrFn
} }
pub fn fmt15(comptime InstrFn: type, comptime L: bool, comptime rb: u3) InstrFn { pub fn fmt15(comptime InstrFn: type, comptime L: bool, comptime rb: u3) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u16) void { fn inner(cpu: *Arm32, opcode: u16) void {

View File

@@ -1,7 +1,7 @@
const sext = @import("zba-util").sext; const sext = @import("zba_util").sext;
pub fn fmt16(comptime InstrFn: type, comptime cond: u4) InstrFn { pub fn fmt16(comptime InstrFn: type, comptime cond: u4) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u16) void { fn inner(cpu: *Arm32, opcode: u16) void {
@@ -17,38 +17,37 @@ pub fn fmt16(comptime InstrFn: type, comptime cond: u4) InstrFn {
}.inner; }.inner;
} }
pub fn fmt18(comptime InstrFn: type) 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; 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 {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u16) void { fn inner(cpu: *Arm32, opcode: u16) void {
// BL
const offset = opcode & 0x7FF; const offset = opcode & 0x7FF;
if (is_low) { switch (H) {
// Instruction 2 0b00 => { // Unconditional Branch
const next_opcode = cpu.r[15] - 2; 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[15] = (cpu.r[14] +% (offset << 1)) & ~@as(u32, 0x3);
cpu.r[14] = next_opcode | 1; cpu.r[14] = next_addr | 1;
cpu.cpsr.t.write(false);
cpu.pipe.reload(cpu); cpu.pipe.reload(cpu);
} else { },
// Instruction 1 0b10 => cpu.r[14] = cpu.r[15] +% (sext(u32, u11, offset) << 12), // BL / BLX Pt. 1
const lr_offset = sext(u32, u11, offset) << 12; 0b11 => { // BL Pt. 2
cpu.r[14] = (cpu.r[15] +% lr_offset) & ~@as(u32, 1); 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; }.inner;

View File

@@ -5,7 +5,7 @@ const lsr = @import("../barrel_shifter.zig").lsr;
const asr = @import("../barrel_shifter.zig").asr; const asr = @import("../barrel_shifter.zig").asr;
pub fn fmt1(comptime InstrFn: type, comptime op: u2, comptime offset: u5) InstrFn { pub fn fmt1(comptime InstrFn: type, comptime op: u2, comptime offset: u5) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u16) void { fn inner(cpu: *Arm32, opcode: u16) void {
@@ -53,13 +53,24 @@ pub fn fmt1(comptime InstrFn: type, comptime op: u2, comptime offset: u5) InstrF
} }
pub fn fmt5(comptime InstrFn: type, comptime op: u2, comptime h1: u1, comptime h2: u1) InstrFn { pub fn fmt5(comptime InstrFn: type, comptime op: u2, comptime h1: u1, comptime h2: u1) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u16) void { fn inner(cpu: *Arm32, opcode: u16) void {
const rs = @as(u4, h2) << 3 | (opcode >> 3 & 0x7); const rs = @as(u4, h2) << 3 | (opcode >> 3 & 0x7);
const rd = @as(u4, h1) << 3 | (opcode & 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 op1 = cpu.r[rd];
const op2 = cpu.r[rs]; const op2 = cpu.r[rs];
@@ -108,7 +119,7 @@ pub fn fmt5(comptime InstrFn: type, comptime op: u2, comptime h1: u1, comptime h
} }
pub fn fmt2(comptime InstrFn: type, comptime I: bool, is_sub: bool, rn: u3) InstrFn { pub fn fmt2(comptime InstrFn: type, comptime I: bool, is_sub: bool, rn: u3) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u16) void { fn inner(cpu: *Arm32, opcode: u16) void {
@@ -142,7 +153,7 @@ pub fn fmt2(comptime InstrFn: type, comptime I: bool, is_sub: bool, rn: u3) Inst
} }
pub fn fmt3(comptime InstrFn: type, comptime op: u2, comptime rd: u3) InstrFn { pub fn fmt3(comptime InstrFn: type, comptime op: u2, comptime rd: u3) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u16) void { fn inner(cpu: *Arm32, opcode: u16) void {
@@ -182,7 +193,7 @@ pub fn fmt3(comptime InstrFn: type, comptime op: u2, comptime rd: u3) InstrFn {
} }
pub fn fmt12(comptime InstrFn: type, comptime isSP: bool, comptime rd: u3) InstrFn { pub fn fmt12(comptime InstrFn: type, comptime isSP: bool, comptime rd: u3) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u16) void { fn inner(cpu: *Arm32, opcode: u16) void {
@@ -195,7 +206,7 @@ pub fn fmt12(comptime InstrFn: type, comptime isSP: bool, comptime rd: u3) Instr
} }
pub fn fmt13(comptime InstrFn: type, comptime S: bool) InstrFn { pub fn fmt13(comptime InstrFn: type, comptime S: bool) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u16) void { fn inner(cpu: *Arm32, opcode: u16) void {
@@ -205,3 +216,13 @@ pub fn fmt13(comptime InstrFn: type, comptime S: bool) InstrFn {
} }
}.inner; }.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;
}

View File

@@ -1,8 +1,8 @@
const rotr = @import("zba-util").rotr; const rotr = @import("zba_util").rotr;
const sext = @import("zba-util").sext; const sext = @import("zba_util").sext;
pub fn fmt6(comptime InstrFn: type, comptime rd: u3) InstrFn { pub fn fmt6(comptime InstrFn: type, comptime rd: u3) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u16) void { fn inner(cpu: *Arm32, opcode: u16) void {
@@ -16,7 +16,7 @@ pub fn fmt6(comptime InstrFn: type, comptime rd: u3) InstrFn {
} }
pub fn fmt78(comptime InstrFn: type, comptime op: u2, comptime T: bool) InstrFn { pub fn fmt78(comptime InstrFn: type, comptime op: u2, comptime T: bool) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u16) void { fn inner(cpu: *Arm32, opcode: u16) void {
@@ -41,15 +41,24 @@ pub fn fmt78(comptime InstrFn: type, comptime op: u2, comptime T: bool) InstrFn
}, },
0b10 => { 0b10 => {
// LDRH // LDRH
const value = cpu.read(u16, address); cpu.r[rd] = switch (Arm32.arch) {
cpu.r[rd] = rotr(u32, value, 8 * (address & 1)); .v4t => rotr(u32, cpu.read(u16, address), 8 * (address & 1)),
.v5te => cpu.read(u16, address),
};
}, },
0b11 => { 0b11 => {
// LDRSH // 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 break :blk switch (address & 1 == 1) {
cpu.r[rd] = if (address & 1 == 1) sext(u32, u8, @as(u8, @truncate(value >> 8))) else sext(u32, u16, value); true => sext(u32, u8, @as(u8, @truncate(value >> 8))),
false => sext(u32, u16, value),
};
},
.v5te => sext(u32, u16, cpu.read(u16, address)),
};
}, },
} }
} else { } else {
@@ -81,7 +90,7 @@ pub fn fmt78(comptime InstrFn: type, comptime op: u2, comptime T: bool) InstrFn
} }
pub fn fmt9(comptime InstrFn: type, comptime B: bool, comptime L: bool, comptime offset: u5) InstrFn { pub fn fmt9(comptime InstrFn: type, comptime B: bool, comptime L: bool, comptime offset: u5) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u16) void { fn inner(cpu: *Arm32, opcode: u16) void {
@@ -117,7 +126,7 @@ pub fn fmt9(comptime InstrFn: type, comptime B: bool, comptime L: bool, comptime
} }
pub fn fmt10(comptime InstrFn: type, comptime L: bool, comptime offset: u5) InstrFn { pub fn fmt10(comptime InstrFn: type, comptime L: bool, comptime offset: u5) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u16) void { fn inner(cpu: *Arm32, opcode: u16) void {
@@ -128,8 +137,10 @@ pub fn fmt10(comptime InstrFn: type, comptime L: bool, comptime offset: u5) Inst
if (L) { if (L) {
// LDRH // LDRH
const value = cpu.read(u16, address); cpu.r[rd] = switch (Arm32.arch) {
cpu.r[rd] = rotr(u32, value, 8 * (address & 1)); .v4t => rotr(u32, cpu.read(u16, address), 8 * (address & 1)),
.v5te => cpu.read(u16, address),
};
} else { } else {
// STRH // STRH
@@ -141,7 +152,7 @@ pub fn fmt10(comptime InstrFn: type, comptime L: bool, comptime offset: u5) Inst
} }
pub fn fmt11(comptime InstrFn: type, comptime L: bool, comptime rd: u3) InstrFn { pub fn fmt11(comptime InstrFn: type, comptime L: bool, comptime rd: u3) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, opcode: u16) void { fn inner(cpu: *Arm32, opcode: u16) void {

View File

@@ -1,5 +1,5 @@
pub fn fmt17(comptime InstrFn: type) InstrFn { pub fn fmt17(comptime InstrFn: type) InstrFn {
const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).Pointer.child).Fn.params[0].type.?).Pointer.child; const Arm32 = @typeInfo(@typeInfo(@typeInfo(InstrFn).pointer.child).@"fn".params[0].type.?).pointer.child;
return struct { return struct {
fn inner(cpu: *Arm32, _: u16) void { fn inner(cpu: *Arm32, _: u16) void {
@@ -14,7 +14,13 @@ pub fn fmt17(comptime InstrFn: type) InstrFn {
cpu.r[14] = ret_addr; // Resume Execution cpu.r[14] = ret_addr; // Resume Execution
cpu.spsr.raw = cpsr; // Previous mode CPSR 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); cpu.pipe.reload(cpu);
} }
}.inner; }.inner;

View File

@@ -20,6 +20,8 @@ pub const arm = struct {
/// Arithmetic Instruction Extension Space /// Arithmetic Instruction Extension Space
const multiplyExt = @import("cpu/arm/multiply.zig").multiply; const multiplyExt = @import("cpu/arm/multiply.zig").multiply;
const cop = @import("cpu/arm/coprocessor.zig");
/// Determine index into ARM InstrFn LUT /// Determine index into ARM InstrFn LUT
pub fn idx(opcode: u32) u12 { pub fn idx(opcode: u32) u12 {
// FIXME: omit these? // FIXME: omit these?
@@ -87,8 +89,29 @@ pub const arm = struct {
const L = i >> 8 & 1 == 1; const L = i >> 8 & 1 == 1;
break :blk branch(InstrFn, L); break :blk branch(InstrFn, L);
}, },
0b10 => und, // COP Data Transfer 0b10 => blk: {
0b11 => if (i >> 8 & 1 == 1) swi(InstrFn) else und, // COP Data Operation + Register Transfer 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; const cond = i >> 2 & 0xF;
break :blk branch.fmt16(InstrFn, cond); break :blk branch.fmt16(InstrFn, cond);
}, },
0b110 => branch.fmt18( 0b110, 0b111 => blk: {
InstrFn, const H = i >> 5 & 0x3;
), break :blk branch.linkExchange(InstrFn, H);
0b111 => blk: {
const is_low = i >> 5 & 1 == 1;
break :blk branch.fmt19(InstrFn, is_low);
}, },
}, },
}; };

View File

@@ -149,6 +149,8 @@ pub const thumb = struct {
return comptime comptime_blk: { return comptime comptime_blk: {
@setEvalBranchQuota(5025); // This is exact @setEvalBranchQuota(5025); // This is exact
var table = [_]InstrFn{und} ** 0x400; 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| { for (&table, 0..) |*handler, i| {
handler.* = switch (@as(u3, i >> 7 & 0x7)) { handler.* = switch (@as(u3, i >> 7 & 0x7)) {
@@ -210,13 +212,18 @@ pub const thumb = struct {
const rd = i >> 2 & 0x7; const rd = i >> 2 & 0x7;
break :blk processing.fmt12(InstrFn, isSP, rd); break :blk processing.fmt12(InstrFn, isSP, rd);
}, },
0b011 => if (i >> 4 & 1 == 1) blk: { 0b011 => switch (@as(u2, @truncate(i >> 3 & 0x3))) {
const L = i >> 5 & 1 == 1; 0b10 => blk: {
const R = i >> 2 & 1 == 1; // PUSH / POP
break :blk block_transfer.fmt14(InstrFn, L, R); const L = i >> 5 & 1 == 1;
} else blk: { const R = i >> 2 & 1 == 1;
const S = i >> 1 & 1 == 1; break :blk block_transfer.fmt14(InstrFn, L, R);
break :blk processing.fmt13(InstrFn, S); },
0b11 => processing.bkpt(InstrFn),
else => blk: {
const S = i >> 1 & 1 == 1;
break :blk processing.fmt13(InstrFn, S);
},
}, },
0b100 => blk: { 0b100 => blk: {
const L = i >> 5 & 1 == 1; const L = i >> 5 & 1 == 1;
@@ -230,12 +237,9 @@ pub const thumb = struct {
const cond = i >> 2 & 0xF; const cond = i >> 2 & 0xF;
break :blk branch.fmt16(InstrFn, cond); break :blk branch.fmt16(InstrFn, cond);
}, },
0b110 => branch.fmt18( 0b110, 0b111 => blk: {
InstrFn, const H = i >> 5 & 0x3;
), break :blk branch.linkExchange(InstrFn, H);
0b111 => blk: {
const is_low = i >> 5 & 1 == 1;
break :blk branch.fmt19(InstrFn, is_low);
}, },
}, },
}; };

View File

@@ -92,9 +92,9 @@ pub const Bus = struct {
const P = @TypeOf(obj); const P = @TypeOf(obj);
const info = @typeInfo(P); const info = @typeInfo(P);
std.debug.assert(info == .Pointer); // `anytype` is a Pointer std.debug.assert(info == .pointer); // `anytype` is a Pointer
std.debug.assert(info.Pointer.size == .One); // Single-Item Pointer std.debug.assert(info.pointer.size == .one); // Single-Item Pointer
std.debug.assert(@typeInfo(info.Pointer.child) == .Struct); // Pointer Child is a `struct` std.debug.assert(@typeInfo(info.pointer.child) == .@"struct"); // Pointer Child is a `struct`
const impl = struct { const impl = struct {
fn read8(ptr: *anyopaque, address: u32) u8 { fn read8(ptr: *anyopaque, address: u32) u8 {
@@ -272,9 +272,9 @@ pub const Coprocessor = struct {
const P = @TypeOf(obj); const P = @TypeOf(obj);
const info = @typeInfo(P); const info = @typeInfo(P);
std.debug.assert(info == .Pointer); // `anytype` is a Pointer std.debug.assert(info == .pointer); // `anytype` is a Pointer
std.debug.assert(info.Pointer.size == .One); // Single-Item Pointer std.debug.assert(info.pointer.size == .one); // Single-Item Pointer
std.debug.assert(@typeInfo(info.Pointer.child) == .Struct); // Pointer Child is a `struct` std.debug.assert(@typeInfo(info.pointer.child) == .@"struct"); // Pointer Child is a `struct`
const impl = struct { const impl = struct {
fn read(ptr: *anyopaque, op1: u3, cn: u4, cm: u4, op2: u3) u32 { fn read(ptr: *anyopaque, op1: u3, cn: u4, cm: u4, op2: u3) u32 {
@@ -351,9 +351,9 @@ pub const Scheduler = struct {
const P = @TypeOf(obj); const P = @TypeOf(obj);
const info = @typeInfo(P); const info = @typeInfo(P);
std.debug.assert(info == .Pointer); // `anytype` is a Pointer std.debug.assert(info == .pointer); // `anytype` is a Pointer
std.debug.assert(info.Pointer.size == .One); // Single-Item Pointer std.debug.assert(info.pointer.size == .one); // Single-Item Pointer
std.debug.assert(@typeInfo(info.Pointer.child) == .Struct); // Pointer Child is a `struct` std.debug.assert(@typeInfo(info.pointer.child) == .@"struct"); // Pointer Child is a `struct`
const impl = struct { const impl = struct {
fn now(ptr: *anyopaque) u64 { fn now(ptr: *anyopaque) u64 {