+stephena Posted September 11, 2012 Share Posted September 11, 2012 I'm looking to improve emulation of Stella wrt INTIM/TIMINT. Almost all the test ROMs I have are working correctly, but a few cases do not. As well, all the test ROMs only cover a small portion of the values. What I'd like is to have someone provide test ROMs (Batari Basic preferred) that cycle through all combinations of reading and writing these registers, to see where Stella can be improved. Can someone offer some advice and/or help in this area? Quote Link to comment Share on other sites More sharing options...
RevEng Posted September 11, 2012 Share Posted September 11, 2012 bB may be a bit problematic, as it uses INTIM/TIM64T for various parts of the display. If you're looking for score code to display values, that's not a problem. If you go into a bit more detail, I'd be glad to code something up. Quote Link to comment Share on other sites More sharing options...
+stephena Posted September 12, 2012 Author Share Posted September 12, 2012 It doesn't absolutely need to be in bB; I only suggested that since I could more easily edit it if necessary. My main goal is to create some sort of ROM that sets all possible values of TIM1T, TIM8T, etc, and reads from INTIM and TIMINT and shows the results (it doesn't have to be all done in one ROM, of course). The ROM will somehow know what the correct values should be, and stop if it finds a value that doesn't match what the real hardware would produce. Basically, I'd then run these ROMs in Stella, and if it doesn't encounter any errors I could be certain that the timer emulation is working. My main concern is finding discrepancies with real hardware; how the ROM works and actually presents the results to the user (ie, me) is up to the programmer. EDIT: I'm also open to a discussion of how the timer actually works, since the documentation I have is either lacking, incomplete or contradictory. And based on my understanding of it, it seems Stella is doing what it should, but sometimes the results don't match real hardware. On top of that, I'm not entirely convinced that my console isn't wonky. Arrrgh, too many variables ... Quote Link to comment Share on other sites More sharing options...
Omegamatrix Posted September 12, 2012 Share Posted September 12, 2012 Per our discussion, some wonky things seem to be going on with TIMINT on real hardware. For one pressing right on the joystick is affecting bit 7. So here are two roms created from the same source. Inside the source is a switch to have these two cases: ;Case 1 ldx #0 .waitVblank: lda INTIM bpl .waitVblank sta WSYNC ;--------------------------------------- stx VBLANK lda TIMINT And this: ;Case 2 ldx #0 .waitVblank: lda TIMINT ; this is the only difference! bpl .waitVblank sta WSYNC ;--------------------------------------- stx VBLANK lda TIMINT The test rom I made reads TIMINT right after the timer is loaded, and after it runs out. It does this every frame. Case 1 does not have bit 7 of TIMINT set in the "after" value. Bizarre. This was test on my Jr. Stephena tested it on his light sixer and also got the same result. Stella screenshot, with "testTIMINT(_bpl_INTIM).bin" showing the after value as 0x80, as one would expect.... When playing these roms on real hardware press right on the joystick. See how the bits change? Also note I never read the joystick anywhere in the rom. testTIMINT.zip Quote Link to comment Share on other sites More sharing options...
SeaGtGruff Posted September 12, 2012 Share Posted September 12, 2012 What kind of console do you have? Since the timer is part of the RIOT chip, I'd expect its behavior to be consistent for all flavors of the 2600 console-- unless some of them didn't use the standard 6532 RIOT chip? The best documentation for the way the timer *should* work would be a document about the 6532 RIOT chip. Following is a brief description of the basics-- as I understand them, anyway. If anyone knows something different, please correct me! (1) The timer is set by writing to TIM1T, TIM8T, TIM64T, or T1024T. On the cycle after the write instruction completes, the timer will contain the value written to it, and the timer interrupt flag will be cleared. On the next cycle, the value in the timer will be decremented by 1. Then it will (normally) be decremented by 1 at intervals of 1, 8, 64, or 1024 cycles, depending on which register you wrote to. (2) When the timer reaches 0, it will stay at 0 for the chosen interval. Then, when it decrements from 0 to 255, the timer interrupt flag will be set, and the timer will start to decrement by 1 every cycle. (3) The only exception I know of for (1) would be if you set the timer to 0-- which I don't think anyone would do on purpose in normal circumstances-- because then the timer would immediately decrement from 0 to 255, thereby setting the flag and causing the timer to continue decrementing every cycle. For example, suppose you set the timer to 1. Depending on which register you wrote to, the results should be as follows: LDA #1 ; cycles -6, -5 STA TIM1T ; cycles -4, -3, -2, -1 cycle 0 -- timer = 1, flag = 0 cycle 1 -- timer = 0, flag = 0 cycle 2 -- timer = 255, flag = 1 cycle 3 -- timer = 254, flag = 1 etc. LDA #1 ; cycles -6, -5 STA TIM8T ; cycles -4, -3, -2, -1 cycle 0 -- timer = 1, flag = 0 cycle 1 -- timer = 0, flag = 0 cycle 2 -- timer = 0, flag = 0 ... cycle 8 -- timer = 0, flag = 0 cycle 9 -- timer = 255, flag = 1 cycle 10 -- timer = 254, flag = 1 etc. LDA #1 ; cycles -6, -5 STA TIM64T ; cycles -4, -3, -2, -1 cycle 0 -- timer = 1, flag = 0 cycle 1 -- timer = 0, flag = 0 cycle 2 -- timer = 0, flag = 0 ... cycle 64 -- timer = 0, flag = 0 cycle 65 -- timer = 255, flag = 1 cycle 66 -- timer = 254, flag = 1 etc. LDA #1 ; cycles -6, -5 STA T1024T ; cycles -4, -3, -2, -1 cycle 0 -- timer = 1, flag = 0 cycle 1 -- timer = 0, flag = 0 cycle 2 -- timer = 0, flag = 0 ... cycle 1024 -- timer = 0, flag = 0 cycle 1025 -- timer = 255, flag = 1 cycle 1026 -- timer = 254, flag = 1 etc. Note that there are two registers for setting each timer interval: TIM1T = $0294 TIM8T = $0295 TIM64T = $0296 T1024T = $0297 TIM1I = $029C TIM8I = $029D TIM64I = $029E T1024I = $029F The labels I've given for $029C through $029F are my own-- as far as I know, they have no official labels. The difference between them is that $0294 through $0297 set the timer and disable the IRQ timer interrupt, whereas $029C through $029F set the timer and enable the IRQ timer interrupt (which is why I use an "I" at the end of the labels for $029C through $029F-- "I" is for "IRQ" or "interrupt"). But since the 6507 doesn't have a pin for the IRQ signal, it makes no difference on the 2600 whether you write to $0294 through $0297 or to $029C through $029F. Also, enabling or disabling the IRQ timer interrupt has no effect on the timer interrupt flag itself-- it gets cleared and set the same either way, regardless of whether it will trigger an IRQ interrupt or not. Setting the timer is actually pretty straightforward. However, checking the timer is more complicated. For one thing, different programmers have different preferences as to how they check the timer-- by reading the timer itself, or by reading the timer flag. If you read the flag, you just test whether TIMINT is positive or negative, since the timer flag is in bit 7 of TIMINT. So if TIMINT is positive, the timer hasn't wrapped around from 0 to 255 yet-- but if TIMINT is negative, then the timer has wrapped around from 0 to 255: Waitforit BIT TIMINT BPL Waitforit ; else the timer has finished counting down On the other hand, if you read the timer itself, then you test whether its value is 0, positive, or negative-- or you might compare its value to some quantity to see if it's less than, less than or equal to, equal to, greater than or equal to, or greater than the desired quantity, by using the appropriate combination of BEQ, BNE, BPL, BMI, BCC, or BCS branches. Reading the timer itself can have some benefits, which is why some programmers prefer that method, but you also have to be sure you pay attention to what you're doing. For example, if you set the timer to some value greater than 128, then checking to see when it becomes negative might give a false reading, since its value might already be negative (greater than 127). There's one thing to keep in mind when reading the timer itself-- writing or reading the timer clears the timer flag. So if you read the timer, you can't rely on the timer flag afterward, since it might have been set sometime before you read the timer, then got cleared when you read the timer. Using the timer can be more complicated than I've described, because once you've detected that the timer flag has been set-- either because you read TIMINT and it was negative, or because (on a non-6507 machine) the timer interrupt was triggered-- it's possible to read the timer to determine how long it's been since the timer flag was set. This assumes you're reading the timer shortly after the timer flag was set, such that the timer hasn't had time to wrap around from 0 to 255 again. But I don't think any 2600 programmers use that technique (although I could be mistaken). Quote Link to comment Share on other sites More sharing options...
SeaGtGruff Posted September 12, 2012 Share Posted September 12, 2012 Edit: I guess the term "timer interrupt flag" is misleading, since the actual interrupt flag is bit 6 of TIMINT, and bit 7 is just the timer flag. So everywhere I said "timer interrupt flag," it should have been just "timer flag." Quote Link to comment Share on other sites More sharing options...
SeaGtGruff Posted September 12, 2012 Share Posted September 12, 2012 Per our discussion, some wonky things seem to be going on with TIMINT on real hardware. For one pressing right on the joystick is affecting bit 7. Hmm, I haven't seen that before-- but then I've never had any reason to check it. Quote Link to comment Share on other sites More sharing options...
SeaGtGruff Posted September 12, 2012 Share Posted September 12, 2012 There's one thing to keep in mind when reading the timer itself-- writing or reading the timer clears the timer flag. So if you read the timer, you can't rely on the timer flag afterward, since it might have been set sometime before you read the timer, then got cleared when you read the timer. This may be part of the problem/s that was/were described. The Commodore Semiconductor Group NMOS documentation for the 6532 (Memory, I/O, Timer Array) states that "The timer flag is cleared when the timer register is either written or read." (See the bottom of page 9 of the PDF at the link below.) So the correct emulation behavior should be that the timer flag (bit 7 of TIMINT) gets cleared whenever INTIM is read. I presume that if you read INTIM *before* the timer flag gets set, and then don't read it again, the timer flag will be set as expected when the timer first wraps around. But if you keep reading INTIM until after the timer has wrapped around, the timer flag should be 0 because it got cleared by reading INTIM. As for the issue reported with pushing the joystick right-- I have no idea! See "6532 Memory, I/O, Timer Array (Feb. 1981)" at http://www.6502.org/documents/datasheets/mos/ Quote Link to comment Share on other sites More sharing options...
+stephena Posted September 12, 2012 Author Share Posted September 12, 2012 OK, my progress so far. First of all, here's the current code from Stella that deals with INTIM and TIMINT reads and writes (sorry for the formatting): In the 'peek' method: ... case 0x04: // Timer Output case 0x06: { // Update timer state and return the resulting clock return updateTimer(); } case 0x05: // Interrupt Flag case 0x07: { // Update timer state and return the resulting flag(s) updateTimer(); return myInterruptFlag; } In the 'poke' method: // A2 distinguishes I/O registers from the timer if((addr & 0x04) != 0) { if((addr & 0x10) != 0) setTimerRegister(value, addr & 0x03); } And the associated methods called from above: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void M6532::setTimerRegister(uInt8 value, uInt8 interval) { static const uInt8 shift[] = { 0, 3, 6, 10 }; myIntervalShift = shift[interval]; myOutTimer[interval] = value; myTimer = value << myIntervalShift; myCyclesWhenTimerSet = mySystem->cycles(); // Interrupt timer flag is reset when writing to the timer myInterruptFlag &= ~TIMER_BIT; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt8 M6532::updateTimer() { // Get number of clocks since timer was set Int32 timer = timerClocks(); if(timer >= 0) { // Timer hasn't expired yet myInterruptFlag &= ~TIMER_BIT; return (timer >> myIntervalShift) & 0xff; } else { // Timer has expired, set flag myInterruptFlag |= TIMER_BIT; // According to the M6532 documentation, the timer continues to count // down to -255 timer clocks after wraparound. However, it isn't // entirely clear what happens *after* if reaches -255. // For now, we'll let it continuously wrap around. return timer & 0xff; } } Also, you're correct that the enable/disable IRQ stuff isn't used on the 2600, so I've completely removed any reference to it. Also correct that the register is called 'interrupt flag register', and that D7 contains the timer flag and D6 contains PA7 stuff. As for the PA7 stuff, note that this is related to the right joystick direction; they both map to the same signal. More research is required here. At this point I'm lost ... Quote Link to comment Share on other sites More sharing options...
+stephena Posted September 12, 2012 Author Share Posted September 12, 2012 The labels I've given for $029C through $029F are my own-- as far as I know, they have no official labels. The difference between them is that $0294 through $0297 set the timer and disable the IRQ timer interrupt, whereas $029C through $029F set the timer and enable the IRQ timer interrupt (which is why I use an "I" at the end of the labels for $029C through $029F-- "I" is for "IRQ" or "interrupt"). But since the 6507 doesn't have a pin for the IRQ signal, it makes no difference on the 2600 whether you write to $0294 through $0297 or to $029C through $029F. Also, enabling or disabling the IRQ timer interrupt has no effect on the timer interrupt flag itself-- it gets cleared and set the same either way, regardless of whether it will trigger an IRQ interrupt or not. The reason for this is the first range of values has A3=0, and the second has A3=1. And A3 is the enable/disable bit for the external IRQ pin. But since (as you say) the IRQ line isn't hooked up to the 6507 (and the schematics confirm this), I just left it out entirely. Also correct (or at least I think) that the external IRQ line has nothing to do with D7 of the interrupt flag. Quote Link to comment Share on other sites More sharing options...
Omegamatrix Posted September 12, 2012 Share Posted September 12, 2012 There's one thing to keep in mind when reading the timer itself-- writing or reading the timer clears the timer flag. So if you read the timer, you can't rely on the timer flag afterward, since it might have been set sometime before you read the timer, then got cleared when you read the timer. I agree with everything you said, and this was the key point that I didn't know about. This would explain why the flag was cleared in my rom. I was reading INTIM and BPL, so it must have cleared the flag. This will be easy to test for. As for pushing right on the joystick I would like to see people try those roms I posted. Note that it is not a press and hold action. It's press, release, press, release, and so on. I had it influence the values on my Jr and Stephena was able to repeat it on his light sixer. We need some more results from different consoles. I will look at my chip sometime over the next few days and get the manufacture and serial numbers. Jeff Quote Link to comment Share on other sites More sharing options...
+stephena Posted September 12, 2012 Author Share Posted September 12, 2012 I agree with everything you said, and this was the key point that I didn't know about. This would explain why the flag was cleared in my rom. I was reading INTIM and BPL, so it must have cleared the flag. This will be easy to test for. Still not sure why it's happening in Stella, but I have a better idea now what might be going on. As for pushing right on the joystick I would like to see people try those roms I posted. Note that it is not a press and hold action. It's press, release, press, release, and so on. I had it influence the values on my Jr and Stephena was able to repeat it on his light sixer. We need some more results from different consoles. I will look at my chip sometime over the next few days and get the manufacture and serial numbers. According to the 6532 specs, related to PA7 there is edge-detect logic that is only triggered when going from low-high or high-low transition, and this somehow sets D6. And since we're only seeing it on a press-release cycle (aka, a transition), it seems likely to be related. This isn't emulated by Stella at all, and not by any other emulator either AFAIK. I'd prefer to get the INTIM and TIMINT behaviour tested and verified in Stella before I look into this. Quote Link to comment Share on other sites More sharing options...
RevEng Posted September 13, 2012 Share Posted September 13, 2012 My main goal is to create some sort of ROM that sets all possible values of TIM1T, TIM8T, etc, and reads from INTIM and TIMINT and shows the results (it doesn't have to be all done in one ROM, of course). The ROM will somehow know what the correct values should be, and stop if it finds a value that doesn't match what the real hardware would produce. So it sets the TIM* timers with various values and then... a) reads INTIM and TIMINT right away? b) does something to waste some time, and then reads INTIM and TIMINT? c) reads INTIM until the timer is done, per usual, and then reads INTIM AND TIMINT? ...got a framework ready with display for all 4 TIM* timers. Just need to define the test. Quote Link to comment Share on other sites More sharing options...
SeaGtGruff Posted September 13, 2012 Share Posted September 13, 2012 The reason for this is the first range of values has A3=0, and the second has A3=1. And A3 is the enable/disable bit for the external IRQ pin. But since (as you say) the IRQ line isn't hooked up to the 6507 (and the schematics confirm this), I just left it out entirely. Also correct (or at least I think) that the external IRQ line has nothing to do with D7 of the interrupt flag. Yes, the RIOT addresses are a bit more complicated than the TIA addresses, because different registers use or ignore different address lines, then there are the chip select lines, R/W line, and RAM select line. Following are the primary RIOT addresses on the 2600 as I've determined them from the 6532 documentation, divided into general categories, along with the labels I've assigned to any RIOT registers that are usually ignored by 2600 programmers: ; RIOT I/O Addresses SWCHA = $0280 SWACNT = $0281 SWCHB = $0282 SWBCNT = $0283 ; RIOT Write Addresses DISNED = $0284 ; DISable Negative Edge Detection DISPED = $0285 ; DISable Positive Edge Detection ENANED = $0286 ; ENAble Negative Edge Detection ENAPED = $0287 ; ENAble Positive Edge Detection TIM1T = $0294 TIM8T = $0295 TIM64T = $0296 T1024T = $0297 TIM1I = $029C ; set TIMer, 1-cycle interval, Interrupt enabled TIM8I = $029D ; set TIMer, 8-cycle interval, Interrupt enabled TIM64I = $029E ; set TIMer, 64-cycle interval, Interrupt enabled T1024I = $029F ; set TIMer, 1024-cycle interval, Interrupt enabled ; RIOT Read Addresses INTIM = $0284 TIMINT = $0285 INTIMI = $028C ; read INterval TIMer, Interrupt enabled The mirroring of the addresses is rather complex, due to the way some of the address lines are ignored. I haven't indicated any of the mirror addresses, but they ought to be recognized by Stella for the emulation to be totally accurate (hopefully this is already being done correctly). I haven't tried to follow the Stella source code to see what's going on with the TIMINT register, but I'm wondering how-- or rather, when-- Stella determines what the value of TIMINT should be? Is Stella setting the timer flag in TIMINT in "real time" when the timer actually rolls over, or does it wait until the programmer reads TIMINT and then calls a function to determine whether the timer has rolled over yet? If it's the latter case, that's probably what's causing the problem. In that case, you could probably fix it by using a separate flag to indicate whether the timer flag should be set or not, something like the following (in pseudocode): declare ignore_timer_flag initialize ignore_timer_flag to 0 when INTIM is read call the function that determines whether the timer has rolled over if the timer has already rolled over then set ignore_timer_flag to 1 else clear ignore_timer_flag endif return the value of the timer when TIMINT is read if ignore_timer_flag is 1 then clear the timer flag else if the timer flag is 0 then call the function that determines whether the timer has rolled over if the timer has rolled over then set the timer flag endif endif return the value of the timer flag That's just a first rough attempt at expressing the logic, so it might not be correct, but I hope you can see what I'm getting at. Again, this code is based on the assumption that Stella doesn't try to set the timer flag in "real time," but instead waits until the 2600 program reads the timer flag register and then calls some function to determine what the state of the timer flag should be. Note that if the timer is read on the same cycle that the timer rolls over, the timer flag will be set as normal-- that is, reading the timer doesn't clear the timer flag as it would in other cases. I guess it may be a matter of priority-- reading the timer may in fact clear the timer flag as usual, but then the fact that the timer just rolled over causes the timer flag to be set. Once the timer rolls over and the timer flag is set, it will remain set until the timer is written or read-- that is, reading the timer flag doesn't clear the timer flag (although reading the TIMINT register *does* clear the interrupt flag in bit 6). Also, I believe the timer will continue decrementing and rolling over again after it has already rolled over-- that is, once it rolls over, sets the timer flag, and starts to decrement every cycle, it does not stop at 0 and stay at 0, but keeps rolling over from 0 to 255. I hope this wasn't too muddled! Quote Link to comment Share on other sites More sharing options...
Omegamatrix Posted September 13, 2012 Share Posted September 13, 2012 Note that if the timer is read on the same cycle that the timer rolls over, the timer flag will be set as normal-- that is, reading the timer doesn't clear the timer flag as it would in other cases. I guess it may be a matter of priority-- reading the timer may in fact clear the timer flag as usual, but then the fact that the timer just rolled over causes the timer flag to be set. Once the timer rolls over and the timer flag is set, it will remain set until the timer is written or read-- that is, reading the timer flag doesn't clear the timer flag (although reading the TIMINT register *does* clear the interrupt flag in bit 6). Also, I believe the timer will continue decrementing and rolling over again after it has already rolled over-- that is, once it rolls over, sets the timer flag, and starts to decrement every cycle, it does not stop at 0 and stay at 0, but keeps rolling over from 0 to 255. I hope this wasn't too muddled! I'm starting to get confused in the conversation between when we are talking about reading the timer (INTIM), and reading the timer flag (TIMINT). Quote Link to comment Share on other sites More sharing options...
Omegamatrix Posted September 13, 2012 Share Posted September 13, 2012 Here is another test rom I had written, which I just modified now to read INTIM on the first cycle the timer rolls over. Then TIMINT is checked right after. You can also move the "delay" cycle to when INTIM is read to +255. I haven't tested this on real hardware yet. testTIMINT(withDelay).zip Quote Link to comment Share on other sites More sharing options...
+stephena Posted September 13, 2012 Author Share Posted September 13, 2012 The mirroring of the addresses is rather complex, due to the way some of the address lines are ignored. I haven't indicated any of the mirror addresses, but they ought to be recognized by Stella for the emulation to be totally accurate (hopefully this is already being done correctly). Responding to mirror addresses has always been there. Currently, the functionality of the RIOT that isn't used by a real console isn't emulated at all (this means external IRQ and D6 interrupt flag). I think it's safe to forget about the IRQ stuff, but I think the D6/PA7 stuff should be added at some point, since I think that's where the 'flickering' digits in the test ROM above is coming from. I haven't tried to follow the Stella source code to see what's going on with the TIMINT register, but I'm wondering how-- or rather, when-- Stella determines what the value of TIMINT should be? Is Stella setting the timer flag in TIMINT in "real time" when the timer actually rolls over, or does it wait until the programmer reads TIMINT and then calls a function to determine whether the timer has rolled over yet? If it's the latter case, that's probably what's causing the problem. In that case, you could probably fix it by using a separate flag to indicate whether the timer flag should be set or not, something like the following (in pseudocode): I was thinking about this last night and came to a similar conclusion. The current code definitely doesn't do things in real time, as that would be much slower (and require threading, etc). It checks the state at the time the programmer needs it (aka, when reading TIMINT), and I think you're right in that this is where the problem is. Quote Link to comment Share on other sites More sharing options...
+stephena Posted September 13, 2012 Author Share Posted September 13, 2012 So it sets the TIM* timers with various values and then... a) reads INTIM and TIMINT right away? b) does something to waste some time, and then reads INTIM and TIMINT? c) reads INTIM until the timer is done, per usual, and then reads INTIM AND TIMINT? ...got a framework ready with display for all 4 TIM* timers. Just need to define the test. That's what I was hoping you'd figure out Basically I need to test all possible combinations of values, and how the results compare between a real system and Stella. How to do this is still up in the air ... Quote Link to comment Share on other sites More sharing options...
RevEng Posted September 13, 2012 Share Posted September 13, 2012 That's what I was hoping you'd figure out Basically I need to test all possible combinations of values, and how the results compare between a real system and Stella. How to do this is still up in the air ... Got it. You're looking to expose edge and oddball cases, but you don't know where they are. I'll see if I can tease something out. Quote Link to comment Share on other sites More sharing options...
SeaGtGruff Posted September 13, 2012 Share Posted September 13, 2012 Responding to mirror addresses has always been there. Just to be clear, I meant the "extra" mirroring of the RIOT addresses that arises from the way the 6532 ignores some of the address lines for some of its registers, not the mirroring caused by the 6507's reduced address bus plus the effects of the TIA's and 6532's chip select, R/W, and RAM select lines. I know the basic mirroring has always been there, including the way the TIA read addresses are mirrored differently than the TIA write addresses, but I haven't checked to see if the "extra" mirroring is there for the 6532. An example of what I mean is that the "Read Timer" register doesn't use the A1 and A4 lines, so the INTIM register's internal address (within the 6532) is %XA1X0, where X is "don't care" and A determines whether the timer IRQ interrupt is enabled or disabled. So in terms of the 2600's addresses, that means $0284 (INTIM) is mirrored at $0286, $0384, and $0386, in addition to all of its other mirrors-- but only for read operations, because the $0284 write address has different mirrors(!). And the $028C read address (which I call INTIMI) is mirrored at $028E, $038C, and $038E. And if the timer interrupt is ignored by Stella since it has no relevance to the 2600, you could say that those INTIMI addresses are just additional mirrors of INTIM. But I imagine all this funky mirroring has been part of Stella's core from the beginning, if it was based on the TIA, 6532, and 6507 specs. Currently, the functionality of the RIOT that isn't used by a real console isn't emulated at all (this means external IRQ and D6 interrupt flag). I think it's safe to forget about the IRQ stuff, but I think the D6/PA7 stuff should be added at some point, since I think that's where the 'flickering' digits in the test ROM above is coming from. The timer interrupt signal and PA7 interrupt signal can be ignored, but I think the PA7 interrupt flag should be set and cleared by Stella just as it would be on real hardware, since there's nothing preventing a programmer from reading the PA7 interrupt flag on a 2600 (if he or she ever had any occasion to use it for something). I was thinking about this last night and came to a similar conclusion. The current code definitely doesn't do things in real time, as that would be much slower (and require threading, etc). It checks the state at the time the programmer needs it (aka, when reading TIMINT), and I think you're right in that this is where the problem is. I figured it must be a "determine it when you need it" situation, since as you say it would be too resource-intensive to emulate everything in "real time." In that case using an extra flag for indicating whether or not to check the timer flag's status may be sufficient. Once the timer flag is set, it will stay set until it's cleared by reading/writing INTIM. And once the timer flag is cleared, it will stay cleared until the timer rolls over the first time after it's been set. So the "ignore" flag that I suggested should be cleared when the timer is set, and should be set when the timer rolls over (but only when the timer flag is being read) or when the timer is read (but only if the timer has already rolled over). And if the timer flag is already set, there's no need to call the function that checks what its state should be. Some examples might help clarify what I'm trying to say: Example 1: Set the timer (clear the timer flag and the ignore flag) Timer begins decrementing at interval rate Read the timer before it rolls over (no change to the timer flag or the ignore flag) Timer rolls over Timer begins decrementing every cycle Read the timer flag (set the timer flag and the ignore flag) Example 2: Set the timer (clear the timer flag and the ignore flag) Timer begins decrementing at interval rate Timer rolls over Timer begins decrementing every cycle Read the timer after it's rolled over (clear the timer flag, set the ignore flag) Read the timer flag (the ignore flag is set, so don't set the timer flag) Example 3: Set the timer (clear the timer flag and the ignore flag) Timer begins decrementing at interval rate Timer rolls over Timer begins decrementing every cycle Read the timer flag (the ignore flag is cleared, so set the timer flag and the ignore flag) Read the timer (clear the timer flag, set the ignore flag [or not, since it's already set anyway]) Timer rolls over again Timer continues decrementing every cycle Read the timer flag (the ignore flag is set, so don't set the timer flag) Example 4: Set the timer (clear the timer flag and the ignore flag) Timer begins decrementing at interval rate Timer rolls over Timer begins decrementing every cycle Read the timer (clear the timer flag, set the ignore flag) Timer rolls over again Timer continues decrementing every cycle Read the timer flag (the ignore flag is set, so don't set the timer flag) Quote Link to comment Share on other sites More sharing options...
+stephena Posted September 13, 2012 Author Share Posted September 13, 2012 I'm pretty sure all mirroring is handled correctly, but I will check it all over before the next release. This the current scheme: All addresses matching the pattern (addr & 0x1080 == 0x80) are passed to the M6532 class (IOW, all addresses with A12 = 0 and A7 = 1). Of those, addresses are differentiated by A2 (0 is RAM, 1 is I/O). Furthermore, when A2 is 1 (I/O), A4 is used to set the timer value, where the timer is based on the last two bits (A1A0). Nothing else is handled. And in particular, nothing is done if you attempt to read from a write address (is that even possible?). For carts that contain extended RAM, reading from the write port would do something else (fill RAM with databus value), so I'm not sure what happens when you do this for RIOT I/O addresses. Finally, I'm almost sure at this point I see what the problem is. I'm setting the D7 flag whenever the timer goes below 0, even if INTIM has been read before. Quote Link to comment Share on other sites More sharing options...
SeaGtGruff Posted September 14, 2012 Share Posted September 14, 2012 You can't read from a write-only register. However, read-only registers and write-only registers can have the same addresses as each other, as is the case with the TIA-- all of its read-only registers have the same addresses as most of its write-only registers, although its write-only registers use two more address lines than its read-only registers, so they're mirrored a bit differently, with each read-only register (along with its mirrors) corresponding to multiple write-only registers. I think the simplest/safest way to make sure all the mirroring is handled correctly is to use bit masks to ignore any address bits that aren't used by a particular register, also accounting for the chip selects, RAM select, and R/W lines. Quote Link to comment Share on other sites More sharing options...
+stephena Posted September 14, 2012 Author Share Posted September 14, 2012 That's just a first rough attempt at expressing the logic, so it might not be correct, but I hope you can see what I'm getting at. Again, this code is based on the assumption that Stella doesn't try to set the timer flag in "real time," but instead waits until the 2600 program reads the timer flag register and then calls some function to determine what the state of the timer flag should be. I now have the code working properly with this logic. The original test ROM that gave 0x80 for 'After' now shows '0x00'. And all the other test ROMs I have still work, so no breakage so far. Note that if the timer is read on the same cycle that the timer rolls over, the timer flag will be set as normal-- that is, reading the timer doesn't clear the timer flag as it would in other cases. I guess it may be a matter of priority-- reading the timer may in fact clear the timer flag as usual, but then the fact that the timer just rolled over causes the timer flag to be set. The very latest ROM from Omega (withDelay) now show 0x00 for all values, whereas on my console delay = 0 and delay = 255 give 0x80. I suspect this is because of the issue above; I don't think I'm properly handling the case where the read is on the same cycle. I'll work on this next. Once the timer rolls over and the timer flag is set, it will remain set until the timer is written or read-- that is, reading the timer flag doesn't clear the timer flag (although reading the TIMINT register *does* clear the interrupt flag in bit 6). I will look into D6 later, but you're right that it is cleared on TIMINT read. On some of the test ROMs, my console actually starts up with D6 set, so we need to handle it here too. Also, I believe the timer will continue decrementing and rolling over again after it has already rolled over-- that is, once it rolls over, sets the timer flag, and starts to decrement every cycle, it does not stop at 0 and stay at 0, but keeps rolling over from 0 to 255. I was already doing that; it basically just returns the last 8 bits (ie, masking with 0xff). Quote Link to comment Share on other sites More sharing options...
+stephena Posted September 14, 2012 Author Share Posted September 14, 2012 (1) The timer is set by writing to TIM1T, TIM8T, TIM64T, or T1024T. On the cycle after the write instruction completes, the timer will contain the value written to it, and the timer interrupt flag will be cleared. On the next cycle, the value in the timer will be decremented by 1. Then it will (normally) be decremented by 1 at intervals of 1, 8, 64, or 1024 cycles, depending on which register you wrote to. For example, suppose you set the timer to 1. Depending on which register you wrote to, the results should be as follows: LDA #1 ; cycles -6, -5 STA TIM1T ; cycles -4, -3, -2, -1 cycle 0 -- timer = 1, flag = 0 cycle 1 -- timer = 0, flag = 0 cycle 2 -- timer = 255, flag = 1 cycle 3 -- timer = 254, flag = 1 etc. LDA #1 ; cycles -6, -5 STA TIM8T ; cycles -4, -3, -2, -1 cycle 0 -- timer = 1, flag = 0 cycle 1 -- timer = 0, flag = 0 cycle 2 -- timer = 0, flag = 0 ... cycle 8 -- timer = 0, flag = 0 cycle 9 -- timer = 255, flag = 1 cycle 10 -- timer = 254, flag = 1 etc. LDA #1 ; cycles -6, -5 STA TIM64T ; cycles -4, -3, -2, -1 cycle 0 -- timer = 1, flag = 0 cycle 1 -- timer = 0, flag = 0 cycle 2 -- timer = 0, flag = 0 ... cycle 64 -- timer = 0, flag = 0 cycle 65 -- timer = 255, flag = 1 cycle 66 -- timer = 254, flag = 1 etc. LDA #1 ; cycles -6, -5 STA T1024T ; cycles -4, -3, -2, -1 cycle 0 -- timer = 1, flag = 0 cycle 1 -- timer = 0, flag = 0 cycle 2 -- timer = 0, flag = 0 ... cycle 1024 -- timer = 0, flag = 0 cycle 1025 -- timer = 255, flag = 1 cycle 1026 -- timer = 254, flag = 1 etc. Can we get some test ROMs to verify this (specifically, decrement by 1 on first cycle, then by interval until zero, then by 1 continuously)? AFAICT, the current code is handling the interval and below zero decrements correctly, but it isn't doing the initial decrement by 1. I would think that if this isn't done it would make Stella and the real console differ in all my test ROMs. But Stella matches in almost all cases. Perhaps the cases where it doesn't match are because of this?? Sorry for the flurry of messages. EDIT: After further inspection, it seems that Stella is doing this after all, it just does it in a different way (but returns the same values at the same cycles). Quote Link to comment Share on other sites More sharing options...
SeaGtGruff Posted September 15, 2012 Share Posted September 15, 2012 Have you posted a "nightly build" with the latest fixes yet? I'm not a C programmer, but if I could look at the latest source code, I might be able to help you figure out what to change and where. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.