GoldenAxe missing magic blue bottle and palette corruption
Moderator: BigEvilCorporation
GoldenAxe missing magic blue bottle and palette corruption
For the most part I have GoldenAxe running well in my emulator, but there are a couple of really weird bugs I'm having trouble tracking down:
1). The magic blue bottles that you get from attacking the elf guys or activating your magic spell either appear or don't appear for each level. Vram address 0xaa80 is where the bottle graphic should be, but when they don't appear this area of vram is left blank. When they appear this area of vram is setup correctly from 68k level setup code that uses vdp vram writes to fill the area (just before the level fades up).
2) The other problem is random palette corruption at stages where the next part of the level is reached (when the "go" arrow appears). This one is just a bad palette copied via 68k vdp cram writes.
Has anyone seen these issues before or have any tips on how to solve them?
cheers
Steve
1). The magic blue bottles that you get from attacking the elf guys or activating your magic spell either appear or don't appear for each level. Vram address 0xaa80 is where the bottle graphic should be, but when they don't appear this area of vram is left blank. When they appear this area of vram is setup correctly from 68k level setup code that uses vdp vram writes to fill the area (just before the level fades up).
2) The other problem is random palette corruption at stages where the next part of the level is reached (when the "go" arrow appears). This one is just a bad palette copied via 68k vdp cram writes.
Has anyone seen these issues before or have any tips on how to solve them?
cheers
Steve
do you check that games does not perform a data write command after setting a read command ?
I know some are doing this and writes should be ignored, don't know anything about golden axe in particular though...
also check your DMA operations (VRAM fill, copy,..), auto-increment address and address limits/masking
I know some are doing this and writes should be ignored, don't know anything about golden axe in particular though...
also check your DMA operations (VRAM fill, copy,..), auto-increment address and address limits/masking
I think I've got those cases covered.
When writing, I'm checking the VDP (code & 0xf) register for vram write, cram write, and vsram-write only. Other case are ignored and the address register does not get incremented. Similarly for the read.
One thing I haven't implemented is freezing the 68k during 68->vram dma or emulating returing that dma is busy for dma fills/vram-copies.
Another thing I've found out is that the blue pot graphic is copied into vram via VDP writes, and not VDP dma. Same with the palette (write to CRAM).
When writing, I'm checking the VDP (code & 0xf) register for vram write, cram write, and vsram-write only. Other case are ignored and the address register does not get incremented. Similarly for the read.
One thing I haven't implemented is freezing the 68k during 68->vram dma or emulating returing that dma is busy for dma fills/vram-copies.
Another thing I've found out is that the blue pot graphic is copied into vram via VDP writes, and not VDP dma. Same with the palette (write to CRAM).
A bit more info:
From debugging I can see the bottle graphics being downloaded into cram instead of vram. (this would explain the missing bottle, and the corrupt palette).
Can you spot anything wrong with my code register update? This is the only function where I update it:
cheers,
Steve
From debugging I can see the bottle graphics being downloaded into cram instead of vram. (this would explain the missing bottle, and the corrupt palette).
Can you spot anything wrong with my code register update? This is the only function where I update it:
cheers,
Steve
Code: Select all
void VDP::WriteControlPort( const uint32 val )
{
// Writing 2nd part of control data?
if( writePending )
{
writePending = FALSE;
codeRegister = ( ( codeRegister & 0x3 ) | ( ( val >> 2 ) & 0x3c ) ) & 0x3f;
addressRegister = ( ( addressRegister & 0x3fff ) | ( val << 14 ) & 0xc000 ) & 0xffff;
CheckDMA();
return;
}
// Write to VDP register?
if( ( val & 0xc000 ) == 0x8000 )
{
registers[ ( val >> 8 ) & 0x1f ] = val & 0xff;
codeRegister = 0; // Reset code register (address register is left as is)
return;
}
// Flag for next write
writePending = TRUE;
codeRegister = ( codeRegister & 0x3c ) | ( ( val >> 14 ) & 0x3 );
addressRegister = ( addressRegister & 0xc000 ) | ( val & 0x3fff ) & 0xffff;
return;
}
I don't have any source at hand, but you're missing an ELSE statement, because you're incorrectly updating the control data:sbroumley wrote:A bit more info:
From debugging I can see the bottle graphics being downloaded into cram instead of vram. (this would explain the missing bottle, and the corrupt palette).
Can you spot anything wrong with my code register update? This is the only function where I update it:
Code: Select all
void VDP::WriteControlPort( const uint32 val )
{
// Writing 2nd part of control data?
if( writePending )
{
writePending = FALSE;
codeRegister = ( ( codeRegister & 0x3 ) | ( ( val >> 2 ) & 0x3c ) ) & 0x3f;
addressRegister = ( ( addressRegister & 0x3fff ) | ( val << 14 ) & 0xc000 ) & 0xffff;
CheckDMA();
return;
}
else /* update first part of control data */
{
// Write to VDP register?
if( ( val & 0xc000 ) == 0x8000 )
{
registers[ ( val >> 8 ) & 0x1f ] = val & 0xff;
// Dont reset the code register codeRegister = 0; // Reset code register (address register is left as is)
// don't return, addr and code needs to be updated return;
}
else
{
// Flag for next write
writePending = TRUE;
}
codeRegister = ( codeRegister & 0x3c ) | ( ( val >> 14 ) & 0x3 );
addressRegister = ( addressRegister & 0xc000 ) | ( val & 0x3fff ) & 0xffff;
}
return;
}
"else" is unnecessary since a "return" is performed in the "if" case
The error is probably here:
should be
compiler usually apply logical operation from left to right
I bet you were applying 0xc000 mask on the whole address register
also note the 0xffff mask is redundant in this case
Now, two things about your implementation, compared to the one in genesis plus (which is told to be correct according to its author):
1/ code should not be reseted on register write (code and address registers are updated as usual). I think the golden axe 2 sega logo relies on that one.
2/ address register is latched when the second CTRL word is wrote and the first CTRL word access use this latched value to compute the new address, NOT the current address value.
This means that the following sequence could occurs:
(1) write first CTRL word
(2) write 2nd CTRL word: ADDR value is latched
(3) write DATA word: ADDR is incremented
...
(4) write first CTRL word
(5) DATA write
In that case, only the first half of ADDR is updated when (4) occurs, but remaining bits use the LATCHED value, not the current ADDR value !
I don't know if you are directly incrementing addressRegister when DATA writes occur but in that case, you have to keep the latched value in a separate variable.
The error is probably here:
Code: Select all
addressRegister = ( ( addressRegister & 0x3fff ) | ( val << 14 ) & 0xc000 ) & 0xffff;
should be
Code: Select all
addressRegister = ( ( addressRegister & 0x3fff ) | ( ( val << 14 ) & 0xc000 ) ) & 0xffff;
compiler usually apply logical operation from left to right
I bet you were applying 0xc000 mask on the whole address register
also note the 0xffff mask is redundant in this case
Now, two things about your implementation, compared to the one in genesis plus (which is told to be correct according to its author):
1/ code should not be reseted on register write (code and address registers are updated as usual). I think the golden axe 2 sega logo relies on that one.
2/ address register is latched when the second CTRL word is wrote and the first CTRL word access use this latched value to compute the new address, NOT the current address value.
This means that the following sequence could occurs:
(1) write first CTRL word
(2) write 2nd CTRL word: ADDR value is latched
(3) write DATA word: ADDR is incremented
...
(4) write first CTRL word
(5) DATA write
In that case, only the first half of ADDR is updated when (4) occurs, but remaining bits use the LATCHED value, not the current ADDR value !
I don't know if you are directly incrementing addressRegister when DATA writes occur but in that case, you have to keep the latched value in a separate variable.
thanks for pointing out the small errors. they didn't actually solve the problem but that's because that was not the cause.
I tracked down the issue to the main loop performing vram writes (for the bottle graphic), but it gets interrupted by a 68k vbl which performs a mem->cram dma. this trashes the vdp reigsters and so when the main loop continues, it is now writing to the bottle graphic to cram - hence the missing bottle and the corrupt palette.
So it looks like my problem is elsewhere. I'm not putting the 68k to sleep during rom/ram dmas so that might be the issue, or I have a bug with the vbl not being masked when it should.
For the vdp to fire off a vbl int on the 68k, I'm just testing bit 5 of vdp register 1 at start of scan line 225. Is there anything else I should be testing for? Also if the 68k is asleep during a vbl trigger, does it get ignored or does it happen as soon as the 68k unfreezes (end of dma)?
cheers
I tracked down the issue to the main loop performing vram writes (for the bottle graphic), but it gets interrupted by a 68k vbl which performs a mem->cram dma. this trashes the vdp reigsters and so when the main loop continues, it is now writing to the bottle graphic to cram - hence the missing bottle and the corrupt palette.
So it looks like my problem is elsewhere. I'm not putting the 68k to sleep during rom/ram dmas so that might be the issue, or I have a bug with the vbl not being masked when it should.
For the vdp to fire off a vbl int on the 68k, I'm just testing bit 5 of vdp register 1 at start of scan line 225. Is there anything else I should be testing for? Also if the 68k is asleep during a vbl trigger, does it get ignored or does it happen as soon as the 68k unfreezes (end of dma)?
cheers
Okay so I've found a solution and was wondering if anyone else is doing the same:
Basically golden axe turns off the display during level initialization (when the bottle graphic is written to vram). Disabling vbl triggering when the display is turned off fixes the problem.
I also implemented (what I think) is correct vdp vbl pending logic. From what I understand, vbl pending is set on line 225, and cleared after line 262 or before if a vbl int is acknowledged and taken by the 68k. In the 68k update function, if a vdp vbl is pending, the 68k takes and acknowledges it if it's interrupt masks are setup to accept it.
I'm testing more games with these changes right now. Do these ring a bell with anyone?
cheers
Steve
Basically golden axe turns off the display during level initialization (when the bottle graphic is written to vram). Disabling vbl triggering when the display is turned off fixes the problem.
I also implemented (what I think) is correct vdp vbl pending logic. From what I understand, vbl pending is set on line 225, and cleared after line 262 or before if a vbl int is acknowledged and taken by the 68k. In the 68k update function, if a vdp vbl is pending, the 68k takes and acknowledges it if it's interrupt masks are setup to accept it.
I'm testing more games with these changes right now. Do these ring a bell with anyone?
cheers
Steve
That's a bitwise operation not a logical operation, but in either case, AND is evaluated before OR. So, it will execute correctly. However, I agree with the addition of parentheses as it makes the statement less ambiguous.Eke wrote:The error is probably here:should beCode: Select all
addressRegister = ( ( addressRegister & 0x3fff ) | ( val << 14 ) & 0xc000 ) & 0xffff;
Code: Select all
addressRegister = ( ( addressRegister & 0x3fff ) | ( ( val << 14 ) & 0xc000 ) ) & 0xffff;
compiler usually apply logical operation from left to right
thanks guys - yes that was a typo, but (luckily) it didn't caused any problems.
anyway, now that I've fixed that, I think I've cracked the golden axe problems:
1) I do not trigger 68k vbl interrupts when the vdp display is disabled (happens when the screen is black just before a level starts in golden axe - this is during level initialization)
2) I do not trigger or request 68k vbl if the vbl is disabled via the 68k SR register (ie when the int pri is above 6). This fixes the mid level palette corruption where golden axe disables 68k ints, decompresses data, then re-anbles ints. this decompression takes several frames.
Are other people performing the same checks before issuing vbl ints from the vdp?
cheers
Steve
anyway, now that I've fixed that, I think I've cracked the golden axe problems:
1) I do not trigger 68k vbl interrupts when the vdp display is disabled (happens when the screen is black just before a level starts in golden axe - this is during level initialization)
2) I do not trigger or request 68k vbl if the vbl is disabled via the 68k SR register (ie when the int pri is above 6). This fixes the mid level palette corruption where golden axe disables 68k ints, decompresses data, then re-anbles ints. this decompression takes several frames.
Are other people performing the same checks before issuing vbl ints from the vdp?
cheers
Steve
I don't think it's correct, interrupts still occur when display is OFF1) I do not trigger 68k vbl interrupts when the vdp display is disabled (happens when the screen is black just before a level starts in golden axe - this is during level initialization)
more generally, about interrupts handling:2) I do not trigger or request 68k vbl if the vbl is disabled via the 68k SR register (ie when the int pri is above 6). This fixes the mid level palette corruption where golden axe disables 68k ints, decompresses data, then re-anbles ints. this decompression takes several frames.
(1) disabling VINT trough VDP register clear the interrupt line, but not the interrupt pending flag
(2) enabling VINT trough VDP register assert the interrupt line if there is a pending interrupt
(3) pending flag is set on line 224 (counting from line 0) and remains set until interrupt is acknowledged by 68k (it is not cleared at the end of the frame)
(4) asserting an interrupt should be followed by interrupt processing if interrupt is not masked
(5) unmasking an interrupt on 68k side should be followed by interrupt processing if interrupt line is asserted
(6) processing of the interrupt clears the interrupt pending flag on VDP side and clears the interrupt line (INTACK)
So basically, you have to handle 3 flags for an interrupt:
interrupt.asserted
interrupt.pending
interrupt.masked
This is all I can think about, there are some other quirks with VDP latency or Hint/Vint being triggered on the same line but that should work with most games
(1) and (2) are important for many games, make sure you handle pending interrupts correctly
Okay - so after looking over my int code very closely I found the real problem (ignore my last posts) and your info confirms this Eke.
My previous 68k frame execute loop only checked for handling ints at the beginning of every scan line. I updated the code so that a check for ints is handled immediately after an SR update instruction happens. This makes sure an interrupt fires off immediately on the next instruction as opposed to several instructions later when the next scan line begins.
I then removed all the vdp int hacks, and golden axe works great
thanks so much for your help Eke!
My previous 68k frame execute loop only checked for handling ints at the beginning of every scan line. I updated the code so that a check for ints is handled immediately after an SR update instruction happens. This makes sure an interrupt fires off immediately on the next instruction as opposed to several instructions later when the next scan line begins.
I then removed all the vdp int hacks, and golden axe works great
thanks so much for your help Eke!