Files
6soz/src/system/nes/ppu/control.zig
T

96 lines
2.9 KiB
Zig

// PPUCTRL
//
// VPHB SINN
pub const Control = @This();
raw: u8 = 0,
pub fn baseNametable(self: Control) u2 {
return @truncate(self.raw & 0x03);
}
pub fn baseNametableAddress(self: Control) u16 {
return 0x2000 +
(@as(u16, self.baseNametable()) * 0x400);
}
pub fn vramIncrement(self: Control) u8 {
return if ((self.raw & 0x04) != 0)
32
else
1;
}
pub fn sprite8x8PatternAddress(self: Control) u16 {
return if ((self.raw & 0x08) != 0)
0x1000
else
0x0000;
}
/// Computes pattern table address for a sprite tile (supports both 8x8 and 8x16 modes).
pub fn spritePatternAddressForTile(self: Control, tile: u8, sprite_y_offset: u4) u16 {
if (self.spriteHeight() == 8) {
return self.sprite8x8PatternAddress() | (@as(u16, tile) << 4) | (sprite_y_offset & 7);
} else {
const bank: u16 = @as(u16, tile & 1) << 12;
var tile_index: u16 = tile & 0xfe;
if (sprite_y_offset >= 8) {
tile_index += 1;
}
return bank | (tile_index << 4) | (sprite_y_offset & 7);
}
}
pub fn backgroundPatternAddress(self: Control) u16 {
return if ((self.raw & 0x10) != 0)
0x1000
else
0x0000;
}
pub fn spriteHeight(self: Control) u8 {
return if ((self.raw & 0x20) != 0)
16
else
8;
}
pub fn masterSlave(self: Control) bool {
return (self.raw & 0x40) != 0;
}
pub fn nmiEnabled(self: Control) bool {
return (self.raw & 0x80) != 0;
}
const std = @import("std");
test "8x8 sprite pattern address" {
const c0: Control = .{ .raw = 0x00 }; // 8x8, pattern table 0
try std.testing.expectEqual(@as(u16, 0x0000), c0.sprite8x8PatternAddress());
try std.testing.expectEqual(@as(u16, 0x0143), c0.spritePatternAddressForTile(0x14, 3));
const c1: Control = .{ .raw = 0x08 }; // 8x8, pattern table 1 ($1000)
try std.testing.expectEqual(@as(u16, 0x1000), c1.sprite8x8PatternAddress());
try std.testing.expectEqual(@as(u16, 0x1143), c1.spritePatternAddressForTile(0x14, 3));
}
test "8x16 sprite pattern address ignores bit 3 and uses tile bit 0" {
const c: Control = .{ .raw = 0x28 }; // 8x16, bit 3 set (ignored!)
try std.testing.expectEqual(@as(u8, 16), c.spriteHeight());
// Tile 0x20: bit 0 is 0 -> bank 0 ($0000). Top half (offset 0..7).
try std.testing.expectEqual(@as(u16, 0x0203), c.spritePatternAddressForTile(0x20, 3));
// Tile 0x20: Bottom half (offset 8..15) -> tile index becomes 0x21!
try std.testing.expectEqual(@as(u16, 0x0212), c.spritePatternAddressForTile(0x20, 10));
// Tile 0x25: bit 0 is 1 -> bank 1 ($1000). Tile index & 0xFE = 0x24. Top half.
try std.testing.expectEqual(@as(u16, 0x1245), c.spritePatternAddressForTile(0x25, 5));
// Tile 0x25: Bottom half -> tile index & 0xFE + 1 = 0x25.
try std.testing.expectEqual(@as(u16, 0x1254), c.spritePatternAddressForTile(0x25, 12));
}