M68K Interrupt Processing
Posted: Sat Oct 31, 2015 9:59 am
It's known that you can execute at least one instruction after enabling VINT in the VDP with a pending VINT. Emulating this is required to make Sesame Street Counting Cafe to work. In an effort to understand exactly what's going on here, I've spent some time writing a test ROM and doing some measurements with a logic analyzer. In addition to answering the original question, I've got timing information for the various stages of interrupt processing and I've noticed something that is technically in the documentation, but not very obvious.
It turns out that the VDP asserts the relevant IPL lines almost immediately after the relevant 68K write ends. There is a small delay which seems to vary from around 90-210ns (presumably based on how in sync the 68K is with SC at the time of the write), but this is not the cause of the delay we have observed in the start of interrupt processing. This becomes clear if you use an instruction that has a couple of bus operations after the write that triggers an interrupt (I used move.l with the register write for enabling the interrupt in the upper word, this gives one extra bus operation to write the low word and a second one to prefetch the next instruction). Instead, it seems that contrary to at least one of the timing diagrams, there is a one instruction latency on interrupt processing. Since this is a 68K level delay, it applies equally to all interrupt sources (I haven't tested level 2 interrupts, but I don't see why it would not apply).
Once interrupt processing starts, the sequence is as follows:
6 cycles - idle bus
4 - save SR
4* - interrupt ack cycle
4 - idle bus
8 - save SP
8 - read vector address
4 - prefetch
2 - idle bus
4 - prefetch
This yields the documented 44 cycles with 5 reads and 3 writes. Obviously all the bus operations can be extended by !DTACK, so this is only the minimum time. The item marked with an asterisk bears some extra explanation though.
The main purpose of the interrupt ack cycle is to allow a peripheral to supply an interrupt vector; however, the 68K is designed to be compatible with old 6800 peripherals which do not support this functionality. The 6800 also had a synchronous bus and it's peripherals were slow by 68K standards. To deal with all these problems, a signal called !VPA (Valid Peripheral Address) was added. This is used instead of !DTACK to indicate that an address is serviced by a 6800 peripheral. It is also used to respond to an interrupt ack cycle to cause an autovector to be used rather than a value from the bus. Unfortunately, since an interrupt ack cycle is a real bus cycle and !VPA is also used to change the timing of a bus operation, this introduces some extra delay into interrupt processing even though the value from the bus is thrown away.
This is not really obvious from reading the sections on interrupt processing in the 68000 User Manual, but you can sort of figure it out by the use of !VPA and the timing information provided in Appendix B for 6800 peripheral access. According to the timing diagrams there, the best case for such an access is 10 cycles and the worst case is 19 cycles. In my testing, I have actually observed a best case of 9 cycles. I suspect the discrepency is due to some assumptions about how fast !VPA can be asserted which are not true on the Genesis/Megadrive.
Perhaps that's already known, but it was quite a surprise to me at least.
Anyway, here's a test ROM for the interrupt latency issue. Both the VINT and HINT lines yield a value of 1 instruction on the real hardware. The code for the two cases is a bit different to excercise some edge cases so even if you are treating both interrupt types the same you may get different values on your emulator.
Source
ROM
Some quick testing suggests that no emulators get the latency quite right, though a couple (Fusion and Genesis Plus GX) are good enough for Sesame Street and Genesis Plus GX is quite close (seems to be emulating roughly 4 cycles of delay, rather than a 1 instruction latency). Not sure about Exodus as I'm having a bit of trouble getting 2.0.X to run under Wine currently, but at least 1.0 did not run seem to emulate any interrupt delay.
It turns out that the VDP asserts the relevant IPL lines almost immediately after the relevant 68K write ends. There is a small delay which seems to vary from around 90-210ns (presumably based on how in sync the 68K is with SC at the time of the write), but this is not the cause of the delay we have observed in the start of interrupt processing. This becomes clear if you use an instruction that has a couple of bus operations after the write that triggers an interrupt (I used move.l with the register write for enabling the interrupt in the upper word, this gives one extra bus operation to write the low word and a second one to prefetch the next instruction). Instead, it seems that contrary to at least one of the timing diagrams, there is a one instruction latency on interrupt processing. Since this is a 68K level delay, it applies equally to all interrupt sources (I haven't tested level 2 interrupts, but I don't see why it would not apply).
Once interrupt processing starts, the sequence is as follows:
6 cycles - idle bus
4 - save SR
4* - interrupt ack cycle
4 - idle bus
8 - save SP
8 - read vector address
4 - prefetch
2 - idle bus
4 - prefetch
This yields the documented 44 cycles with 5 reads and 3 writes. Obviously all the bus operations can be extended by !DTACK, so this is only the minimum time. The item marked with an asterisk bears some extra explanation though.
The main purpose of the interrupt ack cycle is to allow a peripheral to supply an interrupt vector; however, the 68K is designed to be compatible with old 6800 peripherals which do not support this functionality. The 6800 also had a synchronous bus and it's peripherals were slow by 68K standards. To deal with all these problems, a signal called !VPA (Valid Peripheral Address) was added. This is used instead of !DTACK to indicate that an address is serviced by a 6800 peripheral. It is also used to respond to an interrupt ack cycle to cause an autovector to be used rather than a value from the bus. Unfortunately, since an interrupt ack cycle is a real bus cycle and !VPA is also used to change the timing of a bus operation, this introduces some extra delay into interrupt processing even though the value from the bus is thrown away.
This is not really obvious from reading the sections on interrupt processing in the 68000 User Manual, but you can sort of figure it out by the use of !VPA and the timing information provided in Appendix B for 6800 peripheral access. According to the timing diagrams there, the best case for such an access is 10 cycles and the worst case is 19 cycles. In my testing, I have actually observed a best case of 9 cycles. I suspect the discrepency is due to some assumptions about how fast !VPA can be asserted which are not true on the Genesis/Megadrive.
Perhaps that's already known, but it was quite a surprise to me at least.
Anyway, here's a test ROM for the interrupt latency issue. Both the VINT and HINT lines yield a value of 1 instruction on the real hardware. The code for the two cases is a bit different to excercise some edge cases so even if you are treating both interrupt types the same you may get different values on your emulator.
Source
ROM
Some quick testing suggests that no emulators get the latency quite right, though a couple (Fusion and Genesis Plus GX) are good enough for Sesame Street and Genesis Plus GX is quite close (seems to be emulating roughly 4 cycles of delay, rather than a 1 instruction latency). Not sure about Exodus as I'm having a bit of trouble getting 2.0.X to run under Wine currently, but at least 1.0 did not run seem to emulate any interrupt delay.