Closed ale-79 closed 4 years ago
Thanks for that very nice analysis. I didn't look at AA the last few days and completely overlooked it. I'll look into this.
I finally found the description of the correct behaviour in the "MCS6500 family Hardware Manual", pag 78. https://archive.org/details/MCS6500_Family_Hardware_Manual/page/n89/mode/2up
After interrupt, whenever the timer is written or read the interrupt is reset. However, the reading or writing of the timer at the same time interrupt occurs will not reset the interrupt flag.
(Note that all the docs and datasheet I read before only referred to read accesses.)
Here are a few test roms: TimerTest.zip
The test consist in reading or writing the timer at the wraparound cycle and then read back its value after a variable number of cycles. In the middle column, the first read/write happens at the wraparound cycle. For comparision, in the left and right one it happens one cycle earlier or later. The first value on each column is read 11 cycles after the initial read/write access. Each subsequent value is read 5 cycles later than the previous one. I also tested by accessing the timer on subsequent wraparounds (with the interrupt already set), and it doesn't make any difference.
In Stella, the values in the middle column are different than real hardware, where the interrupt is set and the timer counts at 1T rate. This confirms the assumptions made in the first post, and in particular that point (3) is currently not implemented for read nor write accesses.
@ale-79, thanks for the continuing followup on this.
@DirtyHairy, any chance you will have some time to look at this soon? I suspect Stellerator will need to be updated too.
Stellerator emulates correctly for read accesses, but not for write ones.
Thanks alot for digging up the information in manual, @ale-79 ! I'm sorry for my current rather low activity, but I'll try to fix this the next few days.
6502.ts will follow soon 😏
I'm going to reopen this one, because we still need to consider the option to break when this occurs. Other than that it's complete, though.
I will look into the 'break' functionality; don't want to tie @DirtyHairy up with that.
:+1: The rom in the AA thread now displays 238 scanlines in Stella like observed on real hardware. Also the PAL60 "Alien 8k" conversions by Omegamatrix mentioned in this post now correctly show the inconsistent scanline count.
I decided to work on this. So, I added a debugger pseudo-register named timwrapped
that corresponds to M6532::myWrappedThisCycle
. Would this be sufficient? I guess what I'm really asking is, what functionality do we want for the break here? What condition should it actually be testing (and then breaking) on??
It should (optionally) break when the code reads a wrapped around timer.
Usual code reads the timer at the end of vertical blank and overscan. There it mostly checks for INTIM becoming 0 or negative. In the former case, the code wants to get the moment when INTIM changes from 1 to 0. Which seems tricky to catch. The latter case is easier. There the suggested break should be quite easy to implement.
So is what I describe above sufficient? This is the relevant code from M6532
:
void M6532::updateEmulation()
{
uInt32 cycles = uInt32(mySystem->cycles() - myLastCycle);
uInt32 subTimer = mySubTimer;
// Guard against further state changes if the debugger alread forwarded emulation
// state (in particular myWrappedThisCycle)
if (cycles == 0) return;
myWrappedThisCycle = false;
mySubTimer = (cycles + mySubTimer) % myDivider;
if ((myInterruptFlag & TimerBit) == 0)
{
uInt32 timerTicks = (cycles + subTimer) / myDivider;
if(timerTicks > myTimer)
{
cycles -= ((myTimer + 1) * myDivider - subTimer);
myWrappedThisCycle = cycles == 0;
myTimer = 0xFF;
myInterruptFlag |= TimerBit;
}
else
{
myTimer -= timerTicks;
cycles = 0;
}
}
if((myInterruptFlag & TimerBit) != 0) {
myTimer = (myTimer - cycles) & 0xFF;
myWrappedThisCycle = myTimer == 0xFF;
}
myLastCycle = mySystem->cycles();
}
The pseudo-register tracks the status of myWrappedThisCycle
. If the break is enabled, it breaks whenever myWrappedThisCycle
becomes true, which in the code above only happens when the "timer wraps".
Or should it be combined with an INTIM read, doing a break when myWrappedThisCycle
is true and a read is being done??
It must be combined, because the timer will wrap regularly. And timer reads will happen frequently too.
OK, that's a little more complex. I wonder now whether to combine the two conditions into the pseudo-register, or to do it at a higher level and combine it with an AND inside in the debugger class.
It would also help to have a TEST ROM. Would the ROM posted in this issue be a good test for this?
OK, I think I've found where read and write on timer wraparound can be computed. The former will happen inM6532::peek
for INTIM, and the latter in M6532::setTimerRegister
when setting the registers. This allows all the test ROMs above to detect these cases.
Should I separate this out, and add two pseudo-registers; one for read and one for write? Or is just detecting the condition sufficient, regardless of whether a read or write caused it? Also, I will come up with a better name for the pseudo-register, now that I understand what it's detecting.
Writes are irrelevant here. Only reads matter.
Well, the ROM in the initial post has problems because of writes. Or at least it's only triggering when I check on timer writes. And the ROMs in the ZIP from @ale-79 have read/write variants, and they trigger only on read/write, respectively.
I can easily only check on INTIM read, but it will then not detect some of the cases above. So that's why I was asking. This is yet another case of me being able to add code to do either, but not knowing what a developer would want (since I'm not a 2600 developer).
Yes, that's a very special case. After rereading the initial post, @ale-79 wants to catch the write which happens exactly at the wraparound cycle. This seems like a very rare corner case (it took me about 20 years of 2600 coding to experience it once), what I described above happens much more frequently. I won't object though, if you want to add another break option.
Much more important is, that the emulation is correct now.
Well, by the time I add the code to do the read version, it's only another 20 lines or so to add the write version. So I may as well do it, and have two different pseudo-registers. If nobody wants to use the write version, then that's fine. It will be there for the very esoteric cases where it's needed.
As Thomas said, what is most important is that the emulation is now correct, so that the game will (mis)behave on emulation if accessing the timer at those critical cycles like on real hardware.
Since it seems that it doesn't complicate the code too much, I'd like to have both read and write versions. But I'm not a 2600 developer either. Maybe a tinkerer... :stuck_out_tongue:
Full discussion here: https://atariage.com/forums/topic/303277-to-roll-or-not-to-roll/
The behaviour should be the following:
1- If the interrupt flag (bit 7 of TIMINT) is set, the programmed divider is ignored and the timer counts at 1T rate.
2- Whenever the timer wrapsaround from 0 to $FF, the interrupt flag is set. (if it was already set, it stays that way)
3- Writing or reading the timer clears the interrupt flag, unless the read/write happens at the exact wraparound cycle, in which case the flag is set afterwards.
(1) and (2) are already emulated. (3) is not emulated for write accesses. It might be already working for read accesses, as that part is in the 6532 datasheet (Needs verification)
TODO: