Skip to content

Commit 8f8254c

Browse files
committed
c64: benchmarks
Note: tried, use `std.crypto.aes`, but not working for mos target, replaced to tiny-AES-c port
1 parent 1c7150d commit 8f8254c

8 files changed

Lines changed: 553 additions & 0 deletions

File tree

build.zig

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,64 @@ pub fn build(b: *std.Build) void {
547547
run_bininfo.addFileArg(exe.getEmittedBin());
548548
}
549549

550+
// ---- C64 benchmarks ----
551+
{
552+
const step = b.step("c64-bench-crc8", "Build C64 CRC-8/GSM-A benchmark (C64 Kernal)");
553+
const exe = addC64Exe(b, sdk_dep, sdk_src, sdk_libs.c64 orelse @panic("c64 libs not built"), optimize, "c64-bench-crc8", "c64/bench-crc8/bench-crc8.zig", false);
554+
const install = b.addInstallArtifact(exe, .{ .dest_sub_path = "c64-bench-crc8.prg" });
555+
step.dependOn(&install.step);
556+
b.getInstallStep().dependOn(&install.step);
557+
run_bininfo.addFileArg(exe.getEmittedBin());
558+
}
559+
{
560+
const step = b.step("c64-bench-crc16", "Build C64 CRC-16/XMODEM benchmark (C64 Kernal)");
561+
const exe = addC64Exe(b, sdk_dep, sdk_src, sdk_libs.c64 orelse @panic("c64 libs not built"), optimize, "c64-bench-crc16", "c64/bench-crc16/bench-crc16.zig", false);
562+
const install = b.addInstallArtifact(exe, .{ .dest_sub_path = "c64-bench-crc16.prg" });
563+
step.dependOn(&install.step);
564+
b.getInstallStep().dependOn(&install.step);
565+
run_bininfo.addFileArg(exe.getEmittedBin());
566+
}
567+
{
568+
const step = b.step("c64-bench-crc32", "Build C64 CRC-32/CKSUM benchmark (C64 Kernal)");
569+
const exe = addC64Exe(b, sdk_dep, sdk_src, sdk_libs.c64 orelse @panic("c64 libs not built"), optimize, "c64-bench-crc32", "c64/bench-crc32/bench-crc32.zig", false);
570+
const install = b.addInstallArtifact(exe, .{ .dest_sub_path = "c64-bench-crc32.prg" });
571+
step.dependOn(&install.step);
572+
b.getInstallStep().dependOn(&install.step);
573+
run_bininfo.addFileArg(exe.getEmittedBin());
574+
}
575+
{
576+
const step = b.step("c64-bench-aes256", "Build C64 AES-256-CBC benchmark");
577+
const exe = addC64Exe(b, sdk_dep, sdk_src, sdk_libs.c64 orelse @panic("c64 libs not built"), optimize, "c64-bench-aes256", "c64/bench-aes256/bench-aes256.zig", false);
578+
const install = b.addInstallArtifact(exe, .{ .dest_sub_path = "c64-bench-aes256.prg" });
579+
step.dependOn(&install.step);
580+
b.getInstallStep().dependOn(&install.step);
581+
run_bininfo.addFileArg(exe.getEmittedBin());
582+
}
583+
{
584+
const step = b.step("c64-bench-sieve", "Build C64 Sieve of Eratosthenes benchmark");
585+
const exe = addC64Exe(b, sdk_dep, sdk_src, sdk_libs.c64 orelse @panic("c64 libs not built"), optimize, "c64-bench-sieve", "c64/bench-sieve/bench-sieve.zig", false);
586+
const install = b.addInstallArtifact(exe, .{ .dest_sub_path = "c64-bench-sieve.prg" });
587+
step.dependOn(&install.step);
588+
b.getInstallStep().dependOn(&install.step);
589+
run_bininfo.addFileArg(exe.getEmittedBin());
590+
}
591+
{
592+
const step = b.step("c64-bench-pi", "Build C64 pi-digits benchmark (Spigot algorithm)");
593+
const exe = addC64Exe(b, sdk_dep, sdk_src, sdk_libs.c64 orelse @panic("c64 libs not built"), optimize, "c64-bench-pi", "c64/bench-pi/bench-pi.zig", false);
594+
const install = b.addInstallArtifact(exe, .{ .dest_sub_path = "c64-bench-pi.prg" });
595+
step.dependOn(&install.step);
596+
b.getInstallStep().dependOn(&install.step);
597+
run_bininfo.addFileArg(exe.getEmittedBin());
598+
}
599+
{
600+
const step = b.step("c64-bench-fact", "Build C64 factorial benchmark (1000 iterations)");
601+
const exe = addC64Exe(b, sdk_dep, sdk_src, sdk_libs.c64 orelse @panic("c64 libs not built"), optimize, "c64-bench-fact", "c64/bench-fact/bench-fact.zig", false);
602+
const install = b.addInstallArtifact(exe, .{ .dest_sub_path = "c64-bench-fact.prg" });
603+
step.dependOn(&install.step);
604+
b.getInstallStep().dependOn(&install.step);
605+
run_bininfo.addFileArg(exe.getEmittedBin());
606+
}
607+
550608
// ---- GEOS CBM hello ----
551609
{
552610
const step = b.step("geos-hello", "Build GEOS CBM hello example");

c64/bench-aes256/bench-aes256.zig

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
// Copyright (c) 2024 Matheus C. França
2+
// SPDX-License-Identifier: Apache-2.0
3+
//! C64 AES-256-CBC benchmark: encrypts the C64 Kernal ROM ($E000–$FFFF) with AES-256-CBC,
4+
//! then verifies the result with CRC-32/CKSUM. Pure Zig port of tiny-AES-c (public domain).
5+
pub const panic = @import("mos_panic");
6+
const std = @import("std");
7+
8+
const Crc32Cksum = std.hash.crc.Crc32Cksum;
9+
10+
// AES-256: Nk=8 key words, Nr=14 rounds, 240-byte expanded key.
11+
const Nk: usize = 8;
12+
const Nr: usize = 14;
13+
const key_exp_size: usize = 240;
14+
15+
// S-box in ROM (const → .rodata on MOS).
16+
const sbox: [256]u8 = .{
17+
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
18+
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
19+
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
20+
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
21+
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
22+
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
23+
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
24+
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
25+
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
26+
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
27+
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
28+
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
29+
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
30+
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
31+
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
32+
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
33+
};
34+
35+
const Rcon: [11]u8 = .{ 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 };
36+
37+
const aes_key: [32]u8 = .{
38+
0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe,
39+
0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81,
40+
0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7,
41+
0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4,
42+
};
43+
const iv_init: [16]u8 = .{
44+
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
45+
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
46+
};
47+
const expected_crc: u32 = 0xff1ee2c1;
48+
49+
// Globals avoid large stack allocations on the MOS soft stack.
50+
var round_key: [key_exp_size]u8 = @splat(0);
51+
var buf: [0x2000]u8 = @splat(0);
52+
var cbc_state: [4][4]u8 = @splat(@splat(0));
53+
var cbc_iv: [16]u8 = @splat(0);
54+
55+
fn xtime(x: u8) u8 {
56+
@setRuntimeSafety(false);
57+
return (x << 1) ^ (((x >> 7) & 1) *% @as(u8, 0x1b));
58+
}
59+
60+
fn keyExpansion(rk: *[key_exp_size]u8, key: *const [32]u8) void {
61+
@setRuntimeSafety(false);
62+
var tempa: [4]u8 = undefined;
63+
var i: usize = 0;
64+
while (i < Nk) : (i += 1) {
65+
rk[i * 4 + 0] = key[i * 4 + 0];
66+
rk[i * 4 + 1] = key[i * 4 + 1];
67+
rk[i * 4 + 2] = key[i * 4 + 2];
68+
rk[i * 4 + 3] = key[i * 4 + 3];
69+
}
70+
i = Nk;
71+
while (i < 4 * (Nr + 1)) : (i += 1) {
72+
const k = (i - 1) * 4;
73+
tempa[0] = rk[k + 0];
74+
tempa[1] = rk[k + 1];
75+
tempa[2] = rk[k + 2];
76+
tempa[3] = rk[k + 3];
77+
if (i % Nk == 0) {
78+
const u = tempa[0];
79+
tempa[0] = sbox[tempa[1]];
80+
tempa[1] = sbox[tempa[2]];
81+
tempa[2] = sbox[tempa[3]];
82+
tempa[3] = sbox[u];
83+
tempa[0] ^= Rcon[i / Nk];
84+
}
85+
if (i % Nk == 4) {
86+
tempa[0] = sbox[tempa[0]];
87+
tempa[1] = sbox[tempa[1]];
88+
tempa[2] = sbox[tempa[2]];
89+
tempa[3] = sbox[tempa[3]];
90+
}
91+
const j = i * 4;
92+
const m = (i - Nk) * 4;
93+
rk[j + 0] = rk[m + 0] ^ tempa[0];
94+
rk[j + 1] = rk[m + 1] ^ tempa[1];
95+
rk[j + 2] = rk[m + 2] ^ tempa[2];
96+
rk[j + 3] = rk[m + 3] ^ tempa[3];
97+
}
98+
}
99+
100+
// state[col][row], matching tiny-AES-c's state_t[4][4] column-major layout.
101+
fn addRoundKey(round: usize, state: *[4][4]u8, rk: *const [key_exp_size]u8) void {
102+
@setRuntimeSafety(false);
103+
var c: usize = 0;
104+
while (c < 4) : (c += 1) {
105+
var r: usize = 0;
106+
while (r < 4) : (r += 1) {
107+
state[c][r] ^= rk[round * 16 + c * 4 + r];
108+
}
109+
}
110+
}
111+
112+
fn subBytes(state: *[4][4]u8) void {
113+
@setRuntimeSafety(false);
114+
var r: usize = 0;
115+
while (r < 4) : (r += 1) {
116+
var c: usize = 0;
117+
while (c < 4) : (c += 1) {
118+
state[c][r] = sbox[state[c][r]];
119+
}
120+
}
121+
}
122+
123+
fn shiftRows(state: *[4][4]u8) void {
124+
@setRuntimeSafety(false);
125+
var temp: u8 = undefined;
126+
// row 1: shift left 1
127+
temp = state[0][1];
128+
state[0][1] = state[1][1];
129+
state[1][1] = state[2][1];
130+
state[2][1] = state[3][1];
131+
state[3][1] = temp;
132+
// row 2: shift left 2
133+
temp = state[0][2];
134+
state[0][2] = state[2][2];
135+
state[2][2] = temp;
136+
temp = state[1][2];
137+
state[1][2] = state[3][2];
138+
state[3][2] = temp;
139+
// row 3: shift left 3
140+
temp = state[0][3];
141+
state[0][3] = state[3][3];
142+
state[3][3] = state[2][3];
143+
state[2][3] = state[1][3];
144+
state[1][3] = temp;
145+
}
146+
147+
fn mixColumns(state: *[4][4]u8) void {
148+
@setRuntimeSafety(false);
149+
var c: usize = 0;
150+
while (c < 4) : (c += 1) {
151+
const t = state[c][0];
152+
const tmp = state[c][0] ^ state[c][1] ^ state[c][2] ^ state[c][3];
153+
var tm: u8 = undefined;
154+
tm = xtime(state[c][0] ^ state[c][1]);
155+
state[c][0] ^= tm ^ tmp;
156+
tm = xtime(state[c][1] ^ state[c][2]);
157+
state[c][1] ^= tm ^ tmp;
158+
tm = xtime(state[c][2] ^ state[c][3]);
159+
state[c][2] ^= tm ^ tmp;
160+
tm = xtime(state[c][3] ^ t);
161+
state[c][3] ^= tm ^ tmp;
162+
}
163+
}
164+
165+
fn cipher(state: *[4][4]u8, rk: *const [key_exp_size]u8) void {
166+
@setRuntimeSafety(false);
167+
addRoundKey(0, state, rk);
168+
var round: usize = 1;
169+
while (true) : (round += 1) {
170+
subBytes(state);
171+
shiftRows(state);
172+
if (round == Nr) break;
173+
mixColumns(state);
174+
addRoundKey(round, state, rk);
175+
}
176+
addRoundKey(Nr, state, rk);
177+
}
178+
179+
fn cbcEncrypt(data: []u8, iv: *const [16]u8) void {
180+
@setRuntimeSafety(false);
181+
@memcpy(&cbc_iv, iv);
182+
var i: usize = 0;
183+
while (i < data.len) : (i += 16) {
184+
const block = data[i..][0..16];
185+
var j: usize = 0;
186+
while (j < 16) : (j += 1) block[j] ^= cbc_iv[j];
187+
// Unpack bytes into column-major state: cbc_state[col][row] = block[col*4+row].
188+
var c: usize = 0;
189+
while (c < 4) : (c += 1) {
190+
var r: usize = 0;
191+
while (r < 4) : (r += 1) cbc_state[c][r] = block[c * 4 + r];
192+
}
193+
cipher(&cbc_state, &round_key);
194+
c = 0;
195+
while (c < 4) : (c += 1) {
196+
var r: usize = 0;
197+
while (r < 4) : (r += 1) block[c * 4 + r] = cbc_state[c][r];
198+
}
199+
@memcpy(&cbc_iv, block);
200+
}
201+
}
202+
203+
export fn main() void {
204+
_ = std.c.printf("aes256.zig\n");
205+
_ = std.c.printf("Encrypts the C64 Kernal with AES-256-CBC\n");
206+
207+
const kernal: [*]const u8 = @ptrFromInt(0xe000);
208+
@memcpy(&buf, kernal[0..0x2000]);
209+
210+
keyExpansion(&round_key, &aes_key);
211+
cbcEncrypt(&buf, &iv_init);
212+
213+
const crc = Crc32Cksum.hash(&buf);
214+
_ = std.c.printf("CRC32=%08lX", crc);
215+
if (crc == expected_crc) {
216+
_ = std.c.printf(" [OK]\n");
217+
} else {
218+
_ = std.c.printf(" [FAIL] - expected %08lX\n", expected_crc);
219+
}
220+
}

c64/bench-crc16/bench-crc16.zig

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (c) 2024 Matheus C. França
2+
// SPDX-License-Identifier: Apache-2.0
3+
//! C64 CRC-16/XMODEM benchmark: computes CRC16 of the C64 Kernal ROM ($E000–$FFFF).
4+
pub const panic = @import("mos_panic");
5+
const std = @import("std");
6+
7+
// Comptime-typed benchmark dispatcher: same function body works for any CRC algorithm.
8+
// `comptime Crc: type` is instantiated once per distinct type at compile time.
9+
fn runCrc(comptime Crc: type, comptime expected: u16, data: []const u8) void {
10+
const crc = Crc.hash(data);
11+
_ = std.c.printf("CRC16=%04X", @as(c_uint, crc));
12+
if (crc == expected) {
13+
_ = std.c.printf(" [OK]\n");
14+
} else {
15+
_ = std.c.printf(" [FAIL] - expected %04X\n", @as(c_uint, expected));
16+
}
17+
}
18+
19+
const Crc16Xmodem = std.hash.crc.Crc16Xmodem;
20+
21+
// Comptime self-test: Crc16Xmodem.hash runs entirely in the Zig compiler's
22+
// comptime interpreter — the table lookup and reduction happen at build time.
23+
comptime {
24+
const probe: [4]u8 = .{ 0xC6, 0x42, 0x00, 0xFF };
25+
_ = Crc16Xmodem.hash(&probe); // proves the algorithm is comptime-evaluable
26+
}
27+
28+
const kernal_base: usize = 0xe000; // comptime-known C64 Kernal ROM address
29+
const kernal_len: usize = 0x2000;
30+
const EXPECTED_CRC: u16 = 0xffd0;
31+
32+
export fn main() void {
33+
const kernal: [*]const u8 = @ptrFromInt(kernal_base);
34+
_ = std.c.printf("crc16.zig\n");
35+
_ = std.c.printf("Calculates the CRC16 of the C64 Kernal\n");
36+
runCrc(Crc16Xmodem, EXPECTED_CRC, kernal[0..kernal_len]);
37+
}

c64/bench-crc32/bench-crc32.zig

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) 2024 Matheus C. França
2+
// SPDX-License-Identifier: Apache-2.0
3+
//! C64 CRC-32/CKSUM benchmark: computes CRC32 of the C64 Kernal ROM ($E000–$FFFF).
4+
pub const panic = @import("mos_panic");
5+
const std = @import("std");
6+
7+
// Comptime-typed benchmark dispatcher: same function body works for any CRC algorithm.
8+
// `comptime Crc: type` is instantiated once per distinct type at compile time.
9+
// u32 result: passed directly as vararg — `%lX` reads 32-bit on MOS printf ABI.
10+
fn runCrc(comptime Crc: type, comptime expected: u32, data: []const u8) void {
11+
const crc = Crc.hash(data);
12+
_ = std.c.printf("CRC32=%08lX", crc);
13+
if (crc == expected) {
14+
_ = std.c.printf(" [OK]\n");
15+
} else {
16+
_ = std.c.printf(" [FAIL] - expected %08lX\n", expected);
17+
}
18+
}
19+
20+
const Crc32Cksum = std.hash.crc.Crc32Cksum;
21+
22+
// Comptime self-test: Crc32Cksum.hash runs entirely in the Zig compiler's
23+
// comptime interpreter — the table lookup and reduction happen at build time.
24+
comptime {
25+
const probe: [4]u8 = .{ 0xC6, 0x42, 0x00, 0xFF };
26+
_ = Crc32Cksum.hash(&probe); // proves the algorithm is comptime-evaluable
27+
}
28+
29+
const kernal_base: usize = 0xe000; // comptime-known C64 Kernal ROM address
30+
const kernal_len: usize = 0x2000;
31+
const EXPECTED_CRC: u32 = 0xe1fa84c6;
32+
33+
export fn main() void {
34+
const kernal: [*]const u8 = @ptrFromInt(kernal_base);
35+
_ = std.c.printf("crc32.zig\n");
36+
_ = std.c.printf("Calculates the CRC32 of the C64 Kernal\n");
37+
runCrc(Crc32Cksum, EXPECTED_CRC, kernal[0..kernal_len]);
38+
}

0 commit comments

Comments
 (0)