Skip to content

Commit 6b7cd79

Browse files
committed
fix(sdk): Add NES Famitone2 (sound/music) support
* nesdoug: full-game example * nesdoug: mm3-music example
1 parent 1313ed9 commit 6b7cd79

16 files changed

Lines changed: 2061 additions & 17 deletions

File tree

.github/full-game.gif

1.89 MB
Loading

.github/nesdoug-mmc3.gif

1.39 MB
Loading

.github/workflows/ci.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ jobs:
8383
mmc1-hello.nes mmc1-sprites.nes \
8484
mmc3-hello.nes mmc3-pads.nes \
8585
gtrom-hello.nes gtrom-color-cycle.nes \
86+
nesdoug-mmc3.nes full-game.nes \
8687
action53-hello.nes fds-hello.fds \
8788
c64-hello.prg fibonacci.prg plasma.prg \
8889
vic20-hello.prg \
@@ -151,7 +152,8 @@ jobs:
151152
cnrom-hello.nes cnrom-sprites.nes \
152153
unrom-hello.nes unrom-512-hello.nes unrom-color-cycle.nes \
153154
mmc1-hello.nes mmc1-sprites.nes \
154-
mmc3-hello.nes mmc3-pads.nes; do
155+
mmc3-hello.nes mmc3-pads.nes \
156+
nesdoug-mmc3.nes full-game.nes; do
155157
smoke "$f"
156158
done
157159
# Note: gtrom-hello.nes / gtrom-color-cycle.nes (mapper 111) are NOT tested here —
@@ -255,6 +257,7 @@ jobs:
255257
unrom-hello.nes unrom-512-hello.nes unrom-color-cycle.nes \
256258
mmc1-hello.nes mmc1-sprites.nes \
257259
mmc3-hello.nes mmc3-pads.nes \
260+
nesdoug-mmc3.nes full-game.nes \
258261
gtrom-hello.nes gtrom-color-cycle.nes; do
259262
run_emutest "$f" mesen_libretro.so
260263
done

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ zig build nes-gtrom-color-cycle
4848
zig build nes-unrom-512-hello
4949
zig build nes-action53-hello
5050
zig build fds-hello
51+
zig build nes-nesdoug-mmc3
52+
zig build nes-full-game
5153

5254
# Commodore 64
5355
zig build c64-hello
@@ -159,6 +161,8 @@ Output files land in `zig-out/bin/`.
159161
| `nes-unrom-512-hello` — UNROM-512 mapper (mapper 30) | |
160162
| `nes-action53-hello` — Action53 multicart (mapper 28) | |
161163
| `fds-hello` — Famicom Disk System backdrop hello | |
164+
| `nes-nesdoug-mmc3` — MMC3 demo: banked_call, FamiTone2 music, mid-screen IRQ splits, WRAM | ![](.github/nesdoug-mmc3.gif) |
165+
| `nes-full-game` — scrolling platformer (MMC3): player, enemies, coins, collectibles, 3 levels | ![](.github/full-game.gif) |
162166

163167
### SNES
164168

@@ -209,6 +213,8 @@ Output files land in `zig-out/bin/`.
209213
| `nes-unrom-512-hello` | NES UNROM-512 | mos6502 | `.nes` |
210214
| `nes-action53-hello` | NES Action53 (mapper 28) | mos6502 | `.nes` |
211215
| `fds-hello` | Famicom Disk System | mos6502 | `.fds` |
216+
| `nes-nesdoug-mmc3` | NES MMC3 (nesdoug demo) | mos6502 | `.nes` |
217+
| `nes-full-game` | NES MMC3 scrolling platformer | mos6502 | `.nes` |
212218
| `c64-hello`, `c64-fibonacci` | Commodore 64 | mos6502 | `.prg` |
213219
| `c64-plasma` | Commodore 64 | mos6502 | `.prg` |
214220
| `vic20-hello` | Commodore VIC-20 (24K) | mos6502 | `.prg` |
@@ -390,6 +396,8 @@ The `gen-labels` build step runs this automatically for all NES examples.
390396
- **NES GTROM colour-cycle** — GTROM PRG bank cycling with LED toggle via `$5000` write.
391397
- **NES UNROM-512 hello** — uses translated `mapper.h`; calls `set_prg_bank(0)` and `set_chr_bank(0)` to initialise mapper 30 registers. ROM: 512 KB PRG + 32 KB CHR RAM.
392398
- **NES Action53 hello** — uses translated `mapper.h`; mapper 28 (Action53 multicart). ROM: 64 KB PRG + 8 KB CHR RAM.
399+
- **NES nesdoug MMC3** — MMC3 demo ported from Doug Fraker's nesdoug-llvm tutorial (chapter 33); exercises `banked_call`, FamiTone2 music playback, mid-screen IRQ colour splits, and WRAM read/write. Includes a Mesen2 testRunner script (`mmc3.lua`) that verifies NMI running, sprite position, and VRAM writes within 120 frames.
400+
- **NES full-game** — scrolling platformer (chapter 26 port); player movement, enemies, coins, collectibles, score display, and 3 levels. MMC3 mapper with FamiTone2 music; uses nesdoug VRAM buffer for HUD updates.
393401
- **FDS hello** — Famicom Disk System; uses raw PPU register writes via `nes/hardware.zig` (no neslib — FDS has no CHR ROM, raw PPU only). Writes dark-green backdrop (`$1A`) to palette `$3F00`.
394402
- **VIC-20 hello** — uses CBM KERNAL `cbm_k_chrout` to print "HELLO VIC20!", then cycles VIC chip background/border colour register (`$900F`). Targets 24K memory expansion, loads at `$1201`.
395403
- **C64 hello** — uses translated `c64.h` (VIC-II typed struct) via `b.addTranslateC`; cycles VIC-II border colour register.

build.zig

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,7 @@ pub fn build(b: *std.Build) void {
4949
break :blk buf;
5050
};
5151

52-
const optimize = b.option(std.builtin.OptimizeMode, "optimize", "Optimize mode for target platform executables (default: ReleaseFast)") orelse .ReleaseFast;
53-
52+
const optimize = b.standardOptimizeOption(.{});
5453
// ---- SDK build from source (llvm-mos-sdk git) ----
5554
const sdk_step = b.step("sdk-build", "Build llvm-mos-sdk platform libraries from source");
5655

@@ -476,6 +475,21 @@ pub fn build(b: *std.Build) void {
476475
addNesLabels(b, elf2mlb, gen_labels, exe, "gg-demo");
477476
}
478477

478+
// ---- NES full-game ----
479+
{
480+
const step = b.step("nes-full-game", "Build NES full game (CH26 port: scrolling platformer, coins, enemies, 3 levels)");
481+
const exe = addNesExe(b, sdk_dep, sdk_src, sdk_libs, optimize, "full-game", "nes/nesdoug/full-game/full_game.zig", .{ .chr_src = "nes/nesdoug/full-game/full_game.chr", .with_nesdoug = true, .with_famitone2 = true });
482+
exe.root_module.addImport("neslib", neslib_mod);
483+
exe.root_module.addImport("nesdoug", nesdoug_mod);
484+
exe.root_module.addIncludePath(b.path("nes/nesdoug/mmc3"));
485+
exe.root_module.addAssemblyFile(b.path("nes/nesdoug/full-game/music.s"));
486+
const install = b.addInstallArtifact(exe, .{ .dest_sub_path = "full-game.nes" });
487+
step.dependOn(&install.step);
488+
b.getInstallStep().dependOn(&install.step);
489+
run_bininfo.addFileArg(exe.getEmittedBin());
490+
addNesLabels(b, elf2mlb, gen_labels, exe, "full-game");
491+
}
492+
479493
// ---- C64 hello ----
480494
{
481495
const step = b.step("c64-hello", "Build C64 hello example");
@@ -842,6 +856,41 @@ pub fn build(b: *std.Build) void {
842856
run_bininfo.addFileArg(exe.getEmittedBin());
843857
}
844858

859+
// ---- NES MMC3 nesdoug demo (banked_call, IRQ splits, FamiTone2, WRAM) ----
860+
{
861+
const step = b.step("nes-nesdoug-mmc3", "Build NES MMC3 nesdoug demo (banked_call, IRQ splits, FamiTone2 music, WRAM)");
862+
const exe = addNesExe(b, sdk_dep, sdk_src, sdk_libs, optimize, "nesdoug-mmc3", "nes/nesdoug/mmc3/mmc3.zig", .{
863+
.mapper = .mmc3,
864+
.with_nesdoug = true,
865+
.with_famitone2 = true,
866+
.chr_rom_kb = 16, // Alpha.chr (8KB) + Gears.chr (8KB)
867+
});
868+
// CHR ROM: both .chr files in a single .chr_rom section.
869+
const chr_wf = b.addWriteFiles();
870+
const root_fwd_mmc3: []const u8 = blk: {
871+
const p = b.build_root.path orelse ".";
872+
const buf = b.allocator.dupe(u8, p) catch @panic("OOM");
873+
std.mem.replaceScalar(u8, buf, '\\', '/');
874+
break :blk buf;
875+
};
876+
const chr_asm = chr_wf.add("chr-rom-mmc3.s", b.fmt(
877+
\\.section .chr_rom,"a",@progbits
878+
\\.incbin "{s}/nes/nesdoug/mmc3/Alpha.chr"
879+
\\.incbin "{s}/nes/nesdoug/mmc3/Gears.chr"
880+
, .{ root_fwd_mmc3, root_fwd_mmc3 }));
881+
exe.root_module.addAssemblyFile(chr_asm);
882+
// Music and sound-effect data in PRG ROM bank 12.
883+
exe.root_module.addIncludePath(b.path("nes/nesdoug/mmc3"));
884+
exe.root_module.addAssemblyFile(b.path("nes/nesdoug/mmc3/music.s"));
885+
exe.root_module.addImport("neslib", neslib_mod);
886+
exe.root_module.addImport("nesdoug", nesdoug_mod);
887+
exe.root_module.addImport("mapper", nes_mmc3_mapper_mod);
888+
const install = b.addInstallArtifact(exe, .{ .dest_sub_path = "nesdoug-mmc3.nes" });
889+
step.dependOn(&install.step);
890+
b.getInstallStep().dependOn(&install.step);
891+
run_bininfo.addFileArg(exe.getEmittedBin());
892+
}
893+
845894
// ---- NES GTROM colour-cycle with LED ----
846895
{
847896
const step = b.step("nes-gtrom-color-cycle", "Build NES GTROM mapper colour-cycle with LED example");
@@ -1190,7 +1239,9 @@ fn addNesExe(
11901239
mapper: NesMapper = .nrom,
11911240
chr_src: ?[]const u8 = null,
11921241
chr_srcs: ?[]const []const u8 = null,
1242+
chr_rom_kb: usize = 0,
11931243
with_nesdoug: bool = false,
1244+
with_famitone2: bool = false,
11941245
},
11951246
) *std.Build.Step.Compile {
11961247
const target = b.resolveTargetQuery(.{ .cpu_arch = .mos, .os_tag = .nes });
@@ -1299,17 +1350,21 @@ fn addNesExe(
12991350
\\__chr_ram_size = 8;
13001351
\\INCLUDE "{s}/mos-platform/nes-mmc1/link.ld"
13011352
, .{ reset_dir, sdk_src, sdk_src, sdk_src, sdk_src, sdk_src })),
1302-
.mmc3 => wf.add(ld_name, b.fmt(
1303-
\\SEARCH_DIR("{s}");
1304-
\\SEARCH_DIR("{s}/mos-platform/nes-mmc3");
1305-
\\SEARCH_DIR("{s}/mos-platform/nes");
1306-
\\SEARCH_DIR("{s}/mos-platform/nes/rompoke");
1307-
\\SEARCH_DIR("{s}/mos-platform/common/ldscripts");
1308-
\\/* MMC3 hello uses CHR RAM; override the 256 KiB CHR ROM weak default. */
1309-
\\__chr_rom_size = 0;
1310-
\\__chr_ram_size = 8;
1311-
\\INCLUDE "{s}/mos-platform/nes-mmc3/link.ld"
1312-
, .{ reset_dir, sdk_src, sdk_src, sdk_src, sdk_src, sdk_src })),
1353+
.mmc3 => blk: {
1354+
const chr_cfg = if (cfg.chr_rom_kb > 0)
1355+
b.fmt("__chr_rom_size = {d};\n__chr_ram_size = 0;", .{cfg.chr_rom_kb})
1356+
else
1357+
"/* MMC3 uses CHR RAM; override the 256 KiB CHR ROM weak default. */\n__chr_rom_size = 0;\n__chr_ram_size = 8;";
1358+
break :blk wf.add(ld_name, b.fmt(
1359+
\\SEARCH_DIR("{s}");
1360+
\\SEARCH_DIR("{s}/mos-platform/nes-mmc3");
1361+
\\SEARCH_DIR("{s}/mos-platform/nes");
1362+
\\SEARCH_DIR("{s}/mos-platform/nes/rompoke");
1363+
\\SEARCH_DIR("{s}/mos-platform/common/ldscripts");
1364+
\\{s}
1365+
\\INCLUDE "{s}/mos-platform/nes-mmc3/link.ld"
1366+
, .{ reset_dir, sdk_src, sdk_src, sdk_src, sdk_src, chr_cfg, sdk_src }));
1367+
},
13131368
.gtrom => wf.add(ld_name, b.fmt(
13141369
\\SEARCH_DIR("{s}");
13151370
\\SEARCH_DIR("{s}/mos-platform/nes-gtrom");
@@ -1387,6 +1442,7 @@ fn addNesExe(
13871442
exe.root_module.linkLibrary(libs.c);
13881443
if (libs.neslib) |neslib| exe.root_module.linkLibrary(neslib);
13891444
if (cfg.with_nesdoug) if (libs.nesdoug) |nd| exe.root_module.linkLibrary(nd);
1445+
if (cfg.with_famitone2) if (libs.famitone2) |ft2| exe.root_module.linkLibrary(ft2);
13901446
if (libs.nes_c) |nc| exe.root_module.linkLibrary(nc);
13911447
if (libs.nes_c_startup) |ncs| exe.root_module.linkLibrary(ncs);
13921448
exe.setLinkerScript(wrapper_ld);
@@ -2283,6 +2339,7 @@ fn addEaterExe(
22832339
exe.root_module.linkLibrary(libs.crt0);
22842340
exe.root_module.linkLibrary(libs.c);
22852341
if (libs.crt0_obj) |obj| exe.root_module.addObject(obj);
2342+
if (libs.mem) |mem_obj| exe.root_module.addObject(mem_obj);
22862343
exe.forceUndefinedSymbol("__zig_call_main_section");
22872344
exe.forceUndefinedSymbol("main");
22882345
exe.setLinkerScript(wrapper_ld);

nes/nesdoug/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ originally written in C for the llvm-mos toolchain.
2525
| `mappers/` | 24_Mappers | CNROM 4-bank CHR demo — press Start to cycle banks |
2626
| `bat-ball/` | CH05* | Bat-and-ball from ProgrammingGamesForTheNES CH05 |
2727
| `megablast/` | CH13* | Full Megablast game from ProgrammingGamesForTheNES CH13 (enemies, score, lives, levels) |
28+
| `mmc3/` | 33_MMC3 | MMC3 mapper demo: banked_call, FamiTone2 music, mid-screen IRQ splits, WRAM |
29+
| `full-game/` | 26_Full_Game | Scrolling platformer (MMC3): player, enemies, coins, collectibles, game states |
2830

2931
* `bat-ball` and `megablast` are ported from
3032
[tony-cruise/ProgrammingGamesForTheNES](https://github.com/tony-cruise/ProgrammingGamesForTheNES),
8 KB
Binary file not shown.

0 commit comments

Comments
 (0)