Compare commits
4 Commits
580e7baca9
...
main
Author | SHA1 | Date | |
---|---|---|---|
814d081ea0 | |||
0010029783 | |||
6f0e271360 | |||
bdc4bfc642 |
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",
|
||||
},
|
||||
}
|
||||
|
25
src/arm.zig
25
src/arm.zig
@@ -217,6 +217,24 @@ 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 {
|
||||
@@ -462,8 +480,6 @@ fn Tcm(comptime count: usize, comptime default_addr: u32) type {
|
||||
///
|
||||
/// The caller doesn't particularly care about "why" though.
|
||||
pub fn read(self: *const @This(), comptime T: type, address: u32) ?T {
|
||||
const readInt = std.mem.readIntSliceLittle;
|
||||
|
||||
if (!self.enabled) return null;
|
||||
if (self.load_mode) return null;
|
||||
|
||||
@@ -471,7 +487,7 @@ fn Tcm(comptime count: usize, comptime default_addr: u32) type {
|
||||
const end_addr = self.base_address + self.virt.size;
|
||||
|
||||
if (start_addr <= address and address < end_addr) {
|
||||
return readInt(T, self.buf[address & self.virt.mask ..][0..@sizeOf(T)]);
|
||||
return std.mem.readInt(T, self.buf[address & self.virt.mask ..][0..@sizeOf(T)], .little);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -485,14 +501,13 @@ fn Tcm(comptime count: usize, comptime default_addr: u32) type {
|
||||
///
|
||||
/// The caller doesn't particularly care about "why" though.
|
||||
pub fn write(self: *@This(), comptime T: type, address: u32, value: T) bool {
|
||||
const writeInt = std.mem.writeIntSliceLittle;
|
||||
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) {
|
||||
writeInt(T, self.buf[address & self.virt.mask ..][0..@sizeOf(T)], value);
|
||||
std.mem.writeInt(T, self.buf[address & self.virt.mask ..][0..@sizeOf(T)], value, .little);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -27,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
|
||||
@@ -36,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,
|
||||
}
|
||||
|
@@ -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
|
||||
|
||||
|
Reference in New Issue
Block a user