Jump to content
IGNORED

Create ROMs to determine correct INTIM/TIMINT behaviour


stephena

Recommended Posts

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?

Link to comment
Share on other sites

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 ...

Link to comment
Share on other sites

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....

post-7074-0-68354100-1347421980_thumb.png

 

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

Link to comment
Share on other sites

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).

Link to comment
Share on other sites

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/

Link to comment
Share on other sites

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 ...

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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! :)

Link to comment
Share on other sites

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).

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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 ...

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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)

Link to comment
Share on other sites

I'm pretty sure all mirroring is handled correctly, but I will check it all over before the next release. This the current scheme:

  1. All addresses matching the pattern (addr & 0x1080 == 0x80) are passed to the M6532 class (IOW, all addresses with A12 = 0 and A7 = 1).
  2. Of those, addresses are differentiated by A2 (0 is RAM, 1 is I/O).
  3. 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.

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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).

Link to comment
Share on other sites

(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).

Link to comment
Share on other sites

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.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...