Zig64

Check-in [d5c698fc12]
Login

Check-in [d5c698fc12]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Put literally anything on the screen
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: d5c698fc12d6f7036e81f3a6f96ed7e091134e03809fe0c7b3e0148ab1fe43c1
User & Date: ryno 2025-01-30 10:29:18
About

Video output works!

Right now it just outputs yellow (specifically: 320×240 16-bit pixels of 31,31,0,0). There are some elements about the VI that I ain't quite certain about, like good values for the X and Y scale/offset (the n64brew wiki doesn't document them all that well, and libdragon just handles them with convoluted bitshifting and no explanation; all I know is that they're fixed-point numbers, i.e. functionally integers). Also, I'm pretty sure it borks things because there are no interrupt handlers setup yet.

Context
2025-02-04
01:05
Add some memory-related helpers and make `print()` follow the same API as other Zig `print()`s check-in: b1f425d583 user: ryno tags: trunk
2025-01-30
10:29
Put literally anything on the screen check-in: d5c698fc12 user: ryno tags: trunk
04:35
Handle Zig panics check-in: 88241cf9c2 user: ryno tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Added src/VI.zig.

































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
const base = 0xa4400000;

const AAMode = enum(u2) {
    EnabledAlwaysFetch = 0,
    Enabled = 1,
    Disabled = 2,
    DisabledNoResampling = 3,
};

const PixelFormat = enum(u2) {
    Off = 0,
    Reserved = 1,
    Depth16 = 2,
    Depth32 = 3,
};

pub const Pixel16 = packed struct(u16) {
    a: u1,
    b: u5,
    g: u5,
    r: u5,
};

pub const Pixel32 = packed struct(u32) {
    r: u8,
    g: u8,
    b: u8,
    a: u8,
};

const Control = packed struct(u32) {
    pixel_format: PixelFormat,
    enable_gamma_dither: bool,
    enable_gamma: bool,
    enable_divot: bool,
    enable_vbus_clock: bool = false,
    serrate: bool,
    test_mode: bool,
    aa_mode: AAMode,
    reserved1: bool = false,
    kill_we: bool,
    pixel_advance: u4,
    enable_dedither: bool,
    reserved2: u15 = 0,
};
pub const control: *volatile Control = @ptrFromInt(base + 0x0);

pub const origin: *volatile u32 = @ptrFromInt(base + 0x4); // u24

pub const width: *volatile u32 = @ptrFromInt(base + 0x8); // u12

pub const v_interrupt: *volatile u32 = @ptrFromInt(base + 0xc); // u10

const VCurrent = packed struct(u32) {
    field: bool,
    half_line: u9,
    reserved: u22,
};
pub const v_current: *volatile VCurrent = @ptrFromInt(base + 0x10);

const Burst = packed struct(u32) {
    hsync_width: u8,
    burst_width: u8,
    vsync_height: u4,
    burst_start: u10,
    reserved: u2 = 0,
};
pub const burst: *volatile Burst = @ptrFromInt(base + 0x14);

pub const v_total: *volatile u32 = @ptrFromInt(base + 0x18); // u10

const HTotal = packed struct(u32) {
    total: u12,
    reserved1: u4 = 0,
    leap: u5,
    reserved2: u11 = 0,
};
pub const h_total: *volatile HTotal = @ptrFromInt(base + 0x1c);

const HTotalLeap = packed struct(u32) {
    b: u12,
    reserved1: u4 = 0,
    a: u12,
    reserved2: u4 = 0,
};
pub const h_total_leap: *volatile HTotalLeap = @ptrFromInt(base + 0x1c);

const HVideo = packed struct(u32) {
    end: u10,
    reserved1: u6 = 0,
    start: u10,
    reserved2: u6 = 0,
};
pub const h_video: *volatile HVideo = @ptrFromInt(base + 0x24);

const VVideo = packed struct(u32) {
    end: u10,
    reserved1: u6 = 0,
    start: u10,
    reserved2: u6 = 0,
};
pub const v_video: *volatile VVideo = @ptrFromInt(base + 0x28);

const VBurst = packed struct(u32) {
    end: u10,
    reserved1: u6 = 0,
    start: u10,
    reserved2: u6 = 0,
};
pub const v_burst: *volatile VBurst = @ptrFromInt(base + 0x2c);

const XScale = packed struct(u32) {
    scale: u12,
    reserved1: u4 = 0,
    offset: u12,
    reserved2: u4 = 0,
};
pub const x_scale: *volatile XScale = @ptrFromInt(base + 0x30);

const YScale = packed struct(u32) {
    scale: u12,
    reserved1: u4 = 0,
    offset: u10,
    unused: u2 = 0,
    reserved2: u4 = 0,
};
pub const y_scale: *volatile YScale = @ptrFromInt(base + 0x34);

pub const test_addr: *volatile u32 = @ptrFromInt(base + 0x38); // u6

pub const staged_data: *volatile u32 = @ptrFromInt(base + 0x3c);

// -----

const VideoMode = enum {
    NTSC,
    PAL,
    MPAL,
};

const SetupOptions = struct {
    mode: VideoMode = .NTSC,
    format: PixelFormat = .Depth16,
    width: u12 = 320,
    height: u12 = 240,
    interlace: bool = false,
    antialias: bool = false,
    resample: bool = false,
    dedither: bool = false,
    gamma_dither: bool = false,
    gamma: bool = false,
    ique: bool = false,
};

pub fn setup(opts: SetupOptions) void {
    // Defaults based on guidance in
    // https://n64brew.dev/wiki/Video_Interface
    control.* = .{
        .pixel_format = opts.format,
        .enable_gamma_dither = opts.gamma_dither,
        .enable_gamma = opts.gamma,
        .enable_divot = opts.antialias,
        .enable_vbus_clock = false,
        .serrate = opts.interlace,
        .test_mode = false,
        .aa_mode = if (opts.dedither) .EnabledAlwaysFetch else .Disabled,
        .kill_we = false,
        .pixel_advance = if (opts.ique) 1 else 3,
        .enable_dedither = opts.dedither,
    };
    width.* = opts.width;
    v_interrupt.* = 2;
    burst.* = switch (opts.mode) {
        // FIXME: does PAL-M use NTSC or PAL values here?  Or a mix of
        // both?  The n64brew wiki doesn't specify.  Assuming it uses
        // NTSC values since it's supposed to use NTSC dimensions with
        // PAL colors.  If any Brazilians could test this out and make
        // sure this works as expected, that'd be great.
        .NTSC, .MPAL => .{
            .hsync_width = 57,
            .burst_width = 34,
            .vsync_height = 5,
            .burst_start = 62,
        },
        .PAL => .{
            .hsync_width = 58,
            .burst_width = 35,
            .vsync_height = 4,
            .burst_start = 64,
        },
    };
    v_total.* = switch (opts.mode) {
        .NTSC, .MPAL => if (opts.interlace) 524 else 525,
        .PAL => if (opts.interlace) 624 else 625,
    };
    h_total.* = switch (opts.mode) {
        .NTSC => .{ .total = 3093, .leap = 0, },
        .PAL => .{ .total = 3177, .leap = 0x15 },
        // FIXME: should PAL-M leap be 0x15 or 0?
        .MPAL => .{ .total = 3090, .leap = 0, },
    };
    if (opts.mode == .PAL) h_total_leap.* = .{ .a = 3182, .b = 3183 };
    h_video.* = switch (opts.mode) {
        .NTSC, .MPAL => .{ .start = 108, .end = 748 },
        .PAL => .{ .start = 128, .end = 768 },
    };
    v_video.* = switch (opts.mode) {
        .NTSC, .MPAL => .{ .start = 0x025, .end = 0x1ff },
        .PAL => .{ .start = 0x05f, .end = 0x239 },
    };
    v_burst.* = switch (opts.mode) {
        .NTSC, .MPAL => .{ .start = 0x00e, .end = 0x204 },
        .PAL => .{ .start = 0x009, .end = 0x26b },
    };
    // n64brew wiki doesn't go into much detail on these...
    x_scale.* = .{
        .scale = 0x100,
        .offset = 0,
    };
    y_scale.* = .{
        .scale = 0x100,
        .offset = 0,
    };
}

Changes to src/main.zig.

1
2
3
4
5
6
7
8
9
10


11
12
13
14
15
16
17
const std = @import("std");

// BEGIN MEMORY MAP STUFF

const dmem: *align(4) [4096]u8 = @ptrFromInt(0xa4000000);
const imem: *align(4) [4096]u8 = @ptrFromInt(0xa4001000);

/// Peripheral Interface memory regions.  See
/// https://n64brew.dev/wiki/Peripheral_Interface for details.
const PI = @import("./PI.zig");



/// SummerCart 64 memory map and helper functions.  See
/// https://github.com/Polprzewodnikowy/SummerCart64/blob/main/docs/01_memory_map.md
/// for details.
const SC64 = @import("./SC64.zig");

/// IS-Viewer memory map and helper functions.  This operates in










>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const std = @import("std");

// BEGIN MEMORY MAP STUFF

const dmem: *align(4) [4096]u8 = @ptrFromInt(0xa4000000);
const imem: *align(4) [4096]u8 = @ptrFromInt(0xa4001000);

/// Peripheral Interface memory regions.  See
/// https://n64brew.dev/wiki/Peripheral_Interface for details.
const PI = @import("./PI.zig");

const VI = @import("./VI.zig");

/// SummerCart 64 memory map and helper functions.  See
/// https://github.com/Polprzewodnikowy/SummerCart64/blob/main/docs/01_memory_map.md
/// for details.
const SC64 = @import("./SC64.zig");

/// IS-Viewer memory map and helper functions.  This operates in
91
92
93
94
95
96
97




98
99
100
101
102
103
104
    Debug.print("All your Nintendo 64 are belong to us.\n");
    Debug.print("This is another message from Zig.\n");
    const dmem_test_pat: [16]u8 align(4) = .{
        0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
        0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe
    };
    PI.writeBytes(dmem, &dmem_test_pat);




    while (true) {}
}

pub fn panic(
    msg: []const u8,
    _: ?*std.builtin.StackTrace,
    _: ?usize







>
>
>
>







93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
    Debug.print("All your Nintendo 64 are belong to us.\n");
    Debug.print("This is another message from Zig.\n");
    const dmem_test_pat: [16]u8 align(4) = .{
        0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
        0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe
    };
    PI.writeBytes(dmem, &dmem_test_pat);
    const pxl: VI.Pixel16 = .{ .r = 31, .g = 31, .b = 0, .a = 0 };
    const test_framebuffer: [320][240]VI.Pixel16 = .{.{pxl} ** 240} ** 320;
    VI.origin.* = @intFromPtr(&test_framebuffer);
    VI.setup(.{});
    while (true) {}
}

pub fn panic(
    msg: []const u8,
    _: ?*std.builtin.StackTrace,
    _: ?usize