Skip to content

Commit 1842e31

Browse files
committed
feat: allows running unit tests against benchmark emulator
1 parent f3cd96d commit 1842e31

File tree

3 files changed

+187
-6
lines changed

3 files changed

+187
-6
lines changed

build.zig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ pub fn build(b: *std.Build) void {
1616

1717
const unit_exe = b.addExecutable(.{
1818
.name = "zig80-test",
19-
.root_source_file = b.path("src/test/unit.zig"),
19+
.root_source_file = b.path("src/unit.zig"),
2020
.target = target,
2121
.optimize = opt,
2222
});
2323

24+
unit_exe.linkSystemLibrary("z80");
2425
b.installArtifact(unit_exe);
2526
}

src/unit.zig

Lines changed: 181 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,72 @@
11
const std = @import("std");
22
const cpu_import = @import("cpu.zig");
33
const utils = @import("utils.zig");
4+
const cli = @import("cli.zig");
5+
const c = @import("Z80.zig");
6+
47
const Allocator = std.mem.Allocator;
8+
const Z80 = cpu_import.Z80;
9+
const Flag = cpu_import.Flag;
10+
11+
const Options = struct {
12+
benchmark: bool,
13+
};
514

615
pub fn main() !void {
716
var gpa = std.heap.GeneralPurposeAllocator(.{ .safety = true }){};
817
defer _ = gpa.deinit();
918

1019
const alloc = gpa.allocator();
1120

21+
const args = try std.process.argsAlloc(alloc);
22+
defer std.process.argsFree(alloc, args);
23+
24+
var options = Options{ .benchmark = true };
25+
if (!try cli.parse(args, Options, &options)) {
26+
return;
27+
}
28+
1229
const tests = try loadTests(alloc);
1330
defer tests.deinit();
1431

1532
const results = try loadTestResults(alloc);
1633
defer results.deinit();
1734

18-
var cpu = cpu_import.Z80.init(alloc);
35+
if (options.benchmark) {
36+
var cpu = c.Z80{};
37+
for (tests.value, 0..) |t, n| {
38+
var memory = [_]u8{0} ** 0x10000;
39+
loadBench(&cpu, t, &memory);
40+
std.debug.print("Running test '{s}'...\n", .{t.name});
41+
42+
const read = struct {
43+
fn read(context: ?*anyopaque, address: c_ushort) callconv(.C) u8 {
44+
const mem: *[0x10000]u8 = @ptrCast(@alignCast(context.?));
45+
return mem[address];
46+
}
47+
}.read;
48+
49+
const write = struct {
50+
fn write(context: ?*anyopaque, address: c_ushort, value: u8) callconv(.C) void {
51+
const mem: *[0x10000]u8 = @ptrCast(@alignCast(context.?));
52+
mem[address] = value;
53+
}
54+
}.write;
55+
56+
cpu.read = read;
57+
cpu.fetch = read;
58+
cpu.write = write;
59+
cpu.fetch_opcode = read;
60+
cpu.context = &memory;
61+
_ = c.z80_run(&cpu, 1);
62+
63+
compareBenchResult(&cpu, results.value[n], &memory);
64+
}
65+
// runBenchmark(alloc, tests.value, results.value);
66+
return;
67+
}
68+
69+
var cpu = Z80.init(alloc);
1970
for (tests.value, 0..) |t, n| {
2071
std.debug.print("Running test '{s}'...\n", .{t.name});
2172
loadTest(&cpu, t);
@@ -27,11 +78,136 @@ pub fn main() !void {
2778
}
2879
}
2980

30-
fn compareResult(cpu: *cpu_import.Z80, result: TestResult) void {
81+
fn runBenchmark(alloc: Allocator, tests: []TestCase, results: []TestResult) void {
82+
_ = alloc;
83+
var cpu = c.Z80{};
84+
for (tests, 0..) |t, n| {
85+
var memory = [_]u8{0} ** 0x10000;
86+
std.debug.print("Running test '{s}'...\n", .{t.name});
87+
loadBench(&cpu, t, &memory);
88+
_ = c.z80_run(&cpu, t.state.tStates);
89+
90+
compareBenchResult(&cpu, results[n], &memory);
91+
}
92+
}
93+
94+
fn compareBenchResult(cpu: *c.Z80, result: TestResult, memory: *[0x10000]u8) void {
95+
_ = memory;
96+
if (result.state.af != cpu.af.uint16_value) {
97+
std.debug.print("AF mismatch: expected 0x{X:0>4}, got 0x{X:0>4}\n", .{ result.state.af, cpu.af.uint16_value });
98+
var expected_flag = Flag.init(utils.lo(result.state.afDash));
99+
var actual_flag = Flag.init(utils.lo(cpu.af.uint16_value));
100+
std.debug.print(" expected: ", .{});
101+
expected_flag.dump();
102+
std.debug.print(" actual: ", .{});
103+
actual_flag.dump();
104+
}
105+
106+
if (result.state.bc != cpu.bc.uint16_value) {
107+
std.debug.print("BC mismatch: expected 0x{X:0>4}, got 0x{X:0>4}\n", .{ result.state.bc, cpu.bc.uint16_value });
108+
}
109+
110+
if (result.state.de != cpu.de.uint16_value) {
111+
std.debug.print("DE mismatch: expected 0x{X:0>4}, got 0x{X:0>4}\n", .{ result.state.de, cpu.de.uint16_value });
112+
}
113+
114+
if (result.state.hl != cpu.hl.uint16_value) {
115+
std.debug.print("HL mismatch: expected 0x{X:0>4}, got 0x{X:0>4}\n", .{ result.state.hl, cpu.hl.uint16_value });
116+
}
117+
118+
if (result.state.afDash != cpu.af_.uint16_value) {
119+
std.debug.print("AF' mismatch: expected 0x{X:0>4}, got 0x{X:0>4}\n", .{ result.state.afDash, cpu.af_.uint16_value });
120+
}
121+
122+
if (result.state.bcDash != cpu.bc_.uint16_value) {
123+
std.debug.print("BC' mismatch: expected 0x{X:0>4}, got 0x{X:0>4}\n", .{ result.state.bcDash, cpu.bc_.uint16_value });
124+
}
125+
126+
if (result.state.deDash != cpu.de_.uint16_value) {
127+
std.debug.print("DE' mismatch: expected 0x{X:0>4}, got 0x{X:0>4}\n", .{ result.state.deDash, cpu.de_.uint16_value });
128+
}
129+
130+
if (result.state.hlDash != cpu.hl_.uint16_value) {
131+
std.debug.print("HL' mismatch: expected 0x{X:0>4}, got 0x{X:0>4}\n", .{ result.state.hlDash, cpu.hl_.uint16_value });
132+
}
133+
134+
// if (result.state.ix != cpu.ix.uint16_value) {
135+
// std.debug.print("IX mismatch: expected 0x{X:0>4}, got 0x{X:0>4}\n", .{ result.state.ix, cpu.ix.uint16_value });
136+
// }
137+
//
138+
// if (result.state.iy != cpu.iy.uint16_value) {
139+
// std.debug.print("IY mismatch: expected 0x{X:0>4}, got 0x{X:0>4}\n", .{ result.state.iy, cpu.iy.uint16_value });
140+
// }
141+
142+
if (result.state.sp != cpu.sp.uint16_value) {
143+
std.debug.print("SP mismatch: expected 0x{X:0>4}, got 0x{X:0>4}\n", .{ result.state.sp, cpu.sp.uint16_value });
144+
}
145+
146+
if (result.state.pc != cpu.pc.uint16_value) {
147+
std.debug.print("PC mismatch: expected 0x{X:0>4}, got 0x{X:0>4}\n", .{ result.state.pc, cpu.pc.uint16_value });
148+
}
149+
150+
if (result.state.i != cpu.i) {
151+
std.debug.print("I mismatch: expected 0x{X:0>4}, got 0x{X:0>4}\n", .{ result.state.i, cpu.i });
152+
}
153+
154+
if (result.state.r != cpu.r) {
155+
std.debug.print("R mismatch: expected 0x{X:0>4}, got 0x{X:0>4}\n", .{ result.state.r, cpu.r });
156+
}
157+
158+
// if (result.state.iff1 != cpu.iff1) {
159+
// std.debug.print("IFF1 mismatch: expected {}, got {}\n", .{ result.state.iff1, cpu.iff1 });
160+
// }
161+
//
162+
// if (result.state.iff2 != cpu.iff2) {
163+
// std.debug.print("IFF2 mismatch: expected {}, got {}\n", .{ result.state.iff2, cpu.iff2 });
164+
// }
165+
//
166+
// if (result.state.im != cpu.im) {
167+
// std.debug.print("IM mismatch: expected 0x{X:0>4}, got 0x{X:0>4}\n", .{ result.state.im, cpu.im });
168+
// }
169+
//
170+
// if (result.state.halted != cpu.halt_line) {
171+
// std.debug.print("Halted mismatch: expected {}, got {}\n", .{ result.state.halted, cpu.halt_line });
172+
// }
173+
//
174+
// if (result.state.tStates != cpu.cycles - 1) {
175+
// std.debug.print("TStates mismatch: expected {d}, got {d}\n", .{ result.state.tStates, cpu.cycles - 1 });
176+
// }
177+
//
178+
}
179+
180+
fn loadBench(cpu: *c.Z80, t: TestCase, memory: *[0x10000]u8) void {
181+
cpu.af.uint16_value = t.state.af;
182+
cpu.bc.uint16_value = t.state.bc;
183+
cpu.de.uint16_value = t.state.de;
184+
cpu.hl.uint16_value = t.state.hl;
185+
cpu.af_.uint16_value = t.state.afDash;
186+
cpu.bc_.uint16_value = t.state.bcDash;
187+
cpu.de_.uint16_value = t.state.deDash;
188+
cpu.hl_.uint16_value = t.state.hlDash;
189+
// cpu.ix.uint16_value = t.state.ix;
190+
// cpu.iy.uint16_value = t.state.iy;
191+
cpu.sp.uint16_value = t.state.sp;
192+
cpu.pc.uint16_value = t.state.pc;
193+
// cpu.memptr = t.state.memptr;
194+
cpu.i = t.state.i;
195+
cpu.r = t.state.r;
196+
cpu.iff1 = if (t.state.iff1) 1 else 0;
197+
cpu.iff2 = if (t.state.iff2) 1 else 0;
198+
cpu.im = t.state.im;
199+
cpu.halt_line = if (t.state.halted) 1 else 0;
200+
201+
for (t.memory) |m| {
202+
@memcpy(memory[m.address .. m.address + m.data.len], m.data);
203+
}
204+
}
205+
206+
fn compareResult(cpu: *Z80, result: TestResult) void {
31207
if (result.state.af != cpu.af) {
32208
std.debug.print("AF mismatch: expected 0x{X:0>4}, got 0x{X:0>4}\n", .{ result.state.af, cpu.af });
33-
var expected_flag = cpu_import.Flag.init(utils.lo(result.state.afDash));
34-
var actual_flag = cpu_import.Flag.init(utils.lo(cpu.getF()));
209+
var expected_flag = Flag.init(utils.lo(result.state.afDash));
210+
var actual_flag = Flag.init(utils.lo(cpu.getF()));
35211
std.debug.print(" expected: ", .{});
36212
expected_flag.dump();
37213
std.debug.print(" actual: ", .{});
@@ -148,7 +324,7 @@ fn loadTests(alloc: Allocator) !std.json.Parsed([]TestCase) {
148324
return tests;
149325
}
150326

151-
fn loadTest(cpu: *cpu_import.Z80, t: TestCase) void {
327+
fn loadTest(cpu: *Z80, t: TestCase) void {
152328
cpu.reset();
153329
cpu.af = t.state.af;
154330
cpu.bc = t.state.bc;

src/utils.zig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,7 @@ pub fn hi(value: u16) u8 {
169169
pub fn lo(value: u16) u8 {
170170
return @as(u8, @intCast(value & 0xFF));
171171
}
172+
173+
pub fn u16FromBytes(low: u8, high: u8) u16 {
174+
return @as(u16, @intCast(high)) << 8 | @as(u16, @intCast(low));
175+
}

0 commit comments

Comments
 (0)