codeslinger.co.uk

Sega Master System - Interrupts.

Interrupts:

There are three different interrupt mechinisms the Z80 can use but the SMS only uses one of them (interrupt mode 1).

The sega master system has 3 hardware interrupts. A hardware interrupt is a way that the a piece of hardware can signal the CPU to stop doing what it is currently doing and start doing something else. It is a way of achieving multitasking. There are two types of interrupts, maskable interrupts and none-maskable interrupts. The difference is that you can disable maskable interrupts from interrupting the CPU but you cannot stop a none maskable interrupt. Whenever a none maskable interrupt occurs the CPU will stop doing what its doing and service it. An interrupt gets serviced by the CPU pushing its current program counter on to the stack and jumping to the address of the Interrupt Handler. When it has finished servicing the interrupt handler it will set its program counter back to what it was before the interrupt occurred and then carry on where it left off.

Maskable Interrupts:

The Z80 has two interrupt status flip flops which I will call FF1 and FF2. If FF1 is set then maskable interrupts are enabled and can be signalled at any time. When FF1 is not set then the maskable interrupts will not get serviced until FF1 becomes set. However it is important to know that if FF1 is not set and an interrupt is being requested, by the time FF1 becomes set again that same interrupt may no longer be requesting to be serviced so that interrupt would never have got serviced. Before I explain what FF2 does i'll mention that a none maskable interrupt always takes priority over a maskable interrupt. So when a none maskable interrupt is signalled FF1 is set to off so no maskable interrupts can interrupt the current servicing of the none maskable interrupt. When a none maskable interrupt is signalled FF2 becomes the value of FF1 and FF1 is set to off. This way when the none maskable interrupt finishes and returns with a Z80 RETI instruction FF1 is set back to what it was before the none maskable interrupt was serviced which is the value of FF2. The following shows how the Z80 instructions affect the flip flops:

EI (enable interrupts): FF1 = on and FF2 = on
DI (disable interrupts): FF1 = off and FF2 = off
RETI (return from interrupt routine): FF1 = FF2

Actually EI doesnt change the value of FF1 and FF2 to "on" until the instruction after EI.

There are two maskable interrupts that can occur in the SMS and they are both signaled from the VDP. The first interrupt is the vertical sync interrupt. This occurs when the V counter first moves out of the active display area to the none active display area. As discussed in the VDP section of these tutorials when this happens a bit in the VDP status register is set showing the VDP is requesting an interrupt. However it will only request an interrupt if vertical sync interrupts are enabled in the appropriate VDP register, this also was discussed and implemented in the VDP section.

The second maskable interrupt is the line counter. The game can set the line counter to any value whilst the vdp is outside of the active display period (except the first line of the none active display period). When the vdp is in the active display period (also the first line of the none active display period) the line counters value is decremented each time the v counter moves onto the next line. When the line counters value goes less than 0 then its value is reset to what it started at and a line interrupt is requested. All this was discussed in more detail in the VDP section of these tutorials.

One important difference between the vsync interrupt and the line interrupt is that the vsync interrupt will constantly request to be serviced whilst the vsync bit is set in the vdp status register and vsync interrupts are enabled in the appropriate vdp register. However with a line interrupt it is requested once and forgotten about until it occurs again.

The VDP can keep requesting interrupts but until FF1 is set to "on" then none of them will get serviced. When an interrupt has been accepted for being serviced (FF1 is on) then the current program counter is pushed onto the stack and the program counter is set to 0x38 which is the address of the interrupt handling routine.

None Maskable Interrupts:

There is only one none maskable interrupt called the "Reset Interrupt". This occurs when the player pushes the reset button on the control pad. The way to emulate this is to have a boolean variable (from here on referred to as m_ResetInterrupt) which is set to true when the reset button is pushed, and then the main emulation loop should check this variable after every instruction it emulates. As previously mentioned you cannot turn off none maskable interrupts and they have a higher priority than maskable interrupts. When a none maskable interrupt is signalled the CPU pushes its program counter on to the stack and jumps to the interrupt handler for the none maskable interrupt which is address 0x66.

Handling Interrupts:

In the "Getting Started" section of these tutorials I mentioned that I still need to implement the HandleInterrupts function in the main emulation loop. Now we have armed ourselves with all the information needed for implementing this function we can finally do it:

void Emulator::HandleInterrupts()
{
  if (m_ResetInterrupt && !m_ServicingReset)
  {
   m_ServicingReset = true ;
   m_ResetInterrupt = false ;
   FF1 = false ;
   m_Halted = false ;
   PushWordOntoStack(m_ProgramCounter) ;
   m_ProgramCounter = 0x66 ;
  }

  if (m_GraphicsChip.IsRequestingInterupt())
  {
   if (FF1 && m_InteruptMode == 1)
   {
    m_Halted = false ;
    PushWordOntoStack(m_ProgramCounter) ;
    m_ProgramCounter = 0x38 ;
    m_IFF1 = false ;
    m_IFF2 = false ;
   }
  }
}

Remember that the game can sit and wait for an interrupt to happen by calling the HALT instruction. This means no other instruction will be executed until an interrupt occurs. This is why we set the m_Halted variable back to false so the cpu can then start executing instructions again. You will have also noticed that I have an extra boolen variable called m_ServicingReset. This simply stops another reset interrupt from occuring whilst a previous reset interrupt is currently being serviced.