srctree

Johan parent a66ddee8 dca65372 57db88d4
Merge pull request #352 from johnor/vram-reg

Add PpuVram-class
core/include/nes/core/ppu_registers.h added: 278, removed: 137, total 141
@@ -4,6 +4,94 @@
 
namespace n_e_s::core {
 
// Vram register in PPU.
// From http://wiki.nesdev.com/w/index.php/PPU_scrolling
// The 15 bit registers t and v are composed this way:
// yyy NN YYYYY XXXXX
// ||| || ||||| +++++-- coarse X scroll
// ||| || +++++-------- coarse Y scroll
// ||| ++-------------- nametable select
// +++----------------- fine Y scroll
class PpuVram {
public:
constexpr PpuVram() noexcept = default;
constexpr explicit PpuVram(const uint16_t value) : value_(value) {}
 
constexpr bool operator==(const PpuVram &) const = default;
constexpr bool operator!=(const PpuVram &) const = default;
 
constexpr uint16_t value() const {
return value_;
}
 
constexpr void set_fine_scroll_y(uint8_t fine_scroll_y) {
fine_scroll_y = fine_scroll_y & 7u;
value_ &= static_cast<uint16_t>(0b1000'1111'1111'1111);
value_ |= static_cast<uint16_t>(fine_scroll_y << 12u);
}
 
constexpr uint8_t fine_scroll_y() const {
return static_cast<uint8_t>(value_ >> 12u) & 0b0000'0111u;
}
 
constexpr void set_coarse_scroll_y(uint8_t coarse_scroll_y) {
coarse_scroll_y = coarse_scroll_y & 0b0001'1111u;
value_ &= static_cast<uint16_t>(0b1111'1100'0001'1111);
value_ |= static_cast<uint16_t>(coarse_scroll_y << 5u);
}
 
constexpr uint8_t coarse_scroll_y() const {
return static_cast<uint8_t>(value_ >> 5u) & 0b0001'1111u;
}
 
constexpr void set_coarse_scroll_x(uint8_t coarse_scroll_x) {
coarse_scroll_x = coarse_scroll_x & 0b0001'1111u;
value_ &= static_cast<uint16_t>(0b1111'1111'1110'0000);
value_ |= coarse_scroll_x;
}
 
constexpr void set_nametable(uint8_t nametable) {
nametable = nametable & 3u;
value_ &= static_cast<uint16_t>(0b1111'0011'1111'1111);
value_ |= static_cast<uint16_t>(nametable << 10u);
}
 
constexpr uint8_t coarse_scroll_x() const {
return value_ & 0b0001'1111u;
}
 
constexpr void increase_coarse_x() {
const auto coarse_x = coarse_scroll_x();
if (coarse_x == 31u) {
set_coarse_scroll_x(0u);
value_ ^= 0x0400u; // switch horizontal nametable
} else {
set_coarse_scroll_x(coarse_x + 1u);
}
}
 
constexpr void increase_y() {
if (fine_scroll_y() < 7u) {
set_fine_scroll_y(fine_scroll_y() + 1u);
} else {
set_fine_scroll_y(0u);
 
const uint16_t coarse_y = coarse_scroll_y();
if (coarse_y == 29u) {
set_coarse_scroll_y(0u);
value_ ^= 0x0800u; // switch vertical nametable
} else if (coarse_y == 31u) {
set_coarse_scroll_y(0u); // nametable not switched
} else {
set_coarse_scroll_y(coarse_y + 1u);
}
}
}
 
private:
uint16_t value_{0u};
};
 
struct PpuRegisters {
uint16_t scanline;
uint16_t cycle;
@@ -12,8 +100,8 @@ struct PpuRegisters {
uint8_t status;
uint8_t oamaddr;
uint8_t fine_x_scroll;
uint16_t vram_addr;
uint16_t temp_vram_addr;
PpuVram vram_addr;
PpuVram temp_vram_addr;
bool write_toggle;
 
uint8_t name_table_latch;
 
core/src/ppu.cpp added: 278, removed: 137, total 141
@@ -22,62 +22,6 @@ const uint16_t kPostRenderScanline = 240;
const uint16_t kVBlankScanlineStart = 241;
const uint16_t kVBlankScanlineEnd = 260;
 
constexpr uint16_t increase_vram_coarse_x(uint16_t vram_addr) {
// From http://wiki.nesdev.com/w/index.php/PPU_scrolling
if ((vram_addr & 0x001Fu) == 31u) {
vram_addr &= ~0x001Fu; // coarse X = 0
vram_addr ^= 0x0400u; // switch horizontal nametable
} else {
vram_addr += 1u;
}
return vram_addr;
}
 
constexpr uint16_t increase_vram_y(uint16_t vram_addr) {
// From http://wiki.nesdev.com/w/index.php/PPU_scrolling
if ((vram_addr & 0x7000u) != 0x7000u) {
// if fine Y < 7
vram_addr += 0x1000u;
} else {
vram_addr &= ~0x7000u; // fine Y = 0
uint16_t y = (vram_addr & 0x03E0u) >> 5u; // let y = coarse Y
if (y == 29u) {
y = 0u; // coarse Y = 0
vram_addr ^= 0x0800u; // switch vertical nametable
} else if (y == 31u) {
y = 0u; // coarse Y = 0, nametable not switched
} else {
y += 1u; // increment coarse Y
}
vram_addr = static_cast<uint16_t>(
vram_addr & static_cast<uint16_t>(~0x03E0u)) |
static_cast<uint16_t>(y << 5u); // put coarse Y back into v
}
return vram_addr;
}
 
// From http://wiki.nesdev.com/w/index.php/PPU_scrolling
// The 15 bit registers t and v are composed this way during rendering:
// yyy NN YYYYY XXXXX
// ||| || ||||| +++++-- coarse X scroll
// ||| || +++++-------- coarse Y scroll
// ||| ++-------------- nametable select
// +++----------------- fine Y scroll
constexpr uint8_t get_fine_scroll_y(uint16_t vram_addr) {
uint8_t fine_scroll = static_cast<uint8_t>(vram_addr >> 12u) & 0b0000'0111u;
return fine_scroll;
}
 
constexpr uint8_t get_coarse_scroll_y(uint16_t vram_addr) {
uint8_t y = static_cast<uint8_t>(vram_addr >> 5u) & 0b0001'1111u;
return y;
}
 
constexpr uint8_t get_coarse_scroll_x(uint16_t vram_addr) {
uint8_t x = vram_addr & 0b0001'1111u;
return x;
}
 
// From
// https://wiki.nesdev.org/w/index.php/PPU_scrolling#Tile_and_attribute_fetching
uint16_t get_nametable_address(uint16_t vram_addr) {
@@ -109,14 +53,14 @@ uint8_t Ppu::read_byte(uint16_t addr) {
} else if (addr == kOamData) {
byte = oam_data_[registers_->oamaddr];
} else if (addr == kPpuData) {
byte = mmu_->read_byte(registers_->vram_addr);
if (registers_->vram_addr < kFirstPaletteData) {
byte = mmu_->read_byte(registers_->vram_addr.value());
if (registers_->vram_addr.value() < kFirstPaletteData) {
const uint8_t tmp_buffer = read_buffer_;
read_buffer_ = byte;
byte = tmp_buffer;
} else {
read_buffer_ = mmu_->read_byte(
registers_->vram_addr - static_cast<uint16_t>(0x1000));
read_buffer_ = mmu_->read_byte(registers_->vram_addr.value() -
static_cast<uint16_t>(0x1000));
}
increment_vram_address();
} else {
@@ -135,11 +79,7 @@ void Ppu::write_byte(uint16_t addr, uint8_t byte) {
}
 
registers_->ctrl = byte;
auto name_table_bits = static_cast<uint16_t>(byte & 3u);
registers_->temp_vram_addr &=
static_cast<uint16_t>(0b1111'0011'1111'1111);
registers_->temp_vram_addr |=
static_cast<uint16_t>(name_table_bits << 10u);
registers_->temp_vram_addr.set_nametable(byte);
} else if (addr == kPpuMask) {
registers_->mask = byte;
} else if (addr == kOamAddr) {
@@ -150,38 +90,35 @@ void Ppu::write_byte(uint16_t addr, uint8_t byte) {
}
} else if (addr == kPpuScroll) {
if (registers_->write_toggle) { // Second write, Y scroll
uint16_t y_scroll = (byte >> 3u);
auto fine_y_scroll = static_cast<uint16_t>(byte & 7u);
registers_->temp_vram_addr &=
static_cast<uint16_t>(0b1000'1100'0001'1111);
registers_->temp_vram_addr |= static_cast<uint16_t>(y_scroll << 5u);
registers_->temp_vram_addr |=
static_cast<uint16_t>(fine_y_scroll << 12u);
const uint16_t y_scroll = (byte >> 3u);
const auto fine_y_scroll = static_cast<uint16_t>(byte & 7u);
registers_->temp_vram_addr.set_fine_scroll_y(fine_y_scroll);
registers_->temp_vram_addr.set_coarse_scroll_y(y_scroll);
registers_->write_toggle = false;
} else { // First write, X Scroll
uint16_t x_scroll = (byte >> 3u);
registers_->temp_vram_addr &=
static_cast<uint16_t>(0b1111'1111'1110'0000);
registers_->temp_vram_addr |= x_scroll;
const uint16_t x_scroll = (byte >> 3u);
registers_->temp_vram_addr.set_coarse_scroll_x(x_scroll);
registers_->fine_x_scroll = static_cast<uint8_t>(byte & 7u);
registers_->write_toggle = true;
}
} else if (addr == kPpuAddr) {
if (registers_->write_toggle) { // Second write, lower address byte
registers_->temp_vram_addr =
(registers_->temp_vram_addr & 0xFF00u) | byte;
registers_->temp_vram_addr = PpuVram(
(registers_->temp_vram_addr.value() & 0xFF00u) | byte);
registers_->vram_addr = registers_->temp_vram_addr;
registers_->write_toggle = false;
} else { // First write, upper address byte
// Valid addresses are $0000-$3FFF; higher addresses will be
// mirrored down.
const uint16_t upper_byte = (byte & 0x3Fu) << 8u;
const uint16_t lower_byte = registers_->temp_vram_addr & 0x00FFu;
registers_->temp_vram_addr = upper_byte | lower_byte;
const uint16_t lower_byte =
registers_->temp_vram_addr.value() & 0x00FFu;
registers_->temp_vram_addr = PpuVram(upper_byte | lower_byte);
registers_->write_toggle = true;
registers_->write_toggle = true;
}
} else if (addr == kPpuData) {
mmu_->write_byte(registers_->vram_addr, byte);
mmu_->write_byte(registers_->vram_addr.value(), byte);
increment_vram_address();
} else {
mmu_->write_byte(addr, byte);
@@ -267,7 +204,8 @@ uint8_t Ppu::get_vram_address_increment() const {
}
 
void Ppu::increment_vram_address() {
registers_->vram_addr += get_vram_address_increment();
registers_->vram_addr = PpuVram(
registers_->vram_addr.value() + get_vram_address_increment());
}
 
void Ppu::execute_pre_render_scanline() {
@@ -323,27 +261,31 @@ void Ppu::increase_scroll_counters() {
// are reloaded if rendering is enabled. vert(v) == vert(t)
if (scanline() == kPreRenderScanline) {
if (cycle() >= 280 && cycle() <= 304) {
registers_->vram_addr &= static_cast<uint16_t>(~0x7BE0u);
registers_->vram_addr |= (registers_->temp_vram_addr & 0x7BE0u);
auto addr = registers_->vram_addr.value();
addr &= static_cast<uint16_t>(~0x7BE0u);
addr |= (registers_->temp_vram_addr.value() & 0x7BE0u);
registers_->vram_addr = PpuVram(addr);
}
}
 
const bool should_increase_coarse_x =
!(cycle() == 0 || (cycle() >= 256 && cycle() <= 320));
if (should_increase_coarse_x && (cycle() % 8) == 0) {
registers_->vram_addr = increase_vram_coarse_x(registers_->vram_addr);
registers_->vram_addr.increase_coarse_x();
}
 
if (cycle() == 256) {
registers_->vram_addr = increase_vram_y(registers_->vram_addr);
registers_->vram_addr.increase_y();
} else if (cycle() == 257) {
// Copies all bits related to horizontal position from
// temporal to current vram_address
// From http://wiki.nesdev.com/w/index.php/PPU_scrolling
// If rendering is enabled, the PPU copies all bits related to
// horizontal position from t to v:
registers_->vram_addr &= ~0x41Fu;
registers_->vram_addr |= registers_->temp_vram_addr & 0x41Fu;
auto addr = registers_->vram_addr.value();
addr &= ~0x41Fu;
addr |= registers_->temp_vram_addr.value() & 0x41Fu;
registers_->vram_addr = PpuVram(addr);
}
}
void Ppu::fetch() {
@@ -351,7 +293,7 @@ void Ppu::fetch() {
(cycle() >= 321 && cycle() <= 336)) {
const uint16_t background_pattern_table_base_address =
(registers_->ctrl & 0b0001'0000u) ? 0x1000u : 0x0000u;
const uint8_t fine_scroll_y = get_fine_scroll_y(registers_->vram_addr);
const uint8_t fine_scroll_y = registers_->vram_addr.fine_scroll_y();
 
switch ((cycle() - 1) % 8) {
case 0: {
@@ -373,13 +315,13 @@ void Ppu::fetch() {
 
registers_->name_table = registers_->name_table_latch;
const uint16_t nametable_address =
get_nametable_address(registers_->vram_addr);
get_nametable_address(registers_->vram_addr.value());
registers_->name_table_latch = mmu_->read_byte(nametable_address);
break;
}
case 2: {
const uint16_t attribute_address =
get_attribute_address(registers_->vram_addr);
get_attribute_address(registers_->vram_addr.value());
uint8_t byte = mmu_->read_byte(attribute_address);
// Figure out which quadrant we are in and get the two corresponding
// bits.
@@ -388,8 +330,8 @@ void Ppu::fetch() {
// |||| ++--- Color bits 3-2 for top right quadrant of this byte
// ||++------ Color bits 3-2 for bottom left quadrant of this byte
// ++-------- Color bits 3-2 for bottom right quadrant of this byte
const uint8_t coarse_y = get_coarse_scroll_y(registers_->vram_addr);
const uint8_t coarse_x = get_coarse_scroll_x(registers_->vram_addr);
const uint8_t coarse_y = registers_->vram_addr.coarse_scroll_y();
const uint8_t coarse_x = registers_->vram_addr.coarse_scroll_x();
if (coarse_y % 4u >= 2u) {
// We are in the bottom quadrant
byte >>= 4u;
@@ -423,7 +365,7 @@ void Ppu::fetch() {
// at the beginning of the next scanline (tile 3 since tile 1 and 2
// were fetched already in the end of this scanline).
const uint16_t nametable_address =
get_nametable_address(registers_->vram_addr);
get_nametable_address(registers_->vram_addr.value());
registers_->name_table_latch = mmu_->read_byte(nametable_address);
}
}
 
core/test/src/ippu_helpers.cpp added: 278, removed: 137, total 141
@@ -28,8 +28,8 @@ void PrintTo(const PpuRegisters &r, std::ostream *os) {
r.mask,
r.oamaddr,
r.status,
r.vram_addr,
r.temp_vram_addr,
r.vram_addr.value(),
r.temp_vram_addr.value(),
r.write_toggle);
}
 
 
core/test/src/test_ppu.cpp added: 278, removed: 137, total 141
@@ -68,9 +68,9 @@ TEST_F(PpuTest, clear_status_when_reading_status) {
// Test from the example show at:
// https://wiki.nesdev.org/w/index.php/PPU_scrolling#Summary
TEST_F(PpuTest, scrolling_tests) {
registers.temp_vram_addr = 0b00001100'00000000u;
registers.vram_addr = 0b00000000'00000000u;
expected.temp_vram_addr = 0b00000000'00000000u;
registers.temp_vram_addr = PpuVram(0b00001100'00000000u);
registers.vram_addr = PpuVram(0b00000000'00000000u);
expected.temp_vram_addr = PpuVram(0b00000000'00000000u);
registers.write_toggle = expected.write_toggle = true;
 
ppu->write_byte(0x2000, 0x00);
@@ -80,23 +80,24 @@ TEST_F(PpuTest, scrolling_tests) {
ppu->read_byte(0x2002);
EXPECT_EQ(expected, registers);
 
expected.temp_vram_addr = 0b00000000'00001111u;
expected.temp_vram_addr = PpuVram(0b00000000'00001111u);
expected.fine_x_scroll = 0b101u;
expected.write_toggle = true;
ppu->write_byte(0x2005, 0b01111101u);
EXPECT_EQ(expected, registers);
 
expected.temp_vram_addr = 0b01100001'01101111u;
expected.temp_vram_addr = PpuVram(0b01100001'01101111u);
expected.write_toggle = false;
ppu->write_byte(0x2005, 0b01011110u);
EXPECT_EQ(expected, registers);
 
expected.temp_vram_addr = 0b00111101'01101111u;
expected.temp_vram_addr = PpuVram(0b00111101'01101111u);
expected.write_toggle = true;
ppu->write_byte(0x2006, 0b00111101u);
EXPECT_EQ(expected, registers);
 
expected.vram_addr = expected.temp_vram_addr = 0b00111101'11110000u;
expected.vram_addr = expected.temp_vram_addr =
PpuVram(0b00111101'11110000u);
expected.write_toggle = false;
ppu->write_byte(0x2006, 0b11110000u);
EXPECT_EQ(expected, registers);
@@ -184,7 +185,7 @@ TEST_F(PpuTest, clear_vblank_flag_during_pre_render_line) {
 
TEST_F(PpuTest, write_to_ctrl_register) {
expected.ctrl = 0xBA;
expected.temp_vram_addr = 0x800;
expected.temp_vram_addr = PpuVram(0x800);
 
ppu->write_byte(0x2000, 0xBA);
 
@@ -237,7 +238,7 @@ TEST_F(PpuTest, ignore_oamdata_during_pre_render_scanline) {
expected.oamaddr = registers.oamaddr;
expected.scanline = 0;
// Two increases when fetching two tiles for next scanline
expected.vram_addr = 0x0002;
expected.vram_addr = PpuVram(0x0002);
 
step_execution(kCyclesPerScanline);
 
@@ -279,7 +280,7 @@ TEST_F(PpuTest, write_and_read_oamdata_register) {
 
TEST_F(PpuTest, write_ppu_scroll_one_time) {
expected.fine_x_scroll = 0b110;
expected.temp_vram_addr = 0b0000'0000'0001'1101;
expected.temp_vram_addr = PpuVram(0b0000'0000'0001'1101);
expected.write_toggle = true;
 
ppu->write_byte(0x2005, 0b1110'1110);
@@ -289,7 +290,7 @@ TEST_F(PpuTest, write_ppu_scroll_one_time) {
 
TEST_F(PpuTest, write_ppu_scroll_two_times) {
expected.fine_x_scroll = 0b101;
expected.temp_vram_addr = 0b0111'0010'1000'0111;
expected.temp_vram_addr = PpuVram(0b0111'0010'1000'0111);
expected.write_toggle = false;
 
ppu->write_byte(0x2005, 0b0011'1101);
@@ -300,7 +301,7 @@ TEST_F(PpuTest, write_ppu_scroll_two_times) {
 
TEST_F(PpuTest, write_ppu_scroll_nametable_bits_not_overwritten) {
expected.ctrl = 0b0000'0011;
expected.temp_vram_addr = 0b00'1100'0001'1111;
expected.temp_vram_addr = PpuVram(0b00'1100'0001'1111);
expected.write_toggle = true;
 
ppu->write_byte(0x2000, 0b0000'0011);
@@ -310,8 +311,9 @@ TEST_F(PpuTest, write_ppu_scroll_nametable_bits_not_overwritten) {
}
 
TEST_F(PpuTest, write_ppu_addr_one_time) {
registers.temp_vram_addr = 0b0011'1111'0000'0000;
expected.temp_vram_addr = 0b0010'1101'0000'0000;
registers.temp_vram_addr = PpuVram(0b0011'1111'0000'0000);
expected.temp_vram_addr = PpuVram(0b0010'1101'0000'0000);
expected.temp_vram_addr = PpuVram(0b0010'1101'0000'0000);
expected.write_toggle = true;
 
ppu->write_byte(0x2006, 0b0010'1101);
@@ -320,8 +322,8 @@ TEST_F(PpuTest, write_ppu_addr_one_time) {
}
 
TEST_F(PpuTest, write_ppu_addr_two_times) {
registers.temp_vram_addr = 0b0010'1101'1111'1111;
expected.temp_vram_addr = 0b0010'1101'0110'0001;
registers.temp_vram_addr = PpuVram(0b0010'1101'1111'1111);
expected.temp_vram_addr = PpuVram(0b0010'1101'0110'0001);
expected.vram_addr = expected.temp_vram_addr;
expected.write_toggle = false;
 
@@ -332,7 +334,7 @@ TEST_F(PpuTest, write_ppu_addr_two_times) {
}
 
TEST_F(PpuTest, write_ppu_addr_ignores_highest_bits) {
expected.temp_vram_addr = 0b0010'1101'0000'0000;
expected.temp_vram_addr = PpuVram(0b0010'1101'0000'0000);
expected.write_toggle = true;
 
ppu->write_byte(0x2006, 0b1110'1101);
@@ -341,7 +343,7 @@ TEST_F(PpuTest, write_ppu_addr_ignores_highest_bits) {
}
 
TEST_F(PpuTest, increment_vram_addr_by_1_after_writing) {
expected.vram_addr = 0x01;
expected.vram_addr = PpuVram(0x01);
 
ppu->write_byte(0x2007, 0x05);
 
@@ -350,7 +352,7 @@ TEST_F(PpuTest, increment_vram_addr_by_1_after_writing) {
 
TEST_F(PpuTest, increment_vram_addr_by_32_after_writing) {
registers.ctrl = expected.ctrl = 0x04;
expected.vram_addr = 0x20;
expected.vram_addr = PpuVram(0x20);
 
ppu->write_byte(0x2007, 0x05);
 
@@ -358,13 +360,13 @@ TEST_F(PpuTest, increment_vram_addr_by_32_after_writing) {
}
 
TEST_F(PpuTest, forwards_ppudata_reads_to_mmu_) {
registers.vram_addr = 0x4001;
registers.vram_addr = PpuVram(0x4001);
EXPECT_CALL(mmu, write_byte(0x4001, 0x05)).Times(1);
 
ppu->write_byte(0x2007, 0x05);
}
TEST_F(PpuTest, read_vram_below_palette_memory_start) {
registers.vram_addr = 0x03;
registers.vram_addr = PpuVram(0x03);
 
EXPECT_CALL(mmu, read_byte(0x03)).WillOnce(Return(0x45));
const uint8_t first_read_byte = ppu->read_byte(0x2007);
@@ -377,7 +379,7 @@ TEST_F(PpuTest, read_vram_below_palette_memory_start) {
}
 
TEST_F(PpuTest, read_from_palette_memory) {
registers.vram_addr = 0x3F00;
registers.vram_addr = PpuVram(0x3F00);
 
EXPECT_CALL(mmu, read_byte(0x3F00)).WillOnce(Return(0x68));
EXPECT_CALL(mmu, read_byte(0x2F00)).WillOnce(Return(0x31));
@@ -386,7 +388,7 @@ TEST_F(PpuTest, read_from_palette_memory) {
 
EXPECT_EQ(0x68, read_byte);
 
registers.vram_addr = 0x0100;
registers.vram_addr = PpuVram(0x0100);
 
EXPECT_CALL(mmu, read_byte(0x0100)).WillOnce(Return(0x11));
 
@@ -396,7 +398,7 @@ TEST_F(PpuTest, read_from_palette_memory) {
}
 
TEST_F(PpuTest, increment_vram_addr_by_1_after_reading) {
expected.vram_addr = 0x0001;
expected.vram_addr = PpuVram(0x0001);
 
ppu->read_byte(0x2007);
 
@@ -405,7 +407,7 @@ TEST_F(PpuTest, increment_vram_addr_by_1_after_reading) {
 
TEST_F(PpuTest, increment_vram_addr_by_32_after_reading) {
registers.ctrl = expected.ctrl = 0x04;
expected.vram_addr = 0x0020;
expected.vram_addr = PpuVram(0x0020);
 
ppu->read_byte(0x2007);
 
@@ -418,7 +420,7 @@ TEST_F(PpuTest, visible_two_sub_cycles) {
 
expected.cycle = 17;
// Vram should be increased at cycle 8 and 16
expected.vram_addr = 0x0002;
expected.vram_addr = PpuVram(0x0002);
 
// Clear scrolling
ppu->write_byte(0x2005, 0);
@@ -469,7 +471,7 @@ TEST_F(PpuTest, visible_scanline) {
// vram addr: yyy NN YYYYY XXXXX
// Fine scroll should be increase once, and coarse x for each tile except
// the last (31x).
expected.vram_addr = 0b001'00'00000'11111;
expected.vram_addr = PpuVram(0b001'00'00000'11111);
 
// Clear scrolling
ppu->write_byte(0x2005, 0);
@@ -500,7 +502,7 @@ TEST_F(PpuTest, visible_scanline) {
EXPECT_EQ(expected, registers);
 
// During cycle 257 the horizontal bits should be reloaded.
expected.vram_addr = 0b001'00'00000'00000;
expected.vram_addr = PpuVram(0b001'00'00000'00000);
expected.cycle = 258;
 
ppu->execute(); // Cycle 257
@@ -516,7 +518,7 @@ TEST_F(PpuTest, visible_scanline) {
 
// Cycle 321-336.
// Fetch first two tiles for next scanline (fine scroll y=1)
expected.vram_addr = 0b001'00'00000'00010;
expected.vram_addr = PpuVram(0b001'00'00000'00010);
expected.cycle = 337;
// Nametables
EXPECT_CALL(mmu, read_byte(0x2000)).WillOnce(Return(0x02));
@@ -553,7 +555,7 @@ TEST_F(PpuTest, pre_render_two_sub_cycles) {
 
expected.cycle = 17;
// Vram should be increased at cycle 8 and 16
expected.vram_addr = 0x0002;
expected.vram_addr = PpuVram(0x0002);
 
// Clear scrolling
ppu->write_byte(0x2005, 0);
@@ -606,7 +608,7 @@ TEST_F(PpuTest, pre_render_scanline) {
// vram addr: yyy NN YYYYY XXXXX
// Fine scroll should be increase once, and coarse x for each tile except
// the last (31x).
expected.vram_addr = 0b001'00'00000'11111;
expected.vram_addr = PpuVram(0b001'00'00000'11111);
 
// Clear scrolling
ppu->write_byte(0x2005, 0);
@@ -637,7 +639,7 @@ TEST_F(PpuTest, pre_render_scanline) {
EXPECT_EQ(expected, registers);
 
// During cycle 257 the horizontal bits should be reloaded.
expected.vram_addr = 0b001'00'00000'00000;
expected.vram_addr = PpuVram(0b001'00'00000'00000);
expected.cycle = 258;
 
ppu->execute(); // Cycle 257
@@ -645,7 +647,7 @@ TEST_F(PpuTest, pre_render_scanline) {
 
// Cycle 258-320
// During cycle 280-304 the vertical scroll bits should be reloaded.
expected.vram_addr = 0b000'00'00000'00000;
expected.vram_addr = PpuVram(0b000'00'00000'00000);
expected.cycle = 321;
for (int i = 258; i <= 320; ++i) {
ppu->execute();
@@ -654,7 +656,7 @@ TEST_F(PpuTest, pre_render_scanline) {
 
// Cycle 321-336.
// Fetch first two tiles for next scanline.
expected.vram_addr = 0b000'00'00000'00010;
expected.vram_addr = PpuVram(0b000'00'00000'00010);
expected.cycle = 337;
// Nametables
EXPECT_CALL(mmu, read_byte(0x2000)).WillOnce(Return(0x02));
 
core/test/src/test_ppu_registers.cpp added: 278, removed: 137, total 141
@@ -17,4 +17,113 @@ TEST(PpuRegisters, is_rendering_enabled_returns_true_for_bit_three_and_four) {
EXPECT_TRUE(r.is_rendering_enabled());
}
 
TEST(PpuVram, construction) {
constexpr PpuVram kV{0xABCD};
EXPECT_EQ(0xABCD, kV.value());
 
PpuVram v2 = kV;
EXPECT_EQ(0xABCD, v2.value());
}
 
TEST(PpuVram, assignment) {
PpuVram v1{0x1234};
constexpr PpuVram kV2{0xABCD};
v1 = kV2;
EXPECT_EQ(0xABCD, v1.value());
}
 
TEST(PpuVram, equality) {
constexpr PpuVram kV1{0x1234};
constexpr PpuVram kV2{0x1234};
constexpr PpuVram kV3{0xABCD};
EXPECT_TRUE(kV1 == kV2);
EXPECT_TRUE(kV1 != kV3);
}
 
TEST(PpuVram, fine_scroll_y) {
constexpr PpuVram kV{0b101'00'00000'00000};
EXPECT_EQ(0b101, kV.fine_scroll_y());
}
 
TEST(PpuVram, set_fine_scroll_y) {
PpuVram v{0b000'00'00000'00000};
v.set_fine_scroll_y(0b0110'1101);
EXPECT_EQ(0b101, v.fine_scroll_y());
}
 
TEST(PpuVram, coarse_scroll_y) {
constexpr PpuVram kV{0b000'00'10101'00000};
EXPECT_EQ(0b10101, kV.coarse_scroll_y());
}
 
TEST(PpuVram, set_coarse_scroll_y) {
PpuVram v{0b000'00'00000'00000};
v.set_coarse_scroll_y(0b0110'1101);
EXPECT_EQ(0b01101, v.coarse_scroll_y());
}
 
TEST(PpuVram, coarse_scroll_x) {
constexpr PpuVram kV{0b000'00'00000'11001};
EXPECT_EQ(0b11001, kV.coarse_scroll_x());
}
 
TEST(PpuVram, set_coarse_scroll_x) {
PpuVram v{0b000'00'00000'00000};
v.set_coarse_scroll_x(0b0110'1101);
EXPECT_EQ(0b01101, v.coarse_scroll_x());
}
 
TEST(PpuVram, set_nametable) {
PpuVram v{0b000'00'00000'00000};
v.set_nametable(0b0110'0011);
EXPECT_EQ(0b000'11'00000'00000, v.value());
}
 
TEST(PpuVram, increase_coarse_x_increases_x) {
PpuVram v{0b000'00'00000'11001};
v.increase_coarse_x();
EXPECT_EQ(0b000'00'00000'11010, v.value());
v.increase_coarse_x();
EXPECT_EQ(0b000'00'00000'11011, v.value());
}
 
TEST(PpuVram, increase_coarse_x_switches_horizontal_nametable) {
PpuVram v{0b000'00'00000'11111};
v.increase_coarse_x();
EXPECT_EQ(0b000'01'00000'00000, v.value());
 
v = PpuVram(0b000'01'00000'11111);
v.increase_coarse_x();
EXPECT_EQ(0b000'00'00000'00000, v.value());
}
 
TEST(PpuVram, increase_y_increases_fine_y) {
PpuVram v{0b100'00'11001'00000};
v.increase_y();
EXPECT_EQ(0b101'00'11001'00000, v.value());
v.increase_y();
EXPECT_EQ(0b110'00'11001'00000, v.value());
}
 
TEST(PpuVram, increase_y_overflows_to_coarse_y) {
PpuVram v{0b111'00'00000'00000};
v.increase_y();
EXPECT_EQ(0b000'00'00001'00000, v.value());
}
 
TEST(PpuVram, increase_y_switches_vertical_nametable_for_last_row) {
PpuVram v{0b111'00'11101'00000}; // 0b11101 = 29 (last row)
v.increase_y();
EXPECT_EQ(0b000'10'00000'00000, v.value());
v = PpuVram(0b111'10'11101'00000);
v.increase_y();
EXPECT_EQ(0b000'00'00000'00000, v.value());
}
 
TEST(PpuVram, increase_y_resets_coarse_y_for_y_out_of_bounds) {
PpuVram v{0b111'10'11111'00000};
v.increase_y();
EXPECT_EQ(0b000'10'00000'00000, v.value());
}
 
} // namespace