1 #ifdef PPU_CPP
2
latch_counters()3 void PPU::latch_counters() {
4 regs.hcounter = cpu.hdot();
5 regs.vcounter = cpu.vcounter();
6 regs.counters_latched = true;
7 }
8
get_vram_address()9 uint16 PPU::get_vram_address() {
10 uint16 addr = regs.vram_addr;
11 switch(regs.vram_mapping) {
12 case 0: break; //direct mapping
13 case 1: addr = (addr & 0xff00) | ((addr & 0x001f) << 3) | ((addr >> 5) & 7); break;
14 case 2: addr = (addr & 0xfe00) | ((addr & 0x003f) << 3) | ((addr >> 6) & 7); break;
15 case 3: addr = (addr & 0xfc00) | ((addr & 0x007f) << 3) | ((addr >> 7) & 7); break;
16 }
17 return (addr << 1);
18 }
19
20 //NOTE: all VRAM writes during active display are invalid. Unlike OAM and CGRAM, they will
21 //not be written anywhere at all. The below address ranges for where writes are invalid have
22 //been validated on hardware, as has the edge case where the S-CPU MDR can be written if the
23 //write occurs during the very last clock cycle of vblank.
24
vram_mmio_read(uint16 addr)25 uint8 PPU::vram_mmio_read(uint16 addr) {
26 uint8 data;
27
28 if(regs.display_disabled == true) {
29 data = memory::vram[addr];
30 } else {
31 uint16 v = cpu.vcounter();
32 uint16 h = cpu.hcounter();
33 uint16 ls = ((system.region() == System::NTSC ? 525 : 625) >> 1) - 1;
34 if(interlace() && !cpu.field()) ls++;
35
36 if(v == ls && h == 1362) {
37 data = 0x00;
38 } else if(v < (!overscan() ? 224 : 239)) {
39 data = 0x00;
40 } else if(v == (!overscan() ? 224 : 239)) {
41 if(h == 1362) {
42 data = memory::vram[addr];
43 } else {
44 data = 0x00;
45 }
46 } else {
47 data = memory::vram[addr];
48 }
49 }
50
51 return data;
52 }
53
vram_mmio_write(uint16 addr,uint8 data)54 void PPU::vram_mmio_write(uint16 addr, uint8 data) {
55 if(regs.display_disabled == true) {
56 memory::vram[addr] = data;
57 } else {
58 uint16 v = cpu.vcounter();
59 uint16 h = cpu.hcounter();
60 if(v == 0) {
61 if(h <= 4) {
62 memory::vram[addr] = data;
63 } else if(h == 6) {
64 memory::vram[addr] = cpu.regs.mdr;
65 } else {
66 //no write
67 }
68 } else if(v < (!overscan() ? 225 : 240)) {
69 //no write
70 } else if(v == (!overscan() ? 225 : 240)) {
71 if(h <= 4) {
72 //no write
73 } else {
74 memory::vram[addr] = data;
75 }
76 } else {
77 memory::vram[addr] = data;
78 }
79 }
80 }
81
82 //NOTE: OAM accesses during active display are rerouted to 0x0218 ... this can be considered
83 //a hack. The actual address varies during rendering, as the S-PPU reads in data itself for
84 //processing. Unfortunately, we have yet to determine how this works. The algorithm cannot be
85 //reverse engineered using a scanline renderer such as this, and at this time, there does not
86 //exist a more accurate SNES PPU emulator to work from. The only known game to actually access
87 //OAM during active display is Uniracers. It expects accesses to map to offset 0x0218.
88 //It was decided by public consensus to map writes to this address to match Uniracers, primarily
89 //because it is the only game observed to do this, but also because mapping to this address does
90 //not contradict any of our findings, because we have no findings whatsoever on this behavior.
91 //Think of this what you will, I openly admit that this is a hack. But it is more accurate than
92 //writing to the 'expected' address set by $2102,$2103, and will catch problems in software that
93 //accidentally accesses OAM during active display by virtue of not returning the expected data.
94
oam_mmio_read(uint16 addr)95 uint8 PPU::oam_mmio_read(uint16 addr) {
96 addr &= 0x03ff;
97 if(addr & 0x0200) addr &= 0x021f;
98 uint8 data;
99
100 if(regs.display_disabled == true) {
101 data = memory::oam[addr];
102 } else {
103 if(cpu.vcounter() < (!overscan() ? 225 : 240)) {
104 data = memory::oam[0x0218];
105 } else {
106 data = memory::oam[addr];
107 }
108 }
109
110 return data;
111 }
112
oam_mmio_write(uint16 addr,uint8 data)113 void PPU::oam_mmio_write(uint16 addr, uint8 data) {
114 addr &= 0x03ff;
115 if(addr & 0x0200) addr &= 0x021f;
116
117 sprite_list_valid = false;
118
119 if(regs.display_disabled == true) {
120 memory::oam[addr] = data;
121 } else {
122 if(cpu.vcounter() < (!overscan() ? 225 : 240)) {
123 memory::oam[0x0218] = data;
124 } else {
125 memory::oam[addr] = data;
126 }
127 }
128 }
129
130 //NOTE: CGRAM writes during hblank are valid. During active display, the actual address the
131 //data is written to varies, as the S-PPU itself changes the address. Like OAM, we do not know
132 //the exact algorithm used, but we have zero known examples of any commercial software that
133 //attempts to do this. Therefore, the addresses are mapped to 0x01ff. There is nothing special
134 //about this address, it is simply more accurate to invalidate the 'expected' address than not.
135
cgram_mmio_read(uint16 addr)136 uint8 PPU::cgram_mmio_read(uint16 addr) {
137 addr &= 0x01ff;
138 uint8 data;
139
140 if(regs.display_disabled == true) {
141 data = memory::cgram[addr];
142 } else {
143 uint16 v = cpu.vcounter();
144 uint16 h = cpu.hcounter();
145 if(v < (!overscan() ? 225 : 240) && h >= 128 && h < 1096) {
146 data = memory::cgram[0x01ff] & 0x7f;
147 } else {
148 data = memory::cgram[addr];
149 }
150 }
151
152 if(addr & 1) data &= 0x7f;
153 return data;
154 }
155
cgram_mmio_write(uint16 addr,uint8 data)156 void PPU::cgram_mmio_write(uint16 addr, uint8 data) {
157 addr &= 0x01ff;
158 if(addr & 1) data &= 0x7f;
159
160 if(regs.display_disabled == true) {
161 memory::cgram[addr] = data;
162 } else {
163 uint16 v = cpu.vcounter();
164 uint16 h = cpu.hcounter();
165 if(v < (!overscan() ? 225 : 240) && h >= 128 && h < 1096) {
166 memory::cgram[0x01ff] = data & 0x7f;
167 } else {
168 memory::cgram[addr] = data;
169 }
170 }
171 }
172
173 #endif
174