Z80 Info:
The z80 is an 8 bit microprocessor which is used in many early video game systems (and many other systems). It has 8 standard registers, 8 shadow registers, 2 index registers, 1 refresh register and an interrupt vector register. It also has a stack, program counter and 8 flags (only 6 of which are used in the SMS Z80).
The Registers
The 8 standard registers are named A,B,C,D,E,F,H,L. All are 8 bits and have different uses.
A: Accumulator
B: General Purpose
C: General Purpose
D: General Purpose
E: General Purpose
F: Flag Register
H: Address register
L: Address register
Althrough these registers have different uses we dont need to worry about using them because it will be the ROM that uses them, we just need to know which one is being used and the z80 instructions tell us this. The only register we need to work with is the flag register. When emulating the z80 instructions we need to check which flags (if any) the instruction affects and then make sure the flag register gets set accordingly.
There are also 8 shadow registers named A',B',C',D',E',F',H',L'. These registers can also be paired to form 4 sets of 16 bit registers AF',BC',DE',HL'. These registers cannot be modified directly by any z80 instruction. Their purpose is to be exchanged with their corresponding standard registers. So if register pair AF value was 0xABCD and register pair AF' value was 0x1234 then using an exchange instruction would swap the values so AF is 0x1234 and AF' is 0xABCD. The programmer would carry on using the AF registers through the Z80 instructions so the AF value would no longer be 0x1234 and then they can exchange the registers back so AF gets set back to its original value 0xABCD. Once again you do not need to worry about the details of the use of these shadow registers. Just make sure you emulate the exchange instructions correctly.
The refresh register is a counter that gets incrememented each time an opcode is fetched (including opcode prefixes which I'll explain later). The purpose of this register is usually to generate random numbers.
The interrupt register works with interrupt mode 2 which as I explain later is not used in the SMS so you do not need to emulate this
The two index registers IX and IY are used with two opcode prefixes (explained later) which replaces the registers H and L in instructions which use registers HL. For example ADD HL, HL would become ADD IX,IX for one opcode prefix and ADD IY,IY for ther other. When an instruction uses HL for memory accress (represented by (HL)) then an 8bit immediate offset is added to registers IX and IY. This will all be explained later.
The Flags
Each bit in the 8 bit flag register (except bits 5 and 3 which arent used) represents a flag:
Bit 7 = (S)Sign Flag
Bit 6 = (Z)Zero Flag
Bit 5 = Not used
Bit 4 = (H)Half Carry Flag
Bit 3 = Not used
Bit 2 = (P/V)Parity or Overflow Flag
Bit 1 = (N)Subrtract Flag
Bit 0 = (C)Carry Flag
The zero flag is set when the result of an operation is zero
The Half carry flag is set if a subtract operation carried from bit 4 to bit 3 or, or with an 16 bit operation carried from bit 12 to 11. With addition operations it is set if carried from bit 3 to 4 or with 16 bit addition 11 to 12
The Parity or Overflow flag has two meanings. Some instructions use the parity flag which means it gets set if the result of the operation has an even number of bits set. The overflow flag is used by some instructions when the 2-complement of the result does not fit within the register
The subtract flag is simply set when the instruction is a subtraction
The carry flag is set when the instruction overflows its upper or lower limits.
I shall show how to emulate the settings of some of these flags when I show some opcode emulation examples below.
Program Counter and Stack Pointer
The program counter points to the address of the next opcode in memory to execute. The stack pointer points to the next address space where a value that gets added to the stack is stored. The stack works down the address space, it is initialized to address 0xDFF0 so if a value is pushed on to the stack, this is the address it gets added to and the stack pointer gets decremented to 0xDFEF. Popping values off the stack will increment the stack pointer and return the value found at the new position.
Because the z80 address space is 0x10000 in size (0x0-0xFFFF) both the program counter and the stack pointer need to be a WORD in size.
Emulation of the Z80
As previously mentioned the program counter and the stack pointer are both words in size but so are the refresh register and the interrupt register. These can easily be emulated with the following declarations:
WORD m_ProgramCounter
WORD m_StackPointer
WORD m_RegisterR
WORD m_RegisterI
union Register
{
WORD reg ;
struct
{
BYTE lo ;
BYTE hi ;
};
};
Register m_RegisterAF ;
Register m_RegisterBC ;
Register m_RegisterDE ;
Register m_RegisterHL ;
Register m_RegisterAFShadow ;
Register m_RegisterBCShadow ;
Register m_RegisterDEShadow ;
Register m_RegisterHLShadow ;
Register m_RegisterIX ;
Register m_RegisterIY ;
Instructions:
As previously mentioned the program counter points to the next opcode in memory to execute. The program counter is initialized to 0x0. The SMS will get the next opcode from memory, execute the opcode and then increment the program counter so it points to the next opcode to execute. However I prefer to emulate this by retrieving the next opcode from memory and then immediately incrementing the counter before executing the opcode. I do this because the call and jump instructions set the program counter, so I dont wish to then increment the program counter after the call and jump instruction have set it.
int Z80::ExecuteNextInst( )
{
m_Context.m_OpcodeCycles = 0 ;
BYTE instruction = ReadMemory(m_InternalMemory[m_Context.m_ProgramCounter]) ;
m_Context.m_ProgramCounter++ ;
ExecuteOpcode(instruction) ;
assert(m_Context.m_OpcodeCycles > 0) ;
reurn m_Contex.m_OpcodeCycles ;
}
The ExecuteOpcode function takes the opcode as a parameter and will emulate the z80 opcode. It just contains a big swith statement which looks up what the opcode is meant to do and emulates it. Using the opcode table that I linked to on the "Resource" section of the SMS tutorials I can give the following snippet of how the ExecuteOpcode function behaves:
void Z80::ExecuteOpcode(BYTE opcode)
{
IncrementRegR() ;
switch (opcode)
{
case 0x0: m_Context.m_OpcodeCycle = 4 ; break ; // no-op
case 0x8: Exchange(m_Context.m_RegisterAF.reg, m_Context.m_RegisterAFShadow.reg); break;
case 0x87: ADD_8BIT(m_Context.m_RegisterAF.hi, m_Context.m_RegisterAF.hi) ; break ;
case 0xC9: RETURN( ) ; break ;
case 0xBE: COMPARE(ReadMemory( m_Context.m_RegisterHL.reg) ); break ;
case 0xCB: ExecuteCBOpcode( ) ; break ;
default: assert(false); break ; // unhandled opcode
}
}
Opcode Prefixes:
Because the Z80 opcodes are a byte in size there are only 256 possible instructions that can be used. However the developers got round this by using opcode prefixes. Opcode 0xCB is an opcode prefix this means that the next instruction is read from memory and this value represents a different instruction. For example if the opcode 0x8 is read from memory when executed this instruction means "Exchange AF with AFShadow". However if instruction 0xCB is read then another opcode is immediately read, and if this new opcode was 0x8 it would not be the exchange instruction, it would be a "RRC" instruction. This way by using opcode prefix 0xCB the developers now had access to an extra 256 instructions because they could reuse the values. Once again look at that Opcode hex values resource I link to in the "Resource" section of this site and look at how opcode 0x8 in the first table (the standard opcode table) means "EX AF,AF'" and now look at the second table (the CB prefix table) and notice how opcode 0x8 is "RRC b". This means our ExecuteCBOpcode function which I've used in that switch block above can be implemented like this:
void Z80::ExecuteCBOpcode()
{
BYTE opcode = ReadMemory(m_InternalMemory[m_Context.m_ProgramCounter]) ;
m_Context.m_ProgramCounter++;
IncrementRegR() ;
switch (opcode)
{
case 0x8: RRC(m_Context.m_RegisterB.hi); break ;
default: assert(false); break ; // unhandled opcode
}
}
void Z80::ExecuteDDOpcode()
{
BYTE opcode = ReadMemory(m_InternalMemory[m_Context.m_ProgramCounter]) ;
m_Context.m_ProgramCounter++;
IncrementRegR() ;
switch (opcode)
{
case 0x2B: DEC(m_Context.m_RegisterIX); break;
case 0xBE: COMPARE(ReadMemory( GetIXIYOffset(m_Context.m_RegisterIX)) ); break ;
default: assert(false); break ; // unhandled opcode
}
}
WORD Z80::GetIXIYAddress(WORD ixiy)
{
SIGNED_BYTE offset = ReadMemory(m_InternalMemory[m_Context.m_ProgramCounter]) ;
m_Context.m_ProgramCounter++;
return ixiy + offset ;
}
Of course with all rules there are a few exceptions. "JMP (HL)" does not have the offset added to it for 0xDD and 0xFD prefixes, so it is "JMP(IX)" and "JMP (IY)". Also the "EX DE,HL" instruction is not affected by the 0xDD and 0xFD prefixes (meaning HL stays as HL).
Flag awareness
There are a few opcodes which behave differently based on the current flag status. For example "JMP z" will jump to a specific address if the z flag is set, otherwise it will not jump. Similary "CALL nc" will call a function if the carry flag isnt set, otherwise it will not call. Be aware that these instructions carry with them a different clock cycle count if it goes one way or another. To get the opcodes clock cycle count look at the T-States of the Sean Young documentation "Z80 info" in the resource section of my site.
You will also notice that there are few peculiar instructions like "JMP m" and "CALL pe". This is what the following letters mean:
m = S flag is set
po = P/V flag is off
p = S flag is off
pe = P/V flag is set
Opcode Examples:
Now we have the switch blocks setup it is time to emulate the functions they call. In our first switch statement the EXCHANGE function is called, and this is its implementation:
void Z80::Exchange(WORD& ex1, WORD& ex2)
{
WORD temp = ex1 ;
ex1 = ex2 ;
ex2 = temp ;
m_Context.m_OpcodeCycle = 4;
}
The next function shows how to emulate the JR (jump immediate instruction). There are two types of JR instructions, the first type will always jump and the second type will only jump if a certain condition is true. We can emulate it like this:
void Z80::CPU_JUMP_IMMEDIATE(bool useCondition, int flag, bool condition)
{
m_Context.m_OpcodeCycle= 12 ;
if (!useCondition)
{
SIGNED_BYTE n = (SIGNED_BYTE)ReadMemory(m_Context.m_ProgramCounter) ;
m_Context.m_ProgramCounter += n;
}
else if (TestBit(m_Context.m_RegisterAF.lo, flag) == condition)
{
SIGNED_BYTE n = (SIGNED_BYTE)m_Context.ReadMemory(m_Context.m_ProgramCounter) ;
m_Context.m_ProgramCounter += n ;
}
else
{
m_Context.m_OpcodeCycle= 7 ;
}
m_Context.m_ProgramCounter++ ;
}
The next example shows how the 8 bit decrement function works. It decrements the register being passed as an argument then sets all the flags.
void Z80::CPU_8BIT_DEC(BYTE& reg, int cycles)
{
m_Context.m_OpcodeCycle = cycles ;
BYTE before = reg ;
reg-- ;
// set z flag if result is negative
if (reg == 0)
m_Context.m_RegisterAF.lo = BitSet(m_Context.m_RegisterAF.lo, FLAG_Z) ;
else
m_Context.m_RegisterAF.lo = BitReset(m_Context.m_RegisterAF.lo, FLAG_Z) ;
// set h flag if lower nibble is 0, meaning it will carry from bit 4
if ((before & 0x0F) == 0)
m_Context.m_RegisterAF.lo = BitSet(m_Context.m_RegisterAF.lo, FLAG_H) ;
else
m_Context.m_RegisterAF.lo = BitReset(m_Context.m_RegisterAF.lo, FLAG_H) ;
// v is calculated not p
if (before == -128)
m_Context.m_RegisterAF.lo = BitSet(m_Context.m_RegisterAF.lo, FLAG_PV) ;
else
m_Context.m_RegisterAF.lo = BitReset(m_Context.m_RegisterAF.lo, FLAG_PV) ;
// set subtract flag
m_Context.m_RegisterAF.lo = BitSet(m_Context.m_RegisterAF.lo, FLAG_N) ;
// set sign flag to bit 7 of the result
if (TestBit(reg,7))
m_Context.m_RegisterAF.lo = BitSet(m_Context.m_RegisterAF.lo, FLAG_S) ;
else
m_Context.m_RegisterAF.lo = BitReset(m_Context.m_RegisterAF.lo, FLAG_S) ;
}