Jump to content
IGNORED

Emulator test suite?


webdeck

Recommended Posts

6 hours ago, JasonACT said:

The debugger in Classic99 logs many invalid opcodes for >0003 @>628C so you should just ignore it like it's a NOP and keep executing.

Wow, yeah, Moon Patrol has some bugs, clearly (looks like a pattern of putting data after a BL and doing *R11 instead of *R11+ when reading it...)

 

Does the actual 9900 just ignore any instructions it can't parse?

  • Like 1
Link to comment
Share on other sites

6 hours ago, webdeck said:

Does the actual 9900 just ignore any instructions it can't parse?

Not quite ignore... My DS99/4a emulator chews up 6 cycles on any illegal ops. I'm sure I got that from people who knew much more than I did.  Moon Patrol does, indeed, hit lots of illegal ops but plays the same on my emulator as it does on real hardware so I'm guessing that bit of logic is correct (or at least good enough that I've never questioned it).

  • Like 2
Link to comment
Share on other sites

Moon Patrol produces really lots of unknown opcode errors, most of them in a loop:

 

[:maincpu] ** 623e: Illegal opcode 0060
[:maincpu] ** 628c: Illegal opcode 0003
[:maincpu] ** 649e: Illegal opcode 010f
[:maincpu] ** 6816: Illegal opcode 0001

 

In the Geneve, a MID interrupt would be raised each time. However, it seems as if Moon Patrol does not run on the Geneve anyway, I'm getting no reaction on keypresses (possibly directly scanned).

 

Code at those locations:

 

6238:     MOV  R3,R10
623A:     BL   @>7C40
623E:     DATA >0060
6240:     MOV  R0,R0
...
6286:     JNE  >61D2
6288:     BL   @>7C40
628C:     DATA >0003
628E:     MOV  R0,R15
6290:     SLA  R15,1
...
649A:     JNE  >650A
649C:     BL   @>7C40
64A0:     DATA >0006
64A2:     MOV  R0,R0
64A4:     JNE  >650A
...
680E:     AI   R10,>FFDC
6812:     BL   @>7C40
6816:     DATA >0001
6818:     SLA  R0,14
...
7C40:     MOV  *R11,R3
7C42:     LI   R1,>6FE5
7C46:     MPY  @>83C0,R1
7C4A:     AI   R2,>7AB9
7C4E:     MOV  R2,@>83C0
7C52:     MOV  R3,R0
7C54:     SWPB R3
7C56:     SB   R0,R3
7C58:     SRL  R3,8
7C5A:     INC  R3
7C5C:     CLR  R1
7C5E:     SWPB R2
7C60:     DIV  R3,R1
7C62:     SWPB R2
7C64:     AB   R2,R0
7C66:     SRL  R0,8
7C68:     RT  

 

So the reason is just using MOV *R11,R3 instead of MOV *R11+,R3.

  • Like 4
Link to comment
Share on other sites

5 hours ago, mizapf said:

Moon Patrol produces really lots of unknown opcode errors, most of them in a loop:

 

In the Geneve, a MID interrupt would be raised each time. However, it seems as if Moon Patrol does not run on the Geneve anyway, I'm getting no reaction on keypresses (possibly directly scanned).

Moon Patrol will sorta run on the Geneve but a lot of the graphics are corrupted.

Link to comment
Share on other sites

14 hours ago, wavemotion said:

Not quite ignore... My DS99/4a emulator chews up 6 cycles on any illegal ops. I'm sure I got that from people who knew much more than I did.  Moon Patrol does, indeed, hit lots of illegal ops but plays the same on my emulator as it does on real hardware so I'm guessing that bit of logic is correct (or at least good enough that I've never questioned it).

Yeah, datasheet says that illegal opcodes take 6 cycles and have no side effects, I've not seen anything to disprove that yet.

Moon Patrol has illegal opcodes. I remember the other oddity when I started out was Slymoids uses IDLE, which is not safe to use on the 4A console since it can be misinterpreted as a CRU instruction. (Misinterpreted by what, I'm not sure... I haven't heard of any specific hardware that the 'banned' opcodes actually trigger.)

 

  • Like 2
Link to comment
Share on other sites

1 minute ago, Tursi said:

Yeah, datasheet says that illegal opcodes take 6 cycles and have no side effects, I've not seen anything to disprove that yet.

Is Moon Patrol maybe using illegal opcodes as NOPs or some weird way to obfuscate something?  Just seems weird.  Maybe the routines were meant to use the data which follows the BL for something and increment the return pointer.

  • Like 1
Link to comment
Share on other sites

1 hour ago, OLD CS1 said:

Is Moon Patrol maybe using illegal opcodes as NOPs or some weird way to obfuscate something?  Just seems weird.  Maybe the routines were meant to use the data which follows the BL for something and increment the return pointer.

Looks like it was just a unfound bug at the time not incrementing the return pointer after fetching the data.

 

Should be easy enough to fix..I wonder if the game would play any better without the wasted cycles.

  • Like 2
Link to comment
Share on other sites

Posted (edited)

I believe that incrementing the return pointer would actually take longer than allowing an illegal op-code to execute. So, perhaps a feature, though the DATA is never fetched.

 

I updated the code to fetch the DATA.

 

Found another, in the crash loop...

   648A  0300  limi >0002                 
         0002
   648E  0300  limi >0000                 
         0000
   6492  0200  li   R0,>1f03              
         1F03
   6496  06A0  bl   @>7392                
         7392
   649A  06A0  bl   @>70c2                
         70C2
   649E  010F  data >010f                 
   64A0  06C0  swpb R0                    
   64A2  D800  movb R0,@>8c00             
   ...
   70C2  C0DB  mov  *R11,R3               
   70C4  0201  li   R1,>6fe5              
         6FE5
   70C8  3860  mpy  @>83c0,R1             
         83C0
   70CC  0222  ai   R2,>7ab9              
         7AB9
   70D0  C802  mov  R2,@>83c0             
         83C0
   70D4  C003  mov  R3,R0                 
   70D6  06C3  swpb R3                    
   70D8  70C0  sb   R0,R3                 
   70DA  0983  srl  R3,8                  
   70DC  0583  inc  R3                    
   70DE  04C1  clr  R1                    
   70E0  06C2  swpb R2                    
   70E2  3C43  div  R3,R1                 
   70E4  06C2  swpb R2                    
   70E6  B002  ab   R2,R0                 
   70E8  0980  srl  R0,8                  
   70EA  045B  b    *R11       

...Also fixed! ...I don't know how to play.:roll:

 

   moonpatc.bin

   moonpatd.bin

Edited by HOME AUTOMATION
  • Like 1
  • Thanks 1
Link to comment
Share on other sites

On 7/6/2024 at 4:31 PM, webdeck said:

The other Atari game that gave me issues is Jungle Hunt.  The swinging vines are missing.

Have any emulator writers seen the Jungle Hunt issue before and could point me in the right direction as to what I'm doing wrong?  Thanks!

Link to comment
Share on other sites

The Moon Patrol fixes won't have any visible impact on the performance - you're talking about like 10 cycles per frame.

Not the only released title with bugs. Miner2049er has a great one where it tries to use the status register to decide when to check sprite collisions, but it checks the wrong bit so instead of checking only on sprite collision, it checks randomly. Game of course appears to run fine. ;) But emulators that don't put data in the 5th sprite index bits will have no collisions.

 

Can't say I've seen the Jungle Hunt issue. In a quick look here, the vines (first vine at least) is mostly character graphics - though the part that overlaps the leaves is done with sprites - I'd expect if you can see the player that you can see that stub of a vine.

 

The game is running in full bitmap mode but is treating it as a character mode, not an image mode. I went back and looked at your screenshot - the sprites are there, and the leaves and tree trunk look correct, but the vine is missing and you have a single misplaced character. Could there be an addressing error in the second third of the display?
 

Have you tried many other bitmap titles? It seems weird that only Jungle Hunt messes up. :)

 

 

 

 

  • Like 3
Link to comment
Share on other sites

11 hours ago, Tursi said:

Not the only released title with bugs. Miner2049er has a great one where it tries to use the status register to decide when to check sprite collisions, but it checks the wrong bit so instead of checking only on sprite collision, it checks randomly. Game of course appears to run fine. ;) But emulators that don't put data in the 5th sprite index bits will have no collisions.

 

Can't say I've seen the Jungle Hunt issue. In a quick look here, the vines (first vine at least) is mostly character graphics - though the part that overlaps the leaves is done with sprites - I'd expect if you can see the player that you can see that stub of a vine.

 

The game is running in full bitmap mode but is treating it as a character mode, not an image mode. I went back and looked at your screenshot - the sprites are there, and the leaves and tree trunk look correct, but the vine is missing and you have a single misplaced character. Could there be an addressing error in the second third of the display?
 

Have you tried many other bitmap titles? It seems weird that only Jungle Hunt messes up. :)

I have Miner2049er working with the 5S flag.

 

For Jungle Hunt, I can see in the VDP RAM that the one character of the swinging vine that is displayed on the screen is changing in the screen table, but the rest of the vine characters just aren't there at all.  So I don't think it is an issue with the Graphics II rendering, but something wrong with how the bytes are being written to the VDP by the game.  I must have an edge case somewhere that I am missing.

Link to comment
Share on other sites

7 minutes ago, webdeck said:

I have Miner2049er working with the 5S flag.

 

For Jungle Hunt, I can see in the VDP RAM that the one character of the swinging vine that is displayed on the screen is changing in the screen table, but the rest of the vine characters just aren't there at all.  So I don't think it is an issue with the Graphics II rendering, but something wrong with how the bytes are being written to the VDP by the game.  I must have an edge case somewhere that I am missing.

There's not too much too it... if you are handling the address and prefetch correctly you should catch all possible cases.

 

There is an internal flipflop that essentially tracks whether the VDP is waiting for the first or second address byte. This flipflop is reset on reset, status read, or any data access (read or write). This would be the mostly likely cause of the address being completely off - if one of these reset cases is missed.

 

When writing the VDP address, the MSB or LSB is replaced with the data depending on the flipflop (LSB first), it's not shifted like GROMs.

 

On MSB write, the address is masked by 0x3F. After the address is updated, the two most significant bits are checked. If either bit is set, no prefetch is performed (so the prefetched data register is unchanged). If the most significant bit is set, a register write is also performed.

 

Popeye shows incorrect prefetch the best, so it sounds like you have that, and it wouldn't likely affect addressing. If Jungle Hunt was setting a 'read address', then your vine would just be off by one character. So most likely it's not in that mechanism. It IS possible to set the address via a register write, but that's an abuse I've never seen actually used ;) (I have seen setting a write address without the prefetch inhibit bit...)

 

Check resetting of the VDP flipflop. I didn't dig into the vine draw code to see what they are doing that's weird, but I can't think of much else offhand.

 

  • Like 1
Link to comment
Share on other sites

Posted (edited)

Thank you for the detailed response, Tursi.

 

Popeye works correctly (no corruption of the thrown bottles), so I'm pretty sure I have prefetch working correctly, and I have the flipflop latching implemented as you described.  Megademo works completely, and I've tried other graphics 2 games and they seem to work fine (e.g. Moon Patrol is graphics 2.)

 

I double checked the Jungle Hunt roms I'm using and they match what is on whtech for both the RPK and finalGROM versions there.

 

This is the Swift code I'm using for VDP write.  This should be readable if you know C - the only odd thing here is the "address.addingReportingOverflow(UInt16(0x0001)).partialValue" which is just a typesafe equivalent of "address + 1"

    func mappedWrite(_ portAddress: UInt16, _ value: UInt8, instruction: TMS9900Instruction? = nil) {
        if ((portAddress & UInt16(0x0002)) != 0) {
            // Write VDP address - first byte is LSB, second byte is MSB
            if (isAddressChanging) {
                // MSB being written
                address = (address & UInt16(0x00ff)) | (UInt16(value) << 8)
                if ((address & UInt16(0x8000)) != 0) {
                    // VDP register write
                    let register: Int = Int(UInt16(address & UInt16(0x0700)) >> 8)
                    let regValue: UInt8 = UInt8(address & UInt16(0x00ff))
                    setRegister(register, regValue)
                }
                
                if ((address & 0xc000) != 0) {
                    // Mask off top two bits, but don't pre-fetch
                    address = address & UInt16(0x3fff)
                } else {
                    prefetchedByte = memory.getByte(address)
                    address = address.addingReportingOverflow(UInt16(0x0001)).partialValue & UInt16(0x3fff)
                }
            }
            else {
                // LSB being written
                address = (address & UInt16(0xff00)) | UInt16(value)
            }
            isAddressChanging = !isAddressChanging
        }
        else {
            // Write VDP data
            isAddressChanging = false
            memory.setByte(address, value)
            prefetchedByte = value
            address = address.addingReportingOverflow(UInt16(0x0001)).partialValue & UInt16(0x3fff)
        }
    }

 

And for completeness, here is the code for VDP read:

    func mappedRead(_ portAddress: UInt16, instruction: TMS9900Instruction? = nil) -> UInt8 {
        isAddressChanging = false // Reading resets address setting
        
        if ((portAddress & UInt16(0x0002)) != 0) {
            // Read VDP Status Register
            var result: UInt8 = 0
            statusRegisterLock.withLock() {
                result = statusRegister
                // Clear the F, 5S, and C bits
                statusRegister = statusRegister & statusFlagResetOnReadMask
            }

            // When F bit is cleared, the interrupt signal is cleared as well
            TMS9901CRU.shared.writeBit(UInt16(0x0002), true)
            return result
        }
        else {
            // Read VDP data
            let result: UInt8 = prefetchedByte
            prefetchedByte = memory.getByte(address)
            address = address.addingReportingOverflow(UInt16(0x0001)).partialValue & UInt16(0x3fff)
            return result
        }
    }

 

I've attached a video capture of what the game looks like....  so strange.

Edited by webdeck
  • Like 1
Link to comment
Share on other sites

Your code looks correct.  I tried to attack your issue from the other side - I took my code and started to peel out just one simple nuance at a time. i.e. I would remove the 0x3FFF mask on address increment. Or I would not clear the address write latch when reading. Or I would not mask the register writes to match the VDP datasheet.  In all cases the game either still played correctly (vines shown properly) or something far (far!) worse happened (usually a crash). In short: I was unable to reproduce the "disappearing vine act" with strategic willful sabotage of my VDP core.

  • Like 2
Link to comment
Share on other sites

Yeah, I'm not even sure what I'd try for that case...

 

The display is in full bitmap mode, is there anything about the second third that differs from the first and third in your render loop?

 

The fact that the tree trunk is correct blows away most of my theories. It might come down to disassembling the relevant code to see what stupid trick they did to draw the vines. ;)In Classic99 you can watch the VDP memory log to see where the changing bytes are drawn, then set a VDP write breakpoint to catch the code doing it. Takes a little back and forth but should be possible to capture the code fairly quickly. 

(And since you own your own emulator, you can likely do similar. I used to hardcode such breakpoints in Classic99 and have it dump disassemblies before I had the debugger.)

 

 

  • Like 2
Link to comment
Share on other sites

Yep, time to dive into debugging.  Thanks for your help.

 

While I'm here - I have another question regarding memory access timing.  As I understand it, the 9900 always does word memory access, so if you are writing a byte, you have to read the word first, modify the appropriate byte in the word, and then write the word back.  If it is in the 32k expansion, then that would add 8 clock cycles because of the wait states (4 for the read and 4 for the write.)  Is that correct?

 

What about when the 9900 writes a word.  Does it do a read-before-write as well, or is writing a word to 32k expansion only 4 clock cycles.?

Link to comment
Share on other sites

It always does a read before a write, as there is no actual byte operations, everything is word related, just the internal microcode decides afterwards inside the ALU how to treat the word, or if it's byte.

 

See the manual for more info.

Screenshot_20240710-023253.png

Edited by Gary from OPA
  • Like 1
Link to comment
Share on other sites

Posted (edited)

I found the bug!  I knew it had to be some edge case that isn't used much.

 

The game is using the X instruction to do an SRL and then does a JNC.  I didn't properly set the status bits with the X instruction (I set them when doing the SRL, but then they got overwritten back to what it was before the X instruction.)  Vines are now swinging, and I have better debugging tools.

 

Thanks again for the help @Tursi, @wavemotion, @Gary from OPA!

 

And now that I can play the game, man is it not fun... :(

Edited by webdeck
  • Like 4
Link to comment
Share on other sites

2 minutes ago, webdeck said:

I found the bug!  I knew it had to be some edge case that isn't used much.

 

The game is using the X instruction to do an SRL and then does a JNC.  I didn't properly set the status bits with the X instruction (I set them when doing the SRL, but then they got overwritten back to what it was before the X instruction.)  Vines are now swinging, and I have better debugging tools.

 

Thanks again for the help @Tursi, @wavemotion, @Gary from OPA!

Yes, X inst is a key one, not too much software uses it, but TI did in few of its games.

  • Like 1
Link to comment
Share on other sites

Posted (edited)
3 hours ago, Gary from OPA said:

Yes, X inst is a key one, not too much software uses it, but TI did in few of its games.

@PeteE maybe this should be added to your torture test (X running the correct instruction and setting the correct status bits)?

Edited by webdeck
Link to comment
Share on other sites

On 7/9/2024 at 11:19 PM, webdeck said:

Yep, time to dive into debugging.  Thanks for your help.

 

While I'm here - I have another question regarding memory access timing.  As I understand it, the 9900 always does word memory access, so if you are writing a byte, you have to read the word first, modify the appropriate byte in the word, and then write the word back.  If it is in the 32k expansion, then that would add 8 clock cycles because of the wait states (4 for the read and 4 for the write.)  Is that correct?

 

What about when the 9900 writes a word.  Does it do a read-before-write as well, or is writing a word to 32k expansion only 4 clock cycles.?

Yeah. It's not very smart about it, all the opcodes do mostly the same thing. There are only a couple of opcodes that don't read before write. If not for the 4 wait states, we probably wouldn't even care much.

 

The instruction timing sheet there HAS all the information, but it took me a few passes to understand the way they organized it. You'll find a breakdown in Classic99's cpu9900.cpp of every opcode's memory accesses, in plain english, if that's helpful. ;)

 

Quote

The game is using the X instruction to do an SRL and then does a JNC.  I didn't properly set the status bits with the X instruction (I set them when doing the SRL, but then they got overwritten back to what it was before the X instruction.)  Vines are now swinging, and I have better debugging tools.

Ahhh, nice one! X is a tricky instruction to get right the first time. ;)

 

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

I've combed through everything and I believe I have all the timing and memory access wait states correct.   There were a few instructions where I had missed that they read one of the operands even though the value isn't used, but that's all cleaned up now.  Things feel and look right so far.

 

The only issue I'm seeing right now is that the megademo runs a bit slower in my emulator and I can't figure out why that is.  It doesn't stutter or anything, but just incrementally falls further and further behind as it goes on.  VDP frames are being rendered at 60 FPS with no dropped frames, but there is a bit of jitter in the timing of the interrupt - sometimes it is tens of ns early and sometimes tens of ns late, but I don't think that would cause it to run slower, would it?

 

Edited by webdeck
Link to comment
Share on other sites

19 hours ago, webdeck said:

I've combed through everything and I believe I have all the timing and memory access wait states correct.   There were a few instructions where I had missed that they read one of the operands even though the value isn't used, but that's all cleaned up now.  Things feel and look right so far.

 

The only issue I'm seeing right now is that the megademo runs a bit slower in my emulator and I can't figure out why that is.  It doesn't stutter or anything, but just incrementally falls further and further behind as it goes on.  VDP frames are being rendered at 60 FPS with no dropped frames, but there is a bit of jitter in the timing of the interrupt - sometimes it is tens of ns early and sometimes tens of ns late, but I don't think that would cause it to run slower, would it?

 

It's trickier than it seems. It's easy to chalk up a few extra wait states where they shouldn't be. Maybe dump a few opcodes and the cycles they took for comparison?

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