Part 2
The Intel 8080 Processor
Disclaimers / Information:
1. I am using Space Invaders ROM sets under the "Fair Use" policy for demonstration purposes only.
2. Blitz3D has no means to directly work with binary or hexadecimal values and therefore all code uses decimal calculations.
3. I am writing this as one single simple longhand piece of code so that all levels can read and understand.
4. I am using Blitz3D for coding which is a derivative of Blitz Basic, an older language but still a capable one for some projects, and its easy for all to read and understand.
Frequently Asked Questions
1. Do I need to be super switched on to understand this and write an emulator?
a. No. I can do it and believe me, I am no genius!
2. Do I need to have any prior understanding of how processors work?
a. No, you can pick it up as you go.
3. Do I need to understand the Space Invaders code?
a. No. We are writing an emulator, not rewriting the game.
The Intel 8080a processor is an 8-bit processor with 16-bit memory addressing. This means that it reads instructions or "Opcodes" that are 1 byte in length, but it can read from, and write to memory addresses that are 2 bytes or 16 bits in length meaning that it can support up to 64 kB of memory area. Lets look at some of the other features that this processor has.
Registers
Registers are small areas of memory within the processor where values can be stored, used and manipulated. The Intel 8080 processor has eight 8-bit registers (A, B, C, D, E, H, L and Flags) and two 16-bit registers (Program Counter and Stack Pointer).
As you would expect, 8 bit registers deal with bytes of data, and 16 bit registers deal with memory addresses, however some of the 8 bit registers are often used in pairs to store memory addresses e.g. BC, DE and HL.
Program Counter - This 16 bit register points to the memory location of the current Opcode being executed. On completion, it increments to the next instruction address.
Stack Pointer - This points to the current memory location of the stack. The stack is an area of RAM where the processor stores useful stuff that it will need later. I will talk more about the stack further on.
Register A - Register A, also known as the Accumulator, is a special register that allows the processor to carry out all sorts of logical functions on the 8 bit value being stroed in there, as well as other standard register functions.
Registers B, C, D, E, H and L - These registers are similar to the Accumulator, being able to store an 8 bit value in each, but lack the functionality of the Accumulator when it comes to what the processor can do to these values.
Flags - This register stores the state of the five "Conditional Flags" within the processor. These will be explained later.
Note: I get all of my information from google and manuals. I have read two opposing explanations of "Flags"; one states that they are contained in an eight bit register, the other states that they ar just individual bits / switches in the processor. It doesn't matter which is correct as we can still emulate them.
To further explain how registers work, lets have a couple of examples.
Example 1:
You have just started a game and have shot your first invader. Your current score needs to have the score of that invader added to it.
1. Read the memory location of your current score in registers D and E. This will be somewhere in the general purpose RAM.
2. Read the memory location of the value of that invader into registers H and L. This may be in general purpose RAM or somewhere in the ROM.
3. Read the value stored at the memory location stored in register pair DE into register A.
4. Read the value stored at the memory location stored in register pair HL into register B.
5. Add the value in Register B to the value in Register A.
Example 2:
You are copying a 100 byte area of ROM to the general purpose RAM.
1. Set register pair DE to the first memory location to be cread from in ROM.
2. Set register pair HL to the first memory location to be written to in RAM.
3. Set register B to 100.
4. Read the value stored at the memory location stored in register pair DE into register A.
5. Write the value stored in Register A to the memory location stored in register pair HL.
6. Increment DE by 1.
7. Increment HL by 1.
8. Decrement Register B by 1.
9. Repeat until Register B=0.
As far as coding goes, all of the registers can be variables within our emulator.
; 8 Bit Registers
Local A=0
Local B=0
Local C=0
Local D=0
Local E=0
Local H=0
Local L=0
Local flags=0
; 16 Bit Registers
Local PC=0
Local SP=0
Note: Program Counter can start at 0 because the space invaders code is executed from memory location 0, but this is not always the case with all Intel 8080 programs.
Conditional Flags
Conditional flags are single bits that are set or reset depending on the output of a processor function.
The conditional flags are:
1. Zero or Z flag - Set to 1 if the result of an operation is 0, otherwise its reset to 0.
2. Sign or S flag - Set to 1 if the result of an operation is a minus value, otherwise its reset to 0.
3. Carry/Borrow or C flag - Set to 1 if there is a carry i.e. if the result is greater than 255, or if there is a borrow i.e. if the result is less than 0, otherwise its reset to 0.
4. Parity or P flag - Set to 1 if the number of 1's in the byte is an odd number, otherwise its reset to 0.
5. Auxiliary Carry flag - Difficult to explain in a one liner. This flag is used in conjunction with Binary Coded Decimal and is only really used by one Opcode, so is better explained when we come to code the opcode itself.
Again as far as coding goes, all of the conditional flags can be variables within our emulator. Despite the fact that they exist in an 8-bit register, it is easier to have them as individual values and then calculate the value of the register as a whole only when we need to.
; Conditional Flags:
Local Z=0
Local S=0
Local P=0
Local C=0
Local AC=0
Opcodes
A stated above, Opcodes are processor instructions. They are read from the code in the memory area and then carried out by the processor. The Intel 8080 is an 8-bit processor so it can only take 8-bit instructions. This means that it is capable of carrying out 256 different instructions. DON'T PANIC! Most of these instructions are very simple and repeats of previous instructions with very minor changes.
Some Opcodes have a byte of data attached to it so for example if the Opcode basically broke down as "Put this value in register A.", the second byte would be "This value".
Some Opcodes have two bytes of data attached to them so again for example if the Opcode basically broke down to "Put the value in register A into this address.", then the two bytes after would be "This address".
The bytes following the instruction are called "Operands" and the overall length of the Opcode is counted in "Opbytes".
Example - Opcode 0x00 (Opcode 0 in decimal) which is a "NOP" or "No Operation" has no operands and a length of one opbyte.
Example - Opcode 0xC6 (Opcode 198 in decimal) which is an "ADI" or "Add Immediate" has one operand and a length of two opbytes. The data immediately after the opcode is added to Register A.
Processor Speed / Clock Cycles
The processors speed is dictated by its "Clock". The clock feeds the processor pulses at a specific rate, and for each pulse the processor carrys out part of an Opcode. The Intel 8080a is clocked at around 2 MHz or 2 million pulses per second. Each Opcode takes a certain amount of pulses or clock cycles to complete e.g. our "NOP" opcode takes 4 clock cycles to execute, whilst our "ADI" opcode takes 7.
There are some really good websites around with Opcode tables that show each Opcode, how many Opbytes and how many clock cycles for each Opcode.
Below is an Opcode table from www.pastraiser.com which shows the name of each Opcode, how many Opbytes, how many clock cycles and what conditional flags are affected for each Intel 8080 Opcode.
The Space Invaders screen runs at 60 Hz, or 60 updates a second (60 FPS) and therefore if the processor is clocked at 2000000 cycles a second, and the screen is 60 FPS then we need to update the screen every 33333 clocks of processor time and so as we run opcodes, we must count how many clocks have been used so that we can time the screen refresh accurately.
So with all of the above in mind, we can now start writing our processor emulator code. We have already stated the register and conditional flag variables, but we need some more variables to be able to start and then we need an outer processor shell to put our opcode interpreters into.
; Processor Variables:
Local opcode
Local opbytes
Local clock_cycles
Local clock_cycle_counter
Local display_update_constant=33333
repeat
repeat
opcode=memory_array(PC,0)
; Insert Opcode Interpreter Here.
pc=pc+opbytes
clock_cycle_counter=clock_cycle_counter+clock_cycles
until clock_cycle_counter>=33333
clock_cycle_counter=clock_cycle_counter-33333
; Insert Display Update Routine Here.
Until keyhit(1)
We read the opcode value at memory location (program counter). On completion of executing the opcode we increment program counter ready to read the next opcode, and we keep a count of how many clock cycles we have used. When we have used enough clock cycles, we stop executing opcodes and update the display.
Note: Program Counter will not always increment by the number of opbytes. There may be cases where we need to skip this, or modify it to change program counter to a different location in the memory, but for now, this will do.
Lets now do some code for a typical opcode. We will do a really simple one, a "NOP" or No operation. All that this opcode does is waste 4 cycles of processor time. Its the first opcode called up in the space invaders code.
; Insert Opcode Interpreter Here.
select true
; Opcode 0x00 or 0 - "NOP"
case opcode=0
opbytes=1
clock_cycles=4
end select
And thats our first opcode done. 255 to go...Actually thats not quite true as if you check the opcode table above, you will see that there's 13 NOPs at opcodes 0x00 (0), 0x08 (8), 0x10 (16), 0x18 (24), 0x20 (32), 0x28 (40), 0x30 (48) and 0x38 (56). In this case, its more like:
; Insert Opcode Interpreter Here.
select true
; Opcode 0x00 or 0 - "NOP"
case opcode=0
opbytes=1
clock_cycles=4
; Opcode 0x08 or 8 - "NOP"
case opcode=0
opbytes=1
clock_cycles=4
; Opcode 0x10 or 16 - "NOP"
case opcode=0
opbytes=1
clock_cycles=4
; Opcode 0x18 or 24 - "NOP"
case opcode=0
opbytes=1
clock_cycles=4
; Opcode 0x20 or 32 - "NOP"
case opcode=0
opbytes=1
clock_cycles=4
; Opcode 0x28 or 40 - "NOP"
case opcode=0
opbytes=1
clock_cycles=4
; Opcode 0x30 or 38 - "NOP"
case opcode=0
opbytes=1
clock_cycles=4
; Opcode 0x38 or 56 - "NOP"
case opcode=0
opbytes=1
clock_cycles=4
end select
Ok, I think we can wrap up part 2 there. Look out for part 3 where we will be looking into opcodes that are a bit more complex than our NOP opcode above.