09-wave read while on
If the wave channel is enabled, accessing any byte from $FF30-$FF3F is equivalent to accessing the current byte selected by the waveform position. Further, on the DMG accesses will only work in this manner if made within a couple of clocks of the wave channel accessing wave RAM; if made at any other time, reads return $FF and writes have no effect.
Is that really the best we can get in documentation? "Yeah, this thing happens, at some point, probably. What's it to you?"
Code: Select all
12557968: period=103
12558071: fetch
12558071: 00
12558073: fetch
12558075: fetch
12558077: fetch
12558079: fetch
12558081: fetch
12559850: period=102
12559952: fetch
12559953: ff
12559954: fetch
12559956: fetch
12559958: fetch
12559960: fetch
12559962: fetch
12561706: period=101
12561807: fetch
12561809: fetch
12561809: 00
12561811: fetch
12561813: fetch
12561815: fetch
12561817: fetch
12563558: period=100
12563658: fetch
12563660: fetch
12563661: ff
12563662: fetch
12563664: fetch
12563666: fetch
12563668: fetch
12565324: period=99
12565423: fetch
12565425: fetch
12565427: fetch
12565427: 11
12565429: fetch
12565431: fetch
12565433: fetch
12567086: period=98
12567184: fetch
12567186: fetch
12567188: fetch
12567189: ff
12567190: fetch
12567192: fetch
12567194: fetch
12568912: period=97
12569009: fetch
12569011: fetch
12569013: fetch
12569015: fetch
12569015: 11
12569017: fetch
12569019: fetch
12570752: period=96
12570848: fetch
12570850: fetch
12570852: fetch
12570854: fetch
12570855: ff
12570856: fetch
12570858: fetch
First off, the timing on this is obscenely brutal. You don't have to be exact on cycles, you have to be exact to a single clock tick. This kind of absolute, psychopathic perfection doesn't belong in an otherwise general test suite. Especially when the entirety of the documentation consists of a single comment in a source file:
Code: Select all
; Reads from wave RAM while playing, each time 2 clocks later.
Just to get the <pass, fail, pass, fail> instead of <fail, pass, fail, pass> required changing the CPU read handler to step 2 clock ticks, read, then step 2 clock ticks. The same kind of obscene timing that the SNES requires for latching counters.
Next up, the test is interleaved so that more and more wave RAM fetches happen before the read. You basically have to have the CPU read on the exact same cycle that the APU fetches the byte. One cycle later and on the DMG, it expects to get back FF instead. So it's not "within a couple of clocks", it's literally "instantly." One single tick of the 2MHz APU, and the value's gone and back to reading FF. Short of a sadistic test ROM like this, reading back the wave RAM would be nigh impossible while the channel was running.
When enabling the channel, you set uint5 patternOffset=0, and when you run the wave channel and the timer hits zero, then you want:
Code: Select all
if(!(patternOffset & 1)) {
patternByte = pattern[patternOffset >> 1];
}
patternOffset++;
For computing an output sample inside run:
Code: Select all
uint4 patternSample = patternByte >> (patternOffset & 1 ? 0 : 4);
uint4 sample = patternSample >> shift[volume];
Then for reading from wave RAM:
Code: Select all
if(!system.cgb() && enable && (!patternAccess || !patternClocked)) result = 0xff;
else if(enable) result = patternByte;
else result = pattern[addr & 15];
That gets the GBC version of the test to pass, and misses only the first byte. There's almost no combination of setting the initial pattern offset to {30, 31, 0, 1}, setting the CPU clocks to {0,4; 1,3; 2,2; 3,1; 4,0}, setting the wave RAM reads to ((patternOffset-N)&31)>>1, using ++patternOffset vs patternOffset++ in wave::run, etc that can counter the fact that with this test ROM as it is, the first pattern RAM fetch from wave::run happens before the first CPU read from pattern RAM. The only workable solution is for CPU::read to be { add_clocks(1); read(); add_clocks(3); } plus making my CPU sync to APU on clock<=0 (instead of <0), and APU to CPU on clock >=0, which I don't believe for a second to be correct. Plus, {1,3} pushes the tick evens back to <pass,fail,pass,fail>, which won't pass the test.
The only thing I can do to pass the DMG test is an absolutely disgusting hack where "if the period has only expired once AND the APU is on the same clock position both when the period expired AND when the palette RAM was read by the CPU, force the value to 0xFF". But I don't know whether that 0xFF value is secretly "waveRAM[15] 0xFF" or "can't access, busy, 0xFF".
There's probably some special channel enable delays that aren't documented anywhere. Both Gb_Snd_Emu and libgambatte are full of disgusting speed hacks that play fast and loose with time shifting, making it completely impossible to reason about what's going on, timing-wise.
There's a possibility I have a DMG opcode whose cycle timings (eg read, read, i/o vs read, i/o, read) are incorrect. But I pass all the tests from gekkio around this that I know of, and it's not documented anywhere else, and there are no other test ROMs for it, so ... if that's the case, I'd be helpless to find it.