improve debugging, fix a lot, add basic op test, heap allocation, dear god
This commit is contained in:
parent
24f5fd49fa
commit
6d2cb973d1
3 changed files with 193 additions and 115 deletions
15
src/main.zig
15
src/main.zig
|
@ -3,6 +3,8 @@ const std = @import("std");
|
|||
const Uxn = @import("uxn.zig");
|
||||
const Varvara = @import("varvara.zig");
|
||||
|
||||
const DEBUG = true;
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
|
@ -19,16 +21,17 @@ pub fn main() !void {
|
|||
const rom = try file.readToEndAlloc(allocator, 0xFF00);
|
||||
defer allocator.free(rom);
|
||||
|
||||
var varvara = Varvara.init(rom);
|
||||
Uxn.setDebug(DEBUG);
|
||||
|
||||
var running = true;
|
||||
while (running) {
|
||||
std.debug.print("pc={X} code={X} op={s}\n", .{
|
||||
var varvara = try Varvara.init(allocator, rom);
|
||||
defer varvara.deinit();
|
||||
|
||||
while (varvara.uxn.eval()) {
|
||||
if (DEBUG) std.debug.print("{s}\t\tpc {X}, code {X}\n", .{
|
||||
Uxn.fmtInstrs(varvara.uxn.mem.m[varvara.uxn.pc .. varvara.uxn.pc +% 1]),
|
||||
varvara.uxn.pc,
|
||||
varvara.uxn.mem.m[varvara.uxn.pc],
|
||||
Uxn.fmtInstrs(varvara.uxn.mem.m[varvara.uxn.pc .. varvara.uxn.pc +% 1]),
|
||||
});
|
||||
if (varvara.uxn.eval()) running = false;
|
||||
}
|
||||
} else return error.NoRom;
|
||||
}
|
||||
|
|
150
src/uxn.zig
150
src/uxn.zig
|
@ -1,11 +1,17 @@
|
|||
const std = @import("std");
|
||||
const Uxn = @This();
|
||||
|
||||
var debug = true;
|
||||
|
||||
pub fn setDebug(v: bool) void {
|
||||
debug = v;
|
||||
}
|
||||
|
||||
mem: Memory = .{ .m = undefined },
|
||||
ws: Stack = .{ .s = undefined },
|
||||
rs: Stack = .{ .s = undefined },
|
||||
dev: [0x100]u8 = undefined,
|
||||
pc: u16,
|
||||
pc: u16 = 0x100,
|
||||
|
||||
deo8: *const fn (*Uxn, *Stack, u8, u8) void = deo8Stub,
|
||||
deo16: *const fn (*Uxn, *Stack, u8, u16) void = deo16Stub,
|
||||
|
@ -17,18 +23,43 @@ fn deo16Stub(_: *Uxn, _: *Stack, _: u8, _: u16) void {}
|
|||
fn dei8Stub(_: *Uxn, _: *Stack, _: u8) void {}
|
||||
fn dei16Stub(_: *Uxn, _: *Stack, _: u8) void {}
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) !Uxn {
|
||||
const mem = try allocator.alloc(u8, 0x10000);
|
||||
errdefer allocator.free(mem);
|
||||
for (mem) |*b| b.* = 0;
|
||||
return .{
|
||||
.mem = .{ .m = mem },
|
||||
.ws = .{ .s = [_]u8{0} ** 0x100 },
|
||||
.rs = .{ .s = [_]u8{0} ** 0x100 },
|
||||
.dev = [_]u8{0} ** 0x100,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Uxn, allocator: std.mem.Allocator) void {
|
||||
allocator.free(self.mem.m);
|
||||
}
|
||||
|
||||
const Memory = struct {
|
||||
m: [0x10000]u8,
|
||||
m: []u8,
|
||||
|
||||
pub fn peek(self: *Memory, comptime T: type, idx: u16) T {
|
||||
return switch (T) {
|
||||
u8 => self.m[idx],
|
||||
u16 => @as(u16, @intCast(self.m[idx])) << 8 | self.m[idx +% 1],
|
||||
switch (T) {
|
||||
u8 => {
|
||||
const val = self.m[idx];
|
||||
if (debug) std.debug.print("\x1b[33mmem peek:\x1b[0m\tidx {d}, type {s}, val {d}\n", .{ idx, @typeName(T), val });
|
||||
return val;
|
||||
},
|
||||
u16 => {
|
||||
const val = @as(u16, @intCast(self.m[idx])) << 8 | self.m[idx +% 1];
|
||||
if (debug) std.debug.print("\x1b[33mmem peek:\x1b[0m\tidx {d}, type {s}, val {d}\n", .{ idx, @typeName(T), val });
|
||||
return val;
|
||||
},
|
||||
else => @compileError("expected u8 or u16, got " + @typeName(T)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poke(self: *Memory, comptime T: type, idx: u16, val: T) void {
|
||||
if (debug) std.debug.print("\x1b[33mmem poke:\x1b[0m\tidx {d}, type {s}, val {d}\n", .{ idx, @typeName(T), val });
|
||||
switch (T) {
|
||||
u8 => self.m[idx] = val,
|
||||
u16 => {
|
||||
|
@ -41,28 +72,37 @@ const Memory = struct {
|
|||
};
|
||||
|
||||
test "memory peek/poke" {
|
||||
var mem = Memory{ .m = undefined };
|
||||
const m = try std.testing.allocator.alloc(u8, 0x10000);
|
||||
defer std.testing.allocator.free(m);
|
||||
var mem = Memory{ .m = m };
|
||||
mem.poke(u16, 0, 0xABCD);
|
||||
mem.poke(u8, 2, 0x69);
|
||||
try std.testing.expectEqual(0xABCD, mem.peek(u16, 0));
|
||||
try std.testing.expectEqual(0x69, mem.peek(u8, 2));
|
||||
}
|
||||
|
||||
const Stack = struct {
|
||||
pub const Stack = struct {
|
||||
s: [0x100]u8,
|
||||
sp: u8 = 0,
|
||||
|
||||
pub fn peek(self: *Stack, comptime T: type) T {
|
||||
std.debug.print("\x1b[1;32mstack peek:\x1b[0m sp {d}, type {s}\n", .{ self.sp, @typeName(T) });
|
||||
return switch (T) {
|
||||
u8 => self.s[self.sp -% 1],
|
||||
u16 => @as(u16, @intCast(self.s[self.sp -% 2])) << 8 | self.s[self.sp -% 1],
|
||||
switch (T) {
|
||||
u8 => {
|
||||
const val = self.s[self.sp -% 1];
|
||||
if (debug) std.debug.print("\x1b[32mstack peek:\x1b[0m\tsp {d}, type {s}, val {d}\n", .{ self.sp, @typeName(T), val });
|
||||
return val;
|
||||
},
|
||||
u16 => {
|
||||
const val = @as(u16, @intCast(self.s[self.sp -% 2])) << 8 | self.s[self.sp -% 1];
|
||||
if (debug) std.debug.print("\x1b[32mstack peek:\x1b[0m\tsp {d}, type {s}, val {d}\n", .{ self.sp, @typeName(T), val });
|
||||
return val;
|
||||
},
|
||||
else => @compileError("expected u8 or u16, got " + @typeName(T)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poke(self: *Stack, comptime T: type, v: T) void {
|
||||
std.debug.print("\x1b[1;32mstack poke:\x1b[0m sp {d}, type {s}\n", .{ self.sp, @typeName(T) });
|
||||
if (debug) std.debug.print("\x1b[32mstack poke:\x1b[0m\tsp {d}, type {s}, val {d}\n", .{ self.sp, @typeName(T), v });
|
||||
switch (T) {
|
||||
u8 => self.s[self.sp -% 1] = v,
|
||||
u16 => {
|
||||
|
@ -75,16 +115,23 @@ const Stack = struct {
|
|||
|
||||
pub fn pop(self: *Stack, comptime T: type) T {
|
||||
self.sp -%= @intCast(@divExact(@typeInfo(T).int.bits, 8));
|
||||
std.debug.print("\x1b[1;32mstack pop:\x1b[0m sp {d}, type {s}\n", .{ self.sp, @typeName(T) });
|
||||
return switch (T) {
|
||||
u8 => self.s[self.sp],
|
||||
u16 => @as(u16, @intCast(self.s[self.sp])) << 8 | self.s[self.sp +% 1],
|
||||
u8 => {
|
||||
const val = self.s[self.sp];
|
||||
if (debug) std.debug.print("\x1b[32mstack pop:\x1b[0m\tsp {d}, type {s}, val {d}\n", .{ self.sp, @typeName(T), val });
|
||||
return val;
|
||||
},
|
||||
u16 => {
|
||||
const val = @as(u16, @intCast(self.s[self.sp])) << 8 | self.s[self.sp +% 1];
|
||||
if (debug) std.debug.print("\x1b[32mstack pop:\x1b[0m\tsp {d}, type {s}, val {d}\n", .{ self.sp, @typeName(T), val });
|
||||
return val;
|
||||
},
|
||||
else => @compileError("expected u8 or u16, got " + @typeName(T)),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn push(self: *Stack, comptime T: type, v: T) void {
|
||||
std.debug.print("\x1b[1;32mstack push:\x1b[0m sp {d}, type {s}\n", .{ self.sp, @typeName(T) });
|
||||
if (debug) std.debug.print("\x1b[32mstack push:\x1b[0m\tsp {d}, type {s}, value {d}\n", .{ self.sp, @typeName(T), v });
|
||||
switch (T) {
|
||||
u8 => self.s[self.sp] = v,
|
||||
u16 => {
|
||||
|
@ -113,7 +160,7 @@ test "stack poke/peek/push/pop" {
|
|||
|
||||
pub fn eval(self: *Uxn) bool {
|
||||
switch (self.mem.m[self.pc]) {
|
||||
0x00 => return true, // BRK
|
||||
0x00 => return false, // BRK
|
||||
0x01 => inc(&self.ws, u8, false), // INC
|
||||
0x02 => pop(&self.ws, u8, false), // POP
|
||||
0x03 => nip(&self.ws, u8, false), // NIP
|
||||
|
@ -256,8 +303,7 @@ pub fn eval(self: *Uxn) bool {
|
|||
0x7F => sft(&self.rs, u16, false), // SFT2r
|
||||
|
||||
0x80 => {
|
||||
self.ws.push(u8, self.mem.m[self.pc +% 1]);
|
||||
std.debug.print("LIT: {d}\n", .{self.ws.peek(u8)});
|
||||
self.ws.push(u8, self.mem.peek(u8, self.pc +% 1));
|
||||
self.pc +%= 1;
|
||||
}, // LIT
|
||||
0x81 => inc(&self.ws, u8, true), // INCk
|
||||
|
@ -293,8 +339,7 @@ pub fn eval(self: *Uxn) bool {
|
|||
0x9F => sft(&self.ws, u8, true), // SFTk
|
||||
|
||||
0xA0 => {
|
||||
self.ws.push(u16, self.mem.m[self.pc +% 1]);
|
||||
std.debug.print("LIT2: {d}\n", .{self.ws.peek(u16)});
|
||||
self.ws.push(u16, self.mem.peek(u16, self.pc +% 1));
|
||||
self.pc +%= 2;
|
||||
}, // LIT2
|
||||
0xA1 => inc(&self.ws, u16, true), // INC2k
|
||||
|
@ -330,8 +375,7 @@ pub fn eval(self: *Uxn) bool {
|
|||
0xBF => sft(&self.ws, u16, true), // SFT2k
|
||||
|
||||
0xC0 => {
|
||||
self.rs.push(u8, self.mem.m[self.pc +% 1]);
|
||||
std.debug.print("LITr: {d}\n", .{self.rs.peek(u8)});
|
||||
self.rs.push(u8, self.mem.peek(u8, self.pc +% 1));
|
||||
self.pc +%= 1;
|
||||
}, // LITr
|
||||
0xC1 => inc(&self.rs, u8, true), // INCkr
|
||||
|
@ -367,8 +411,7 @@ pub fn eval(self: *Uxn) bool {
|
|||
0xDF => sft(&self.rs, u8, true), // SFTkr
|
||||
|
||||
0xE0 => {
|
||||
self.rs.push(u16, self.mem.m[self.pc +% 1]);
|
||||
std.debug.print("LIT2r: {d}\n", .{self.rs.peek(u16)});
|
||||
self.rs.push(u16, self.mem.peek(u16, self.pc +% 1));
|
||||
self.pc +%= 2;
|
||||
}, // LIT2r
|
||||
0xE1 => inc(&self.rs, u16, true), // INC2kr
|
||||
|
@ -405,7 +448,7 @@ pub fn eval(self: *Uxn) bool {
|
|||
}
|
||||
|
||||
self.pc +%= 1;
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
fn brk() void {}
|
||||
|
@ -565,7 +608,7 @@ fn dei(self: *Uxn, stack: *Stack, comptime T: type, comptime keep: bool) void {
|
|||
fn deo(self: *Uxn, stack: *Stack, comptime T: type, comptime keep: bool) void {
|
||||
const d = if (keep) stack.peek(u8) else stack.pop(u8);
|
||||
const v = if (keep) stack.peek(T) else stack.pop(T);
|
||||
std.debug.print("DEO: dev {X}, val {X}\n", .{ d, v });
|
||||
if (debug) std.debug.print("DEO: dev {X}, val {X}\n", .{ d, v });
|
||||
switch (T) {
|
||||
u8 => {
|
||||
self.dev[d] = v;
|
||||
|
@ -640,24 +683,30 @@ fn sft(stack: *Stack, comptime T: type, comptime keep: bool) void {
|
|||
}
|
||||
}
|
||||
|
||||
// test "arithmetic instructions" {
|
||||
// var uxn = Uxn{
|
||||
// .mem = .{ .m = undefined },
|
||||
// .ws = .{ .s = undefined },
|
||||
// .rs = .{ .s = undefined },
|
||||
// .pc = 0,
|
||||
// };
|
||||
// uxn.mem.m[0] = 0x18;
|
||||
// uxn.mem.m[1] = 0x19;
|
||||
// uxn.mem.m[2] = 0x1A;
|
||||
// uxn.mem.m[3] = 0x1B;
|
||||
// uxn.ws.push(u8, 4);
|
||||
// uxn.ws.push(u8, 20);
|
||||
// uxn.ws.push(u8, 6);
|
||||
// uxn.loop();
|
||||
// uxn.loop();
|
||||
// try std.testing.expectEqual(18, uxn.ws.pop(u8));
|
||||
// }
|
||||
test "op" {
|
||||
var uxn = try Uxn.init(std.testing.allocator);
|
||||
defer uxn.deinit(std.testing.allocator);
|
||||
|
||||
const rom = [_]u8{
|
||||
0x01, 0x01, 0x01, 0x01,
|
||||
0x00,
|
||||
};
|
||||
@memcpy(uxn.mem.m[0x100 .. rom.len + 0x100], &rom);
|
||||
|
||||
std.debug.print("op.inc\n", .{});
|
||||
uxn.ws.push(u8, 0x01);
|
||||
uxn.ws.push(u8, 0xff);
|
||||
uxn.ws.push(u8, 0xfe);
|
||||
uxn.ws.push(u8, 0x00);
|
||||
_ = uxn.eval();
|
||||
try std.testing.expectEqual(0x01, uxn.ws.pop(u8));
|
||||
_ = uxn.eval();
|
||||
try std.testing.expectEqual(0xff, uxn.ws.pop(u8));
|
||||
_ = uxn.eval();
|
||||
try std.testing.expectEqual(0x00, uxn.ws.pop(u8));
|
||||
_ = uxn.eval();
|
||||
try std.testing.expectEqual(0x02, uxn.ws.pop(u8));
|
||||
}
|
||||
|
||||
pub fn formatInstruction(
|
||||
bytes: []const u8,
|
||||
|
@ -667,9 +716,12 @@ pub fn formatInstruction(
|
|||
) !void {
|
||||
_ = fmt;
|
||||
_ = options;
|
||||
if (!debug) return;
|
||||
std.debug.assert(bytes.len == 1);
|
||||
try writer.writeAll("\x1b[1;31m");
|
||||
instr: {
|
||||
switch (bytes[0] & 0x1F) {
|
||||
0x00 => return switch (bytes[0]) {
|
||||
0x00 => break :instr switch (bytes[0]) {
|
||||
0x00 => try writer.writeAll("BRK"),
|
||||
0x20 => try writer.writeAll("JCI"),
|
||||
0x40 => try writer.writeAll("JMI"),
|
||||
|
@ -716,6 +768,8 @@ pub fn formatInstruction(
|
|||
if (bytes[0] & 0x20 == 0x20) try writer.writeByte('2');
|
||||
if (bytes[0] & 0x40 == 0x40) try writer.writeByte('k');
|
||||
if (bytes[0] & 0x80 == 0x80) try writer.writeByte('r');
|
||||
}
|
||||
try writer.writeAll("\x1b[0m");
|
||||
}
|
||||
|
||||
pub fn fmtInstrs(bytes: []const u8) std.fmt.Formatter(formatInstruction) {
|
||||
|
|
|
@ -5,36 +5,57 @@ const Varvara = @This();
|
|||
|
||||
uxn: Uxn,
|
||||
|
||||
pub fn init(rom: []const u8) Varvara {
|
||||
var uxn = Uxn{ .pc = 0x100 };
|
||||
allocator: std.mem.Allocator,
|
||||
metadata: ?[]const u8 = null,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, rom: []const u8) !Varvara {
|
||||
var uxn = try Uxn.init(allocator);
|
||||
uxn.deo8 = deo8;
|
||||
uxn.deo16 = deo16;
|
||||
uxn.dei8 = dei8;
|
||||
uxn.dei16 = dei16;
|
||||
@memcpy(uxn.mem.m[0x100 .. rom.len + 0x100], rom);
|
||||
return .{ .uxn = uxn };
|
||||
return .{ .uxn = uxn, .allocator = allocator };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Varvara) void {
|
||||
self.uxn.deinit(self.allocator);
|
||||
}
|
||||
|
||||
pub fn deo8(uxn: *Uxn, stack: *Uxn.Stack, device: u8, value: u8) void {
|
||||
const self: Varvara = @fieldParentPtr("uxn", uxn);
|
||||
// _ = .{ self, stack, device, value };
|
||||
_ = self;
|
||||
_ = stack;
|
||||
switch (device) {
|
||||
const self: *Varvara = @fieldParentPtr("uxn", uxn);
|
||||
_ = .{ self, stack, device, value };
|
||||
switch (device) { // Console/write
|
||||
0x18 => {
|
||||
std.io.getStdOut().writer().writeByte(value) catch |e| @panic(e);
|
||||
std.io.getStdOut().writer().writeByte(value) catch |e| @panic(@errorName(e));
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deo16(uxn: *Uxn, stack: *Uxn.Stack, device: u8, value: u16) void {
|
||||
const self: Varvara = @fieldParentPtr("uxn", uxn);
|
||||
const self: *Varvara = @fieldParentPtr("uxn", uxn);
|
||||
_ = .{ self, stack, device, value };
|
||||
switch (device) {
|
||||
0x06, 0x07 => { // System/metadata
|
||||
self.metadata = std.mem.span(@as([*:0]const u8, @ptrCast(&uxn.mem.m[value +% 1])));
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dei8(uxn: *Uxn, stack: *Uxn.Stack, device: u8, value: u8) void {
|
||||
const self: Varvara = @fieldParentPtr("uxn", uxn);
|
||||
_ = .{ self, stack, device, value };
|
||||
pub fn dei8(uxn: *Uxn, stack: *Uxn.Stack, device: u8) void {
|
||||
const self: *Varvara = @fieldParentPtr("uxn", uxn);
|
||||
_ = .{ self, stack, device };
|
||||
switch (device) {
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dei16(uxn: *Uxn, stack: *Uxn.Stack, device: u8, value: u16) void {
|
||||
const self: Varvara = @fieldParentPtr("uxn", uxn);
|
||||
_ = .{ self, stack, device, value };
|
||||
pub fn dei16(uxn: *Uxn, stack: *Uxn.Stack, device: u8) void {
|
||||
const self: *Varvara = @fieldParentPtr("uxn", uxn);
|
||||
_ = .{ self, stack, device };
|
||||
switch (device) {
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue