Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .gitignore

This file was deleted.

16 changes: 3 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Commodore 64 MOS 6510 Emulator Core

![Tests](https://github.com/M64GitHub/zig64/actions/workflows/test.yml/badge.svg)
![Version](https://img.shields.io/badge/version-0.4.0-007bff?style=flat)
![Version](https://img.shields.io/badge/version-0.5.0-007bff?style=flat)
![Status](https://img.shields.io/badge/status-active-00cc00?style=flat)
![License](https://img.shields.io/badge/license-MIT-brightgreen?style=flat)
![Zig](https://img.shields.io/badge/Zig-0.14.0-orange?style=flat)
![Zig](https://img.shields.io/badge/Zig-0.15.2-orange?style=flat)

A **Commodore 64 MOS 6510 emulator core** implemented in **Zig**, engineered for precision, flexibility, and seamless integration into C64-focused projects. This emulator delivers cycle-accurate CPU execution, detailed raster beam emulation for PAL and NTSC video synchronization, and advanced SID register tracking with change decoding, making it an ideal foundation for C64 software analysis, dissecting SID player routines, analyzing register manipulations, and debugging.

Expand Down Expand Up @@ -418,16 +418,7 @@ zig build test
## Using zig64 In Your Project
To add `zig64` as a dependency, use:
```sh
zig fetch --save https://github.com/M64GitHub/zig64/archive/refs/tags/v0.4.0.tar.gz
```
This will add the dependency to your `build.zig.zon`:
```zig
.dependencies = .{
.zig64 = .{
.url = "https://github.com/M64GitHub/zig64/archive/refs/tags/v0.4.0.tar.gz",
.hash = "zig64-0.4.0-v6Fnevh-BADQQLrOWxSwFPI_uzYK_c75MpZtAyP2zosT",
},
},
zig fetch --save https://github.com/M64GitHub/zig64/archive/refs/tags/v0.5.0.tar.gz
```

In your `build.zig`, add the module as follows:
Expand Down Expand Up @@ -470,7 +461,6 @@ git clone https://github.com/M64GitHub/zig64.git
cd zig64
zig build
```
Enjoy bringing the **C64 CPU to life in Zig!**



Expand Down
56 changes: 32 additions & 24 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -13,40 +13,46 @@ pub fn build(b: *std.Build) void {
const mod_flagz = dep_flagz.module("flagz");

// -- Example loadprg
const exe_loadprg = b.addExecutable(.{
.name = "loadprg-example",
.root_source_file = b.path(
"src/examples/loadprg_example.zig",
),
const exe_loadprg_mod = b.addModule("exe_loadprg", .{
.root_source_file = b.path("src/examples/loadprg_example.zig"),
.target = target,
.optimize = optimize,
});
exe_loadprg.root_module.addImport("flagz", mod_flagz);
exe_loadprg.root_module.addImport("zig64", mod_zig64);
exe_loadprg_mod.addImport("flagz", mod_flagz);
exe_loadprg_mod.addImport("zig64", mod_zig64);

const exe_loadprg = b.addExecutable(.{
.name = "loadprg-example",
.root_module = exe_loadprg_mod,
});
b.installArtifact(exe_loadprg);

// -- Example cpu-writebyte
const exe_writebyte = b.addExecutable(.{
.name = "writebyte-example",
.root_source_file = b.path(
"src/examples/cpu-writebyte_example.zig",
),
const exe_writebyte_mod = b.addModule("exe_writebyte", .{
.root_source_file = b.path("src/examples/cpu-writebyte_example.zig"),
.target = target,
.optimize = optimize,
});
exe_writebyte.root_module.addImport("zig64", mod_zig64);
exe_writebyte_mod.addImport("zig64", mod_zig64);

const exe_writebyte = b.addExecutable(.{
.name = "writebyte-example",
.root_module = exe_writebyte_mod,
});
b.installArtifact(exe_writebyte);

// -- Example sid-trace
const exe_sidtrace = b.addExecutable(.{
.name = "sidtrace-example",
.root_source_file = b.path(
"src/examples/sid_trace_example.zig",
),
const exe_sidtrace_mod = b.addModule("exe_sidtrace", .{
.root_source_file = b.path("src/examples/sid_trace_example.zig"),
.target = target,
.optimize = optimize,
});
exe_sidtrace.root_module.addImport("zig64", mod_zig64);
exe_sidtrace_mod.addImport("zig64", mod_zig64);

const exe_sidtrace = b.addExecutable(.{
.name = "sidtrace-example",
.root_module = exe_sidtrace_mod,
});
b.installArtifact(exe_sidtrace);

// -- Run steps for all
Expand Down Expand Up @@ -82,14 +88,16 @@ pub fn build(b: *std.Build) void {
run_cmd_sidtrace.step.dependOn(b.getInstallStep());

// -- Test (Cpu)
const test_exe = b.addTest(.{
.root_source_file = b.path(
"src/test/test-cpu.zig",
),
const test_mod = b.addModule("test", .{
.root_source_file = b.path("src/test/test-cpu.zig"),
.target = target,
.optimize = optimize,
});
test_exe.root_module.addImport("zig64", mod_zig64);
test_mod.addImport("zig64", mod_zig64);

const test_exe = b.addTest(.{
.root_module = test_mod,
});

const test_run = b.addRunArtifact(test_exe);
test_run.step.dependOn(b.getInstallStep());
Expand Down
10 changes: 5 additions & 5 deletions build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
.{
.name = .zig64,
.version = "0.4.0",
.fingerprint = 0xd65e0f9f7a67a1bf,
.minimum_zig_version = "0.14.0",
.version = "0.5.0",
.minimum_zig_version = "0.15.0",
.fingerprint = 0xd65e0f9f3ad8ba3a,
.dependencies = .{
.flagz = .{
.url = "https://github.com/M64GitHub/flagZ/archive/refs/tags/v1.0.0.tar.gz",
.hash = "flagz-1.0.0-vdU1bCRQAQD3QOyKf6gAVpJuhTnlOoA10UmxO_XTycHm",
.url = "https://github.com/M64GitHub/flagZ/archive/refs/tags/v1.1.0.tar.gz",
.hash = "flagz-1.1.0-vdU1bLhJAQBcZ9qTy1SfJBLoYihjq32fjHjVLR9x_WXV",
},
},
.paths = .{
Expand Down
3 changes: 1 addition & 2 deletions src/asm.zig
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const std = @import("std");
const stdout = std.io.getStdOut().writer();

pub const Asm = @This();

Expand Down Expand Up @@ -104,7 +103,7 @@ pub fn disassembleForward(mem: []u8, pc_start: u16, count: usize) !void {
const insn = decodeInstruction(&bytes);
var obuf: [32]u8 = undefined;
const str = try disassembleCodeLine(&obuf, pc, insn);
stdout.print("{s}\n", .{str}) catch {};
std.debug.print("{s}\n", .{str});
pc = pc +% getInstructionSize(insn);
}
}
Expand Down
33 changes: 16 additions & 17 deletions src/cpu.zig
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const std = @import("std");
const stdout = std.io.getStdOut().writer();

const Ram64k = @import("mem.zig");
const Sid = @import("sid.zig");
Expand Down Expand Up @@ -106,11 +105,11 @@ fn doHardReset(cpu: *Cpu) void {
pub fn reset(cpu: *Cpu, hard: bool) void {
if (hard) {
if (cpu.dbg_enabled)
stdout.print("[cpu] hard reset\n", .{}) catch {};
std.debug.print("[cpu] hard reset\n", .{});
cpu.doHardReset();
} else {
if (cpu.dbg_enabled)
stdout.print("[cpu] reset\n", .{}) catch {};
std.debug.print("[cpu] reset\n", .{});
cpu.reset();
}
}
Expand Down Expand Up @@ -163,7 +162,7 @@ pub fn printStatus(cpu: *Cpu) void {

const insn_size = Asm.getInstructionSize(insn);

stdout.print("[cpu] PC: {X:0>4} | {s} | {s} | A: {X:0>2} | X: {X:0>2} | Y: {X:0>2} | SP: {X:0>2} | Cycl: {d:0>2} | Cycl-TT: {d} | ", .{
std.debug.print("[cpu] PC: {X:0>4} | {s} | {s} | A: {X:0>2} | X: {X:0>2} | Y: {X:0>2} | SP: {X:0>2} | Cycl: {d:0>2} | Cycl-TT: {d} | ", .{
cpu.pc,
bytesToHex(&cpu.mem.data, cpu.pc, insn_size),
padTo16(disasm, 12, &buf_disasm_pad),
Expand All @@ -173,13 +172,13 @@ pub fn printStatus(cpu: *Cpu) void {
cpu.sp,
cpu.cycles_last_step,
cpu.cycles_executed,
}) catch {};
});
printFlags(cpu);
stdout.print("\n", .{}) catch {};
std.debug.print("\n", .{});
}

pub fn printTrace(cpu: *Cpu) void {
stdout.print("PC: {X:0>4} OP: {X:0>2} {X:0>2} {X:0>2} A:{X:0>2} X:{X:0>2} Y:{X:0>2} FL:{X:0>2}", .{
std.debug.print("PC: {X:0>4} OP: {X:0>2} {X:0>2} {X:0>2} A:{X:0>2} X:{X:0>2} Y:{X:0>2} FL:{X:0>2}", .{
cpu.pc,
cpu.mem.data[cpu.pc],
cpu.mem.data[cpu.pc + 1],
Expand All @@ -188,13 +187,13 @@ pub fn printTrace(cpu: *Cpu) void {
cpu.x,
cpu.y,
cpu.status,
}) catch {};
stdout.print("\n", .{}) catch {};
});
std.debug.print("\n", .{});
}

pub fn printFlags(cpu: *Cpu) void {
cpu.flagsToPS();
stdout.print("FL: {b:0>8}", .{cpu.status}) catch {};
std.debug.print("FL: {b:0>8}", .{cpu.status});
}

pub fn readByte(cpu: *Cpu, addr: u16) u8 {
Expand Down Expand Up @@ -877,17 +876,17 @@ pub fn runStep(cpu: *Cpu) u8 {
cpu.pc = jsr_addr;
cpu.cycles_executed +%= 1; // Matches 6 cycles with fetch and push
if (cpu.dbg_enabled) {
stdout.print("[cpu] JSR {X:0>4}, return to {X:0>4}\n", .{
std.debug.print("[cpu] JSR {X:0>4}, return to {X:0>4}\n", .{
jsr_addr,
ret_addr,
}) catch {};
});
}
},

Asm.rts.opcode => {
if (cpu.sp == 0xFF) {
if (cpu.dbg_enabled) {
stdout.print("[cpu] RTS EXIT!\n", .{}) catch {};
std.debug.print("[cpu] RTS EXIT!\n", .{});
}
cpu.cycles_last_step =
@as(u8, @truncate(cpu.cycles_executed -% cycles_now));
Expand All @@ -898,17 +897,17 @@ pub fn runStep(cpu: *Cpu) u8 {
cpu.pc = ret_addr + 1;
cpu.cycles_executed +%= 2;
if (cpu.dbg_enabled) {
stdout.print("[cpu] RTS to {X:0>4}\n", .{
std.debug.print("[cpu] RTS to {X:0>4}\n", .{
ret_addr + 1,
}) catch {};
});
}
},

Asm.jmp_abs.opcode => {
const addr: u16 = addrAbs(cpu);
cpu.pc = addr;
if (cpu.dbg_enabled) {
stdout.print("[cpu] JMP {X:0>4}\n", .{addr}) catch {};
std.debug.print("[cpu] JMP {X:0>4}\n", .{addr});
}
},

Expand Down Expand Up @@ -1394,7 +1393,7 @@ pub fn runStep(cpu: *Cpu) u8 {
if ((cpu.mem.data[0x01] & 0x07) != 0x5 and
((cpu.pc == 0xea31) or (cpu.pc == 0xea81)))
{
stdout.print("[cpu] RTI\n", .{}) catch {};
std.debug.print("[cpu] RTI\n", .{});

return 0;
}
Expand Down
23 changes: 11 additions & 12 deletions src/examples/cpu-writebyte_example.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,18 @@ const Asm = C64.Asm;

pub fn main() !void {
const allocator = std.heap.page_allocator;
const stdout = std.io.getStdOut().writer();

// Initialize the C64 emulator at $0800 with PAL VIC
var c64 = try C64.init(allocator, C64.Vic.Model.pal, 0x0800);
defer c64.deinit(allocator);

// Print initial emulator state
try stdout.print("CPU start address: ${X:0>4}\n", .{c64.cpu.pc});
try stdout.print("VIC model: {s}\n", .{@tagName(c64.vic.model)});
try stdout.print("SID base address: ${X:0>4}\n", .{c64.sid.base_address});
std.debug.print("CPU start address: ${X:0>4}\n", .{c64.cpu.pc});
std.debug.print("VIC model: {s}\n", .{@tagName(c64.vic.model)});
std.debug.print("SID base address: ${X:0>4}\n", .{c64.sid.base_address});

// Write a SID register sweep program to $0800
try stdout.print("\nWriting SID sweep program to $0800...\n", .{});
std.debug.print("\nWriting SID sweep program to $0800...\n", .{});
c64.cpu.writeByte(Asm.lda_imm.opcode, 0x0800); // LDA #$0A ; Load initial value 10
c64.cpu.writeByte(0x0A, 0x0801);
c64.cpu.writeByte(Asm.tax.opcode, 0x0802); // TAX ; X = A (index for SID regs)
Expand All @@ -38,10 +37,10 @@ pub fn main() !void {
c64.sid.dbg_enabled = true;

// Step through the program, analyzing SID changes
try stdout.print("\nExecuting SID sweep step-by-step...\n", .{});
std.debug.print("\nExecuting SID sweep step-by-step...\n", .{});
while (c64.cpu.runStep() != 0) {
if (c64.sid.last_change) |change| {
try stdout.print(
std.debug.print(
"SID register {s} changed!\n",
.{@tagName(change.meaning)},
);
Expand All @@ -52,27 +51,27 @@ pub fn main() !void {
Sid.FilterModeVolume.fromValue(change.old_value).volume;
const new_vol =
Sid.FilterModeVolume.fromValue(change.new_value).volume;
try stdout.print(
std.debug.print(
"Volume changed: {d} => {d}\n",
.{ old_vol, new_vol },
);
}
if (change.oscWaveformChanged(1)) {
const wf = Sid.WaveformControl.fromValue(change.new_value);
try stdout.print(
std.debug.print(
"Osc1 waveform updated: Pulse={}\n",
.{wf.pulse},
);
}
if (change.oscFreqChanged(1)) {
try stdout.print(
std.debug.print(
"Osc1 freq updated: {X:02} => {X:02}\n",
.{ change.old_value, change.new_value },
);
}
if (change.oscAttackDecayChanged(1)) {
const ad = Sid.AttackDecay.fromValue(change.new_value);
try stdout.print(
std.debug.print(
"Osc1 attack/decay: A={d}, D={d}\n",
.{ ad.attack, ad.decay },
);
Expand All @@ -81,6 +80,6 @@ pub fn main() !void {
}

// Final SID state
try stdout.print("\nFinal SID registers:\n", .{});
std.debug.print("\nFinal SID registers:\n", .{});
c64.sid.printRegisters();
}
13 changes: 6 additions & 7 deletions src/examples/loadprg_example.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const stdout = std.io.getStdOut().writer();

const Args = struct {
prg: []const u8,
Expand All @@ -19,7 +18,7 @@ pub fn main() !void {
const args = try flagz.parse(Args, allocator);
defer flagz.deinit(args, allocator);

try stdout.print("[EXE] initializing emulator\n", .{});
std.debug.print("[EXE] initializing emulator\n", .{});
var c64 = try C64.init(allocator, C64.Vic.Model.pal, 0x0000);
defer c64.deinit(allocator);

Expand All @@ -30,14 +29,14 @@ pub fn main() !void {
// c64.sid.dbg_enabled = true;

// load a .prg file from disk
try stdout.print("[EXE] Loading '{s}'\n", .{args.prg});
std.debug.print("[EXE] Loading '{s}'\n", .{args.prg});
const load_address = try c64.loadPrg(allocator, args.prg, true);
try stdout.print("[EXE] Load address: {X:0>4}\n\n", .{load_address});
std.debug.print("[EXE] Load address: {X:0>4}\n\n", .{load_address});

try stdout.print("[EXE] Disassembling from: {X:0>4}\n", .{load_address});
std.debug.print("[EXE] Disassembling from: {X:0>4}\n", .{load_address});
try Asm.disassembleForward(&c64.mem.data, load_address, 31);
try stdout.print("\n\n", .{});
std.debug.print("\n\n", .{});

try stdout.print("[EXE] RUN\n", .{});
std.debug.print("[EXE] RUN\n", .{});
c64.run();
}
Loading
Loading