Skip to content

Commit 4b20e10

Browse files
committed
fix(sdk): pce headers bypass
1 parent 534c7bf commit 4b20e10

5 files changed

Lines changed: 119 additions & 34 deletions

File tree

build.zig

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ pub fn build(b: *std.Build) void {
133133
const atari8_target = b.resolveTargetQuery(.{ .cpu_arch = .mos, .os_tag = .atari8 });
134134
const atari8_gtia_mod = atari8GtiaHeaderMod(b, sdk_dep, atari8_target, optimize);
135135

136+
// Translated PCE headers for PC Engine.
137+
const pce_target = b.resolveTargetQuery(.{ .cpu_arch = .mos, .os_tag = .pce });
138+
const pce_mod = pceHeaderMod(b, sdk_dep, pce_target, optimize);
139+
136140
// Host tool: converts MOS ELF symbol tables to Mesen label files (.mlb).
137141
const elf2mlb = b.addExecutable(.{
138142
.name = "elf2mlb",
@@ -564,6 +568,7 @@ pub fn build(b: *std.Build) void {
564568
{
565569
const step = b.step("pce-color-cycle", "Build PC Engine color-cycle example");
566570
const exe = addPceExe(b, sdk_dep, sdk_src, sdk_libs.pce orelse @panic("pce libs not built"), optimize, "pce-color-cycle", "pce/color-cycle/color-cycle.zig");
571+
exe.root_module.addImport("pce", pce_mod);
567572
const install = b.addInstallArtifact(exe, .{ .dest_sub_path = "pce-color-cycle.pce" });
568573
step.dependOn(&install.step);
569574
b.getInstallStep().dependOn(&install.step);
@@ -574,6 +579,7 @@ pub fn build(b: *std.Build) void {
574579
{
575580
const step = b.step("pce-color-cycle-banked", "Build PC Engine banked color-cycle example");
576581
const exe = addPceExe(b, sdk_dep, sdk_src, sdk_libs.pce orelse @panic("pce libs not built"), optimize, "pce-color-cycle-banked", "pce/color-cycle-banked/color-cycle-banked.zig");
582+
exe.root_module.addImport("pce", pce_mod);
577583
const install = b.addInstallArtifact(exe, .{ .dest_sub_path = "pce-color-cycle-banked.pce" });
578584
step.dependOn(&install.step);
579585
b.getInstallStep().dependOn(&install.step);
@@ -836,6 +842,24 @@ pub fn build(b: *std.Build) void {
836842
}
837843
}
838844

845+
fn pceHeaderMod(
846+
b: *std.Build,
847+
sdk_dep: *std.Build.Dependency,
848+
target: std.Build.ResolvedTarget,
849+
opt: std.builtin.OptimizeMode,
850+
) *std.Build.Module {
851+
const tc = b.addTranslateC(.{
852+
.root_source_file = b.path("pce/pce.h"),
853+
.target = target,
854+
.optimize = opt,
855+
.link_libc = false,
856+
});
857+
tc.addIncludePath(sdk_dep.path("mos-platform/pce-common/libpce/include"));
858+
tc.addIncludePath(sdk_dep.path("mos-platform/pce-common"));
859+
tc.addIncludePath(sdk_dep.path("mos-platform/common/include"));
860+
return tc.createModule();
861+
}
862+
839863
fn atari2600HeaderMod(
840864
b: *std.Build,
841865
sdk_dep: *std.Build.Dependency,
@@ -1643,6 +1667,11 @@ fn addPceExe(
16431667
});
16441668
exe.bundle_compiler_rt = false;
16451669
exe.lto = .full;
1670+
// LTO DCE eliminates mosCallMainSection (in .call_main) because the
1671+
// linker KEEP() reference runs after LTO. Force both symbols to keep
1672+
// the .call_main JSR and the main() function itself.
1673+
exe.forceUndefinedSymbol("__zig_call_main_section");
1674+
exe.forceUndefinedSymbol("main");
16461675
exe.root_module.addCSourceFile(.{
16471676
.file = sdk_dep.path("mos-platform/pce/crt0/crt0.S"),
16481677
});
@@ -1653,6 +1682,7 @@ fn addPceExe(
16531682
exe.root_module.linkLibrary(libs.crt);
16541683
exe.root_module.linkLibrary(libs.crt0);
16551684
exe.root_module.linkLibrary(libs.c);
1685+
if (libs.crt0_obj) |obj| exe.root_module.addObject(obj);
16561686
exe.setLinkerScript(wrapper_ld);
16571687
exe.root_module.addImport("mos_panic", b.createModule(.{
16581688
.root_source_file = b.path("sdk/panic.zig"),
Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
// Copyright (c) 2024 Matheus C. França
22
// SPDX-License-Identifier: Apache-2.0
3-
//! PC Engine banked color-cycle demo.
4-
//! Declares ROM bank 1 at virtual offset 6 (0xC000), places cycle_color
5-
//! in that bank section, maps the bank at startup, then loops.
3+
//! PC Engine banked color-cycle demo — cycleColor() lives in ROM bank 1.
64
pub const panic = @import("mos_panic");
5+
const pce = @import("pce");
76

8-
// translate-c cannot handle the volatile pointer cast macros in hardware.h
9-
// (they use C volatile casts that translate-c rejects), so we define the
10-
// VCE registers directly — same addresses as hardware.h.
7+
// IO_VCE_COLOR_INDEX / IO_VCE_COLOR_DATA: hardware.h volatile pointer-cast macros
8+
// are not emitted by translate-c (Aro drops them); keep @ptrFromInt directly.
119
const IO_VCE_COLOR_INDEX: *volatile u16 = @ptrFromInt(0x0402);
1210
const IO_VCE_COLOR_DATA: *volatile u16 = @ptrFromInt(0x0404);
1311

14-
var color: u16 = 0;
15-
16-
// Emit the linker symbols that declare bank 1 at ROM offset 6 (8 KB unit).
12+
// Declare physical bank 1 at ROM offset 6 (0xC000). Mirrors PCE_ROM_BANK_AT(1, 6).
1713
comptime {
1814
asm (
1915
\\.global __rom_bank1
@@ -24,31 +20,27 @@ comptime {
2420
}
2521

2622
// Map ROM bank 1 to virtual address window 6 (0xC000).
27-
// Mirrors pce_rom_bank1_map() from pce/config.h: lda #bank_num; tam #(1<<6).
23+
// Mirrors pce_rom_bank1_map(): lda #bank_num; tam #(1<<6).
2824
fn pceRomBank1Map() void {
2925
asm volatile (
3026
\\lda #__rom_bank1_bank
3127
\\tam #64
3228
);
3329
}
3430

35-
// Must be noinline and in .rom_bank1 section so the linker places it in bank 1.
31+
var color: u16 = 0;
32+
33+
// Placed in ROM bank 1 so it runs from the banked window.
3634
noinline fn cycleColor() linksection(".rom_bank1") void {
3735
IO_VCE_COLOR_INDEX.* = 0x100;
3836
IO_VCE_COLOR_DATA.* = color;
3937
color +%= 1;
4038
}
4139

4240
export fn main() void {
41+
pce.pce_vdc_set_resolution(256, 240, 0);
4342
pceRomBank1Map();
4443
while (true) {
4544
cycleColor();
4645
}
4746
}
48-
49-
// Required interrupt handler stubs for PCE vector table.
50-
export fn irq_vdc() callconv(.c) void {}
51-
export fn irq_timer() callconv(.c) void {}
52-
export fn nmi() callconv(.c) void {}
53-
export fn irq_ext() callconv(.c) void {}
54-
export fn irq_vdc_2() callconv(.c) void {}

pce/color-cycle/color-cycle.zig

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,59 @@
11
// Copyright (c) 2024 Matheus C. França
22
// SPDX-License-Identifier: Apache-2.0
3-
//! PC Engine color-cycle demo.
4-
//! Writes a cycling 9-bit color to VCE palette entry 0 (background color).
5-
//! VCE registers: COLOR_INDEX at 0x0400, COLOR_DATA at 0x0402.
3+
//! PC Engine color-cycle demo — VCE entry 0x100 (backdrop) cycles through 9-bit colors.
64
pub const panic = @import("mos_panic");
5+
const pce = @import("pce");
76

7+
// IO_VCE_COLOR_INDEX / IO_VCE_COLOR_DATA: hardware.h volatile pointer-cast macros
8+
// are not emitted by translate-c (Aro drops them); keep @ptrFromInt directly.
89
const IO_VCE_COLOR_INDEX: *volatile u16 = @ptrFromInt(0x0402);
910
const IO_VCE_COLOR_DATA: *volatile u16 = @ptrFromInt(0x0404);
11+
const IRQ_VDC: u8 = 0x02;
12+
13+
// ticks and irq_vdc are defined together in module-level asm so that the asm
14+
// reference "inc ticks" is resolved by the assembler/linker rather than the
15+
// LTO optimizer (which treats inline-asm symbol names as opaque text and
16+
// cannot match them to LLVM IR globals).
17+
extern var ticks: u16;
18+
comptime {
19+
asm (
20+
\\.section .bss
21+
\\.global ticks
22+
\\ticks:
23+
\\ .space 2
24+
\\
25+
\\.section .text
26+
\\.global irq_vdc
27+
\\irq_vdc:
28+
\\ pha
29+
\\ txa
30+
\\ pha
31+
\\ tya
32+
\\ pha
33+
\\ lda mos16($0000)
34+
\\ and #0x20
35+
\\ beq .Lskip_ticks
36+
\\ inc ticks
37+
\\ bne .Lskip_ticks
38+
\\ inc ticks+1
39+
\\.Lskip_ticks:
40+
\\ pla
41+
\\ tay
42+
\\ pla
43+
\\ tax
44+
\\ pla
45+
\\ rti
46+
);
47+
}
1048

1149
export fn main() void {
12-
var color: u16 = 0;
50+
pce.pce_vdc_set_resolution(256, 240, 0);
51+
pce.pce_vdc_irq_vblank_enable();
52+
pce.pce_irq_enable(IRQ_VDC);
53+
asm volatile ("cli"); // enable CPU IRQs (pce_cpu_irq_enable inline asm not translated)
54+
const tp: *volatile u16 = &ticks;
1355
while (true) {
14-
IO_VCE_COLOR_INDEX.* = 0;
15-
IO_VCE_COLOR_DATA.* = color & 0x1FF;
16-
color +%= 1;
56+
IO_VCE_COLOR_INDEX.* = 0x100;
57+
IO_VCE_COLOR_DATA.* = tp.* >> 3;
1758
}
1859
}
19-
20-
// Required interrupt handler stubs for PCE vector table (crt0.S declares them weak).
21-
export fn irq_vdc() callconv(.c) void {}
22-
export fn irq_timer() callconv(.c) void {}
23-
export fn nmi() callconv(.c) void {}
24-
export fn irq_ext() callconv(.c) void {}
25-
export fn irq_vdc_2() callconv(.c) void {}

pce/pce.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) 2024 Matheus C. França
2+
// SPDX-License-Identifier: Apache-2.0
3+
// Minimal PCE libpce wrapper for translate-c.
4+
// Excludes bank.h (inline asm bank-switch stubs) and system.h (inline asm
5+
// pce_cpu_irq_enable/disable) — Aro cannot lower inline asm function bodies.
6+
// pce_irq_enable is declared here directly; its C signature translates cleanly.
7+
// hardware.h volatile pointer-cast macros (#define IO_VCE_COLOR_INDEX ...) are
8+
// silently dropped by Aro — use @ptrFromInt() in Zig source for MMIO registers.
9+
#include <stdint.h>
10+
#include <stdbool.h>
11+
#include <pce/vce.h>
12+
#include <pce/vdc.h>
13+
void pce_irq_enable(uint8_t mask);

sdk/build.zig

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -811,8 +811,24 @@ fn buildPce(
811811
) Libs {
812812
const libpce_src = b.fmt("{s}/libpce/src", .{pce_common_dir});
813813
const libpce_inc = b.fmt("{s}/libpce/include", .{pce_common_dir});
814+
// common/crt0/crt0.S provides .call_main (jsr main) and .fini_rts sections.
815+
// Must be a TRUE object (not archive member): section-only contributions with no
816+
// exported symbol are silently skipped by ld.lld archive extraction.
817+
// Mirrors cmake add_platform_object_file(common-crt0-o).
818+
const crt0_obj = b.addObject(.{
819+
.name = "crt0",
820+
.root_module = b.createModule(.{ .target = target, .optimize = opt }),
821+
});
822+
crt0_obj.root_module.addIncludePath(.{ .cwd_relative = com_asm });
823+
crt0_obj.root_module.addIncludePath(.{ .cwd_relative = com_inc });
824+
crt0_obj.root_module.addCSourceFiles(.{
825+
.root = .{ .cwd_relative = crt0_dir },
826+
.files = &.{"crt0.S"},
827+
});
828+
crt0_obj.lto = .none;
829+
814830
// libcrt0: pce-specific crt0 files + common init-stack + exit-loop.
815-
// Note: crt0/crt0.S is a standalone object added per-exe (not in this lib).
831+
// Note: pce/crt0/crt0.S is a standalone object added per-exe (not in this lib).
816832
const libcrt0 = addLib(b, "crt0", target, opt);
817833
libcrt0.lto = .none;
818834
libcrt0.root_module.addIncludePath(.{ .cwd_relative = com_asm });
@@ -851,7 +867,7 @@ fn buildPce(
851867
},
852868
});
853869

854-
return .{ .crt = libcrt, .crt0 = libcrt0, .c = libc };
870+
return .{ .crt = libcrt, .crt0 = libcrt0, .c = libc, .crt0_obj = crt0_obj };
855871
}
856872

857873
fn buildAtari8CartStd(

0 commit comments

Comments
 (0)