improve debugging, fix a lot, add basic op test, heap allocation, dear god

This commit is contained in:
Jeeves 2025-03-05 02:56:41 -07:00
parent 24f5fd49fa
commit 6d2cb973d1
3 changed files with 193 additions and 115 deletions

View file

@ -3,6 +3,8 @@ const std = @import("std");
const Uxn = @import("uxn.zig"); const Uxn = @import("uxn.zig");
const Varvara = @import("varvara.zig"); const Varvara = @import("varvara.zig");
const DEBUG = true;
pub fn main() !void { pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit(); defer _ = gpa.deinit();
@ -19,16 +21,17 @@ pub fn main() !void {
const rom = try file.readToEndAlloc(allocator, 0xFF00); const rom = try file.readToEndAlloc(allocator, 0xFF00);
defer allocator.free(rom); defer allocator.free(rom);
var varvara = Varvara.init(rom); Uxn.setDebug(DEBUG);
var running = true; var varvara = try Varvara.init(allocator, rom);
while (running) { defer varvara.deinit();
std.debug.print("pc={X} code={X} op={s}\n", .{
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.pc,
varvara.uxn.mem.m[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; } else return error.NoRom;
} }

View file

@ -1,11 +1,17 @@
const std = @import("std"); const std = @import("std");
const Uxn = @This(); const Uxn = @This();
var debug = true;
pub fn setDebug(v: bool) void {
debug = v;
}
mem: Memory = .{ .m = undefined }, mem: Memory = .{ .m = undefined },
ws: Stack = .{ .s = undefined }, ws: Stack = .{ .s = undefined },
rs: Stack = .{ .s = undefined }, rs: Stack = .{ .s = undefined },
dev: [0x100]u8 = undefined, dev: [0x100]u8 = undefined,
pc: u16, pc: u16 = 0x100,
deo8: *const fn (*Uxn, *Stack, u8, u8) void = deo8Stub, deo8: *const fn (*Uxn, *Stack, u8, u8) void = deo8Stub,
deo16: *const fn (*Uxn, *Stack, u8, u16) void = deo16Stub, 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 dei8Stub(_: *Uxn, _: *Stack, _: u8) void {}
fn dei16Stub(_: *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 { const Memory = struct {
m: [0x10000]u8, m: []u8,
pub fn peek(self: *Memory, comptime T: type, idx: u16) T { pub fn peek(self: *Memory, comptime T: type, idx: u16) T {
return switch (T) { switch (T) {
u8 => self.m[idx], u8 => {
u16 => @as(u16, @intCast(self.m[idx])) << 8 | self.m[idx +% 1], 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)), else => @compileError("expected u8 or u16, got " + @typeName(T)),
}; }
} }
pub fn poke(self: *Memory, comptime T: type, idx: u16, val: T) void { 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) { switch (T) {
u8 => self.m[idx] = val, u8 => self.m[idx] = val,
u16 => { u16 => {
@ -41,28 +72,37 @@ const Memory = struct {
}; };
test "memory peek/poke" { 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(u16, 0, 0xABCD);
mem.poke(u8, 2, 0x69); mem.poke(u8, 2, 0x69);
try std.testing.expectEqual(0xABCD, mem.peek(u16, 0)); try std.testing.expectEqual(0xABCD, mem.peek(u16, 0));
try std.testing.expectEqual(0x69, mem.peek(u8, 2)); try std.testing.expectEqual(0x69, mem.peek(u8, 2));
} }
const Stack = struct { pub const Stack = struct {
s: [0x100]u8, s: [0x100]u8,
sp: u8 = 0, sp: u8 = 0,
pub fn peek(self: *Stack, comptime T: type) T { 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) }); switch (T) {
return switch (T) { u8 => {
u8 => self.s[self.sp -% 1], const val = self.s[self.sp -% 1];
u16 => @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;
},
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)), else => @compileError("expected u8 or u16, got " + @typeName(T)),
}; }
} }
pub fn poke(self: *Stack, comptime T: type, v: T) void { 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) { switch (T) {
u8 => self.s[self.sp -% 1] = v, u8 => self.s[self.sp -% 1] = v,
u16 => { u16 => {
@ -75,16 +115,23 @@ const Stack = struct {
pub fn pop(self: *Stack, comptime T: type) T { pub fn pop(self: *Stack, comptime T: type) T {
self.sp -%= @intCast(@divExact(@typeInfo(T).int.bits, 8)); 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) { return switch (T) {
u8 => self.s[self.sp], u8 => {
u16 => @as(u16, @intCast(self.s[self.sp])) << 8 | self.s[self.sp +% 1], 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)), else => @compileError("expected u8 or u16, got " + @typeName(T)),
}; };
} }
pub fn push(self: *Stack, comptime T: type, v: T) void { 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) { switch (T) {
u8 => self.s[self.sp] = v, u8 => self.s[self.sp] = v,
u16 => { u16 => {
@ -113,7 +160,7 @@ test "stack poke/peek/push/pop" {
pub fn eval(self: *Uxn) bool { pub fn eval(self: *Uxn) bool {
switch (self.mem.m[self.pc]) { switch (self.mem.m[self.pc]) {
0x00 => return true, // BRK 0x00 => return false, // BRK
0x01 => inc(&self.ws, u8, false), // INC 0x01 => inc(&self.ws, u8, false), // INC
0x02 => pop(&self.ws, u8, false), // POP 0x02 => pop(&self.ws, u8, false), // POP
0x03 => nip(&self.ws, u8, false), // NIP 0x03 => nip(&self.ws, u8, false), // NIP
@ -256,8 +303,7 @@ pub fn eval(self: *Uxn) bool {
0x7F => sft(&self.rs, u16, false), // SFT2r 0x7F => sft(&self.rs, u16, false), // SFT2r
0x80 => { 0x80 => {
self.ws.push(u8, self.mem.m[self.pc +% 1]); self.ws.push(u8, self.mem.peek(u8, self.pc +% 1));
std.debug.print("LIT: {d}\n", .{self.ws.peek(u8)});
self.pc +%= 1; self.pc +%= 1;
}, // LIT }, // LIT
0x81 => inc(&self.ws, u8, true), // INCk 0x81 => inc(&self.ws, u8, true), // INCk
@ -293,8 +339,7 @@ pub fn eval(self: *Uxn) bool {
0x9F => sft(&self.ws, u8, true), // SFTk 0x9F => sft(&self.ws, u8, true), // SFTk
0xA0 => { 0xA0 => {
self.ws.push(u16, self.mem.m[self.pc +% 1]); self.ws.push(u16, self.mem.peek(u16, self.pc +% 1));
std.debug.print("LIT2: {d}\n", .{self.ws.peek(u16)});
self.pc +%= 2; self.pc +%= 2;
}, // LIT2 }, // LIT2
0xA1 => inc(&self.ws, u16, true), // INC2k 0xA1 => inc(&self.ws, u16, true), // INC2k
@ -330,8 +375,7 @@ pub fn eval(self: *Uxn) bool {
0xBF => sft(&self.ws, u16, true), // SFT2k 0xBF => sft(&self.ws, u16, true), // SFT2k
0xC0 => { 0xC0 => {
self.rs.push(u8, self.mem.m[self.pc +% 1]); self.rs.push(u8, self.mem.peek(u8, self.pc +% 1));
std.debug.print("LITr: {d}\n", .{self.rs.peek(u8)});
self.pc +%= 1; self.pc +%= 1;
}, // LITr }, // LITr
0xC1 => inc(&self.rs, u8, true), // INCkr 0xC1 => inc(&self.rs, u8, true), // INCkr
@ -367,8 +411,7 @@ pub fn eval(self: *Uxn) bool {
0xDF => sft(&self.rs, u8, true), // SFTkr 0xDF => sft(&self.rs, u8, true), // SFTkr
0xE0 => { 0xE0 => {
self.rs.push(u16, self.mem.m[self.pc +% 1]); self.rs.push(u16, self.mem.peek(u16, self.pc +% 1));
std.debug.print("LIT2r: {d}\n", .{self.rs.peek(u16)});
self.pc +%= 2; self.pc +%= 2;
}, // LIT2r }, // LIT2r
0xE1 => inc(&self.rs, u16, true), // INC2kr 0xE1 => inc(&self.rs, u16, true), // INC2kr
@ -405,7 +448,7 @@ pub fn eval(self: *Uxn) bool {
} }
self.pc +%= 1; self.pc +%= 1;
return false; return true;
} }
fn brk() void {} 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 { 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 d = if (keep) stack.peek(u8) else stack.pop(u8);
const v = if (keep) stack.peek(T) else stack.pop(T); 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) { switch (T) {
u8 => { u8 => {
self.dev[d] = v; self.dev[d] = v;
@ -640,24 +683,30 @@ fn sft(stack: *Stack, comptime T: type, comptime keep: bool) void {
} }
} }
// test "arithmetic instructions" { test "op" {
// var uxn = Uxn{ var uxn = try Uxn.init(std.testing.allocator);
// .mem = .{ .m = undefined }, defer uxn.deinit(std.testing.allocator);
// .ws = .{ .s = undefined },
// .rs = .{ .s = undefined }, const rom = [_]u8{
// .pc = 0, 0x01, 0x01, 0x01, 0x01,
// }; 0x00,
// uxn.mem.m[0] = 0x18; };
// uxn.mem.m[1] = 0x19; @memcpy(uxn.mem.m[0x100 .. rom.len + 0x100], &rom);
// uxn.mem.m[2] = 0x1A;
// uxn.mem.m[3] = 0x1B; std.debug.print("op.inc\n", .{});
// uxn.ws.push(u8, 4); uxn.ws.push(u8, 0x01);
// uxn.ws.push(u8, 20); uxn.ws.push(u8, 0xff);
// uxn.ws.push(u8, 6); uxn.ws.push(u8, 0xfe);
// uxn.loop(); uxn.ws.push(u8, 0x00);
// uxn.loop(); _ = uxn.eval();
// try std.testing.expectEqual(18, uxn.ws.pop(u8)); 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( pub fn formatInstruction(
bytes: []const u8, bytes: []const u8,
@ -667,55 +716,60 @@ pub fn formatInstruction(
) !void { ) !void {
_ = fmt; _ = fmt;
_ = options; _ = options;
if (!debug) return;
std.debug.assert(bytes.len == 1); std.debug.assert(bytes.len == 1);
switch (bytes[0] & 0x1F) { try writer.writeAll("\x1b[1;31m");
0x00 => return switch (bytes[0]) { instr: {
0x00 => try writer.writeAll("BRK"), switch (bytes[0] & 0x1F) {
0x20 => try writer.writeAll("JCI"), 0x00 => break :instr switch (bytes[0]) {
0x40 => try writer.writeAll("JMI"), 0x00 => try writer.writeAll("BRK"),
0x60 => try writer.writeAll("JSI"), 0x20 => try writer.writeAll("JCI"),
0x80 => try writer.writeAll("LIT"), 0x40 => try writer.writeAll("JMI"),
0xA0 => try writer.writeAll("LIT2"), 0x60 => try writer.writeAll("JSI"),
0xC0 => try writer.writeAll("LITr"), 0x80 => try writer.writeAll("LIT"),
0xE0 => try writer.writeAll("LIT2r"), 0xA0 => try writer.writeAll("LIT2"),
0xC0 => try writer.writeAll("LITr"),
0xE0 => try writer.writeAll("LIT2r"),
else => unreachable,
},
0x01 => try writer.writeAll("INC"),
0x02 => try writer.writeAll("POP"),
0x03 => try writer.writeAll("NIP"),
0x04 => try writer.writeAll("SWP"),
0x05 => try writer.writeAll("ROT"),
0x06 => try writer.writeAll("DUP"),
0x07 => try writer.writeAll("OVR"),
0x08 => try writer.writeAll("EQU"),
0x09 => try writer.writeAll("NEQ"),
0x0A => try writer.writeAll("GTH"),
0x0B => try writer.writeAll("LTH"),
0x0C => try writer.writeAll("JMP"),
0x0D => try writer.writeAll("JCN"),
0x0E => try writer.writeAll("JSR"),
0x0F => try writer.writeAll("STH"),
0x10 => try writer.writeAll("LDZ"),
0x11 => try writer.writeAll("STZ"),
0x12 => try writer.writeAll("LDR"),
0x13 => try writer.writeAll("STR"),
0x14 => try writer.writeAll("LDA"),
0x15 => try writer.writeAll("STA"),
0x16 => try writer.writeAll("DEI"),
0x17 => try writer.writeAll("DEO"),
0x18 => try writer.writeAll("ADD"),
0x19 => try writer.writeAll("SUB"),
0x1A => try writer.writeAll("MUL"),
0x1B => try writer.writeAll("DIV"),
0x1C => try writer.writeAll("AND"),
0x1D => try writer.writeAll("ORA"),
0x1E => try writer.writeAll("EOR"),
0x1F => try writer.writeAll("SFT"),
else => unreachable, else => unreachable,
}, }
0x01 => try writer.writeAll("INC"), if (bytes[0] & 0x20 == 0x20) try writer.writeByte('2');
0x02 => try writer.writeAll("POP"), if (bytes[0] & 0x40 == 0x40) try writer.writeByte('k');
0x03 => try writer.writeAll("NIP"), if (bytes[0] & 0x80 == 0x80) try writer.writeByte('r');
0x04 => try writer.writeAll("SWP"),
0x05 => try writer.writeAll("ROT"),
0x06 => try writer.writeAll("DUP"),
0x07 => try writer.writeAll("OVR"),
0x08 => try writer.writeAll("EQU"),
0x09 => try writer.writeAll("NEQ"),
0x0A => try writer.writeAll("GTH"),
0x0B => try writer.writeAll("LTH"),
0x0C => try writer.writeAll("JMP"),
0x0D => try writer.writeAll("JCN"),
0x0E => try writer.writeAll("JSR"),
0x0F => try writer.writeAll("STH"),
0x10 => try writer.writeAll("LDZ"),
0x11 => try writer.writeAll("STZ"),
0x12 => try writer.writeAll("LDR"),
0x13 => try writer.writeAll("STR"),
0x14 => try writer.writeAll("LDA"),
0x15 => try writer.writeAll("STA"),
0x16 => try writer.writeAll("DEI"),
0x17 => try writer.writeAll("DEO"),
0x18 => try writer.writeAll("ADD"),
0x19 => try writer.writeAll("SUB"),
0x1A => try writer.writeAll("MUL"),
0x1B => try writer.writeAll("DIV"),
0x1C => try writer.writeAll("AND"),
0x1D => try writer.writeAll("ORA"),
0x1E => try writer.writeAll("EOR"),
0x1F => try writer.writeAll("SFT"),
else => unreachable,
} }
if (bytes[0] & 0x20 == 0x20) try writer.writeByte('2'); try writer.writeAll("\x1b[0m");
if (bytes[0] & 0x40 == 0x40) try writer.writeByte('k');
if (bytes[0] & 0x80 == 0x80) try writer.writeByte('r');
} }
pub fn fmtInstrs(bytes: []const u8) std.fmt.Formatter(formatInstruction) { pub fn fmtInstrs(bytes: []const u8) std.fmt.Formatter(formatInstruction) {

View file

@ -5,36 +5,57 @@ const Varvara = @This();
uxn: Uxn, uxn: Uxn,
pub fn init(rom: []const u8) Varvara { allocator: std.mem.Allocator,
var uxn = Uxn{ .pc = 0x100 }; 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); @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 { pub fn deo8(uxn: *Uxn, stack: *Uxn.Stack, device: u8, value: u8) void {
const self: Varvara = @fieldParentPtr("uxn", uxn); const self: *Varvara = @fieldParentPtr("uxn", uxn);
// _ = .{ self, stack, device, value }; _ = .{ self, stack, device, value };
_ = self; switch (device) { // Console/write
_ = stack;
switch (device) {
0x18 => { 0x18 => {
std.io.getStdOut().writer().writeByte(value) catch |e| @panic(e); std.io.getStdOut().writer().writeByte(value) catch |e| @panic(@errorName(e));
}, },
else => {}, else => {},
} }
} }
pub fn deo16(uxn: *Uxn, stack: *Uxn.Stack, device: u8, value: u16) void { 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 }; _ = .{ 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 { pub fn dei8(uxn: *Uxn, stack: *Uxn.Stack, device: u8) void {
const self: Varvara = @fieldParentPtr("uxn", uxn); const self: *Varvara = @fieldParentPtr("uxn", uxn);
_ = .{ self, stack, device, value }; _ = .{ self, stack, device };
switch (device) {
else => {},
}
} }
pub fn dei16(uxn: *Uxn, stack: *Uxn.Stack, device: u8, value: u16) void { pub fn dei16(uxn: *Uxn, stack: *Uxn.Stack, device: u8) void {
const self: Varvara = @fieldParentPtr("uxn", uxn); const self: *Varvara = @fieldParentPtr("uxn", uxn);
_ = .{ self, stack, device, value }; _ = .{ self, stack, device };
switch (device) {
else => {},
}
} }