VDP Interrupts & HVC tests

For anything related to VDP (plane, color, sprite, tiles)

Moderators: BigEvilCorporation, Mask of Destiny

Post Reply
Eke
Very interested
Posts: 884
Joined: Wed Feb 28, 2007 2:57 pm
Contact:

VDP Interrupts & HVC tests

Post by Eke » 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).
Last edited by Eke on Tue Aug 17, 2010 12:23 pm, edited 1 time in total.

Nemesis
Very interested
Posts: 791
Joined: Wed Nov 07, 2007 1:09 am
Location: Sydney, Australia

Post by Nemesis » Mon Aug 16, 2010 1:22 am

Hi Eke, thanks for doing these measurements, this is very useful info! One thing to note, I suspect there may actually be a 1 step latency with the values you've measured, simply because the VDP most likely updates the HV counters between asserting interrupts, and latching the new state for the HL line. Eg, I suspect the VDP is doing the following steps in order, each internal pixel clock cycle:
1. Update HV counter
2. Latch current HL line state, and latch HV counter if line has just been asserted.
3. Trigger any required interrupts

It makes sense that the VDP would trigger interrupts after updating the HV counters and latching a new value for the HL line, since both these steps tell the VDP which interrupts need to be triggered. This would mean that the latched HV counter values you're getting are probably one step ahead of the true values at the time the interrupt line was asserted, meaning HINT pending would be set at the same time as VCounter increment, 0xA5 or 0x85, and Z80 int is triggered at 0x01, which is the same time as the F flag is set in the status register.

mickagame
Very interested
Posts: 256
Joined: Sat Jun 07, 2008 7:37 am

Post by mickagame » Mon Aug 16, 2010 7:40 am

Great tests Eke !!
It will be very useful.

Eke
Very interested
Posts: 884
Joined: Wed Feb 28, 2007 2:57 pm
Contact:

Post by Eke » Mon Aug 16, 2010 9:02 am

Nemesis wrote:Hi Eke, thanks for doing these measurements, this is very useful info! One thing to note, I suspect there may actually be a 1 step latency with the values you've measured, simply because the VDP most likely updates the HV counters between asserting interrupts, and latching the new state for the HL line. Eg, I suspect the VDP is doing the following steps in order, each internal pixel clock cycle:
1. Update HV counter
2. Latch current HL line state, and latch HV counter if line has just been asserted.
3. Trigger any required interrupts

It makes sense that the VDP would trigger interrupts after updating the HV counters and latching a new value for the HL line, since both these steps tell the VDP which interrupts need to be triggered. This would mean that the latched HV counter values you're getting are probably one step ahead of the true values at the time the interrupt line was asserted, meaning HINT pending would be set at the same time as VCounter increment, 0xA5 or 0x85, and Z80 int is triggered at 0x01, which is the same time as the F flag is set in the status register.
Yeah, maybe you are right. I guess this is as much accurate as we could do anyway (and using 0xA5 and 0x85 in an emulator just work fine). Even with super cycle-accurate CPU cores (lol), it would not be possible to exactly emulate the VDP latency between the time ports are read and the one value on ports are updated. This will probably always remain a mystery and is definitively not necessary to make games run correctly.


That's said, I was rather thinking the VDP raster engine was running at the highest clock (MCLK) and that pixel engine (including internal HCounter) was running separately, at the pixel clock.

Then following events could occur (at MCLK rate):

1) every line, wait for line end (Hcounter $84->$85, or more likely the 9-bits value) then increment VCounter.
2) if VBLANK flag is set, reload HINT Counter else decrement it.
3) if VCounter=$E0($F0), set VBLANK flag else if VCounter=$FF, clear it.
4) if HINT Counter overflows, set HINT flag then reload HINT Counter.
5) if HINT is enabled and HINT flag is set, interrupt control asserts /IPL2.
6) if VCounter=$E0($F0), wait for Hcounter $00->$01 then set VINT flag.
7) if VINT is enabled and VINT flag is set, interrupt control asserts /IPL2 and /IPL1.


I also think that /HL latches the internal HCounter value immediately (or at least faster that one pixel clock), it has been designed for that specific reason so it's very unlikely there is so much latency (we are talking about 2 pixels) there.

Much probably, there is latency between the moment VCounter is incremented, pending flag are set and interrupt lines are triggered by the interrupt control, which explains the observed values.


About what happens when both pending flags are set and /INTACK is asserted, i don't have yet run tests but I think I guess how it behaves. It probably depends on the current state of /IPL lines, if both are asserted, VINT flag is cleared, if only /IPL2 is asserted, HINT flag is cleared. The interrupt control then updates the state of /IPL lines regarding the remaining flag.

In theory and under very specific timings (and this is what this strange appendix is describing), it could happen that IRQ4 is being acknowledged by the CPU *just* when the VDP is updating the /IPL lines for VINT, which would result in VDP clearing VINT flag instead of HINT flag and the CPU not seeing IRQ6 but seeing IRQ4 twice.

Eke
Very interested
Posts: 884
Joined: Wed Feb 28, 2007 2:57 pm
Contact:

Post by Eke » Tue Aug 17, 2010 1:38 pm

I confirmed that if both flags are set (for example if both interrupts were disabled or masked), when it receives an interrupt acknowledge, the VDP will look at the current IRQ level and clear the appropriate flag.

I also verified that the display ON/OFF bit has no effects on interrupts.

Finally, I took some time to test VDP Mode 4 (which is enable by clearing register #1 bit 2) and here are my results:

- the HVC latch bit does nothing: by default, VCounter is free-running and HCounter returns a fixed value, presumly the value that was latched when VDP switched into Mode 4.

- if enabled, HINT (IRQ4) seems to be generated at fixed line, no matter what value is loaded into reg#10, at VC=$C0 and $HC=$F6

- if enabled, VINT (IRQ6) is generated at VC=$C0 and $HC=$02. Z80 INT is also generated at the same point, dependless of the state of VINT.

- pending flags seem to be immediately cleared, VINT flag can be detected by a tight loop reading VDP status but not if you read it on line $C1 for example (reading VDP status also clear the flag apparently). Due to the limited way it was tested, I'm not sure how accurate this description is but all tests about pending interrupts return $0000 in HV Counter values which would indicate interrupts didn't get triggered once they were reenabled/unmasked

Post Reply