VDP Interrupts & HVC tests
Posted: Sun Aug 15, 2010 3:22 pm
Thanks to Krikzz easy-to-use SDK, I finally managed to code some program and test various VDP behavior around interrupts and HVC.
--------------------
1) HVC latch bit
--------------------
If VDP register 0 bit 1 (M3) is set, any read to HVC port will return a fixed value, presumly the value that was latched in the internal HV counter when this bit was set.
This behavior must be correctly emulated in order to get Sunset Riders introduction right. No PC emulators handled this actually.
An interesting feature is that when M3 bit is set, any high-to-low transition on the VDP /HL input line will latch a new value into HVC port, using the current value of the internal HV counter. This feature is used by lightgun games, which configure I/O controller port 2 so that TH is an input and any high-to-low transition on TH triggers the /HL line as well.
I used this trick to figure the *exact* HVC value when VDP interrupts are triggered, by simply connecting 68000 /IPL2 or Z80 /INT input to controller port 2 TH (pin 7).
-------------------
2) HV Interrupts
-------------------
The VDP manages two internal pending interrupt flags:
- HINT pending flag is set when the internal HINT counter overflows. This counter is reloaded when overflow occurs and during VBLANK , using VDP register $0A value. It is decremented on each lines between line 0 and line $E0 (if VDP height is set to 224 lines) or line $F0 (if VDP height is set to 240 lines) included, exactly at HCounter = $A6 (if VDP width is set to 320 pixels) or $86 (if VDP width is set to 256 pixels). As a reference, Vcounter is incremented just before HINT pending flag is set, at HCounter = $A5 or $85. I verified that the 'display blank bit' (VDP register #1 bit 6) has no effect on the generation of HINT.
- VINT pending flag is set on line $E0 (if VDP height is set to 224 lines) or line $F0 (if VDP height is set to 240 lines), exactly at HCounter = $02 (regardless of the VDP width). The state of this flag can be seen in real-time by reading VDP status bit 7.
Both pending interrupt flags are set regardless of the state of VDP interrupts, which are managed using VDP registers $0 and $1:
- as long as HINT pending flag is set and VDP register $00 bit 4 is set, interrupt level 4 will be generated (/IRQ2 asserted).
- as long as VINT pending flag is set and VDP register $01 bit 5 is set, interrupt level 6 will be generated (/IRQ1 and /IRQ2 asserted).
The only way for these two flags to be cleared is when the /INTACK signal is asserted, which only happen when the 68k acknowledge an interrupt.
When /INTACK is asserted, the VDP will look at the current IRQ level through the state of /IPL1 and /IPL2 , clear the appropriate flag then update the IRQ level according to the remaining flag (if any).
Once they are set, interrupt pending flags remain set as long as interrupts are not acknowledged by the 68k. They are NOT cleared by:
- disabling interrupts through VDP registers
- reading VDP status
- starting a new frame
---------------------
3) Z80 Interrupt
---------------------
It is set at the exact same time as V-Interrupt (HCounter = $02).
It is (apparently) cleared on next line, still at $Hcounter = $02, regardless of interrupts being masked on Z80 side or not.
NB: the last statement needs to be confirmed.
-------------------
4) Test Program
-------------------
This program was used to verify the statements made above, it can be used to run the following tests (press button A to switch between 32-cell and 40-cell mode).
Test 1:
--------
This test is used to verify the HVC latch feature. An horizontal interrupt is programmed on line $80, which read HVC, set M3 latch bit then read HVC again. The main program then waits until VBLANK , read HVC again, clear M3 latch bit and read HVC a last time. It should return:
HVC=$80xx
HVC=$80yy
HVC=$80yy
HVC=$E1zz
As explained above, no PC emulators actually pass this test.
Test 2,3:
-----------
These tests make use of the HVC latch property to determine exact HV counter values when HINT and VINT are being triggered.
They are only useful on real hardware if you connect controller port 2 TH pin to /IPL2 pin.
By connecting to Z80 /INT instead, you can measure Z80 interrupt exact occurrence.
Test 4,5,6:
-------------
These tests verify the behavior of HINT pending flag when interrupts are enabled on 68k side but disabled on VDP side. An horizontal interrupt is programmed on line $80 and IRQ4 callback is set to save HVC when interrupts is accepted on 68k side. The main program waits until $D0 (current frame), $E2 (VBLANK) or $10 (next frame) then enable HINT on VDP side, waits a few line and display HVC read value.
It should display:
HVC = $0000 (this verify HINT has not been triggered on the line it was programmed)
HVC = $D0xx, $E2xx, $10xx (this verify HINT is triggered immediately as interrupt is enabled on VDP side)
All PC emulators pass these tests fine.
Test 7,8:
-------------
These tests verify the behavior of VINT pending flag when interrupts are enabled on 68k side but disabled on VDP side. IRQ6 callback is set to save HVC when interrupts is accepted on 68k side. The main program do the following sequence:
(1) waits until line $E1 then read the VDP status
(2) wait until line $E4 (VBLANK) or $10 (next frame) then read the VDP status
(3) disable VINT on VDP side then read the VDP status
(3) enable VINT on VDP side, wait a few lines then read the VDP status and display HVC read value.
It should display:
HVC = $0000 (this verify VINT has not been triggered on line $E0)
HVC = $E5xx, $11xx (this verify HINT is triggered after interrupt is enabled on VDP side)
VDP = $0288 (this verify VINT pending flag is set regardless of interrupt being enabled or not on VDP side)
VDP = $028x
VDP = $028x (this verify VINT pending flag is still set after forcing interrupt to be disabled)
VDP = $020x (this verify VINT pending flag is cleared as interrupt is acknowledged)
All PC emulators pass these tests fine except Gens which does not emulate the FIFO bits correctly, does not clear VINT pending flag when interrupt is acknowledged during VBLANK but clears VINT pending flag at the beginning of a new frame.
Test 9,10,11:
----------------
These tests are identical as tests 4,5,6 except that this time, HINT is enabled on VDP side but IRQ4 is masked on 68k side. Also verify that switching HINT on VDP side once HINT pending flag has been set does not clear it.
It should display:
HVC = $0000
HVC = $D0xx, $E2xx, $10xx
All PC emulators pass these tests fine.
Test 12,13:
-------------
Identical as tests 7,8 except that this time, VINT is enabled on VDP side but IRQ6 is masked on 68k side. Also verify that switching VINT on VDP side once VINT pending flag has been set does not clear it.
It should display:
HVC = $0000
HVC = $E5xx, $11xx
VDP = $0288
VDP = $028x
VDP = $028x
VDP = $020x
Only Regen passes this test, Gens have same problem as with tests 7,8 and Kega seems to still acknowledge interrupt when they are masked, the result being pending flag is immediately cleared and interrupts are not triggered once they are unmasked.
And that's it...
Here's the linkto the test program (C sourcecode included, it requires Krikzz devkit available on his website and you need to replace vdp.c in lib directory with the one I included).
--------------------
1) HVC latch bit
--------------------
If VDP register 0 bit 1 (M3) is set, any read to HVC port will return a fixed value, presumly the value that was latched in the internal HV counter when this bit was set.
This behavior must be correctly emulated in order to get Sunset Riders introduction right. No PC emulators handled this actually.
An interesting feature is that when M3 bit is set, any high-to-low transition on the VDP /HL input line will latch a new value into HVC port, using the current value of the internal HV counter. This feature is used by lightgun games, which configure I/O controller port 2 so that TH is an input and any high-to-low transition on TH triggers the /HL line as well.
I used this trick to figure the *exact* HVC value when VDP interrupts are triggered, by simply connecting 68000 /IPL2 or Z80 /INT input to controller port 2 TH (pin 7).
-------------------
2) HV Interrupts
-------------------
The VDP manages two internal pending interrupt flags:
- HINT pending flag is set when the internal HINT counter overflows. This counter is reloaded when overflow occurs and during VBLANK , using VDP register $0A value. It is decremented on each lines between line 0 and line $E0 (if VDP height is set to 224 lines) or line $F0 (if VDP height is set to 240 lines) included, exactly at HCounter = $A6 (if VDP width is set to 320 pixels) or $86 (if VDP width is set to 256 pixels). As a reference, Vcounter is incremented just before HINT pending flag is set, at HCounter = $A5 or $85. I verified that the 'display blank bit' (VDP register #1 bit 6) has no effect on the generation of HINT.
- VINT pending flag is set on line $E0 (if VDP height is set to 224 lines) or line $F0 (if VDP height is set to 240 lines), exactly at HCounter = $02 (regardless of the VDP width). The state of this flag can be seen in real-time by reading VDP status bit 7.
Both pending interrupt flags are set regardless of the state of VDP interrupts, which are managed using VDP registers $0 and $1:
- as long as HINT pending flag is set and VDP register $00 bit 4 is set, interrupt level 4 will be generated (/IRQ2 asserted).
- as long as VINT pending flag is set and VDP register $01 bit 5 is set, interrupt level 6 will be generated (/IRQ1 and /IRQ2 asserted).
The only way for these two flags to be cleared is when the /INTACK signal is asserted, which only happen when the 68k acknowledge an interrupt.
When /INTACK is asserted, the VDP will look at the current IRQ level through the state of /IPL1 and /IPL2 , clear the appropriate flag then update the IRQ level according to the remaining flag (if any).
Once they are set, interrupt pending flags remain set as long as interrupts are not acknowledged by the 68k. They are NOT cleared by:
- disabling interrupts through VDP registers
- reading VDP status
- starting a new frame
---------------------
3) Z80 Interrupt
---------------------
It is set at the exact same time as V-Interrupt (HCounter = $02).
It is (apparently) cleared on next line, still at $Hcounter = $02, regardless of interrupts being masked on Z80 side or not.
NB: the last statement needs to be confirmed.
-------------------
4) Test Program
-------------------
This program was used to verify the statements made above, it can be used to run the following tests (press button A to switch between 32-cell and 40-cell mode).
Test 1:
--------
This test is used to verify the HVC latch feature. An horizontal interrupt is programmed on line $80, which read HVC, set M3 latch bit then read HVC again. The main program then waits until VBLANK , read HVC again, clear M3 latch bit and read HVC a last time. It should return:
HVC=$80xx
HVC=$80yy
HVC=$80yy
HVC=$E1zz
As explained above, no PC emulators actually pass this test.
Test 2,3:
-----------
These tests make use of the HVC latch property to determine exact HV counter values when HINT and VINT are being triggered.
They are only useful on real hardware if you connect controller port 2 TH pin to /IPL2 pin.
By connecting to Z80 /INT instead, you can measure Z80 interrupt exact occurrence.
Test 4,5,6:
-------------
These tests verify the behavior of HINT pending flag when interrupts are enabled on 68k side but disabled on VDP side. An horizontal interrupt is programmed on line $80 and IRQ4 callback is set to save HVC when interrupts is accepted on 68k side. The main program waits until $D0 (current frame), $E2 (VBLANK) or $10 (next frame) then enable HINT on VDP side, waits a few line and display HVC read value.
It should display:
HVC = $0000 (this verify HINT has not been triggered on the line it was programmed)
HVC = $D0xx, $E2xx, $10xx (this verify HINT is triggered immediately as interrupt is enabled on VDP side)
All PC emulators pass these tests fine.
Test 7,8:
-------------
These tests verify the behavior of VINT pending flag when interrupts are enabled on 68k side but disabled on VDP side. IRQ6 callback is set to save HVC when interrupts is accepted on 68k side. The main program do the following sequence:
(1) waits until line $E1 then read the VDP status
(2) wait until line $E4 (VBLANK) or $10 (next frame) then read the VDP status
(3) disable VINT on VDP side then read the VDP status
(3) enable VINT on VDP side, wait a few lines then read the VDP status and display HVC read value.
It should display:
HVC = $0000 (this verify VINT has not been triggered on line $E0)
HVC = $E5xx, $11xx (this verify HINT is triggered after interrupt is enabled on VDP side)
VDP = $0288 (this verify VINT pending flag is set regardless of interrupt being enabled or not on VDP side)
VDP = $028x
VDP = $028x (this verify VINT pending flag is still set after forcing interrupt to be disabled)
VDP = $020x (this verify VINT pending flag is cleared as interrupt is acknowledged)
All PC emulators pass these tests fine except Gens which does not emulate the FIFO bits correctly, does not clear VINT pending flag when interrupt is acknowledged during VBLANK but clears VINT pending flag at the beginning of a new frame.
Test 9,10,11:
----------------
These tests are identical as tests 4,5,6 except that this time, HINT is enabled on VDP side but IRQ4 is masked on 68k side. Also verify that switching HINT on VDP side once HINT pending flag has been set does not clear it.
It should display:
HVC = $0000
HVC = $D0xx, $E2xx, $10xx
All PC emulators pass these tests fine.
Test 12,13:
-------------
Identical as tests 7,8 except that this time, VINT is enabled on VDP side but IRQ6 is masked on 68k side. Also verify that switching VINT on VDP side once VINT pending flag has been set does not clear it.
It should display:
HVC = $0000
HVC = $E5xx, $11xx
VDP = $0288
VDP = $028x
VDP = $028x
VDP = $020x
Only Regen passes this test, Gens have same problem as with tests 7,8 and Kega seems to still acknowledge interrupt when they are masked, the result being pending flag is immediately cleared and interrupts are not triggered once they are unmasked.
And that's it...
Here's the linkto the test program (C sourcecode included, it requires Krikzz devkit available on his website and you need to replace vdp.c in lib directory with the one I included).