Thursday, 14 October 2021

Lunchtime Coder #10 - Space Invaders RAM Mirror

Whilst I was gathering information to write my Space Invaders emulator, I came across some information that completely baffled and stumped me.

It was on the computerarcheology.com website under the Space Invaders Hardware page.

It was under the title "Memory Map" and stated the words "RAM mirror".

I had never heard of a RAM mirror before and had absolutely no idea of what it was, or what the term meant.

Did I need to allow for more RAM in my emulator? Was my emulator written incorrectly?

After a lot of reading from various different sites, I managed to put together the full picture of what a RAM mirror is and why it occurs, and I thought that it would be useful to explain this for other budding emulator enthusiasts.

Does It Affect Space Invaders Emulation?

The good news is that the RAM mirror will not need to be considered in a Space Invaders emulator.

Despite someone on an emulation forum insisting that a Space Invaders emulator needed more than 16 kB of memory to work correctly, it doesn't.

An 8 kB ROM area and an 8 kB RAM area arranged in one 16 kB memory area is all that is needed for a Space Invaders emulator. If your emulator is addressing above 16 kB, you have a bug.

So It Only Affects Space Invaders Hardware Then?

Well, no not really. An arcade Space Invaders machine doesn't contain a RAM mirror either, but it does have the potential to contain one.

Well What The F%&! Is It Then?

It was a way of saving time, money and resources when making the Space Invaders circuit boards.

The Intel 8080 processor has a 16 bit memory address bus, meaning that it can read and write to up to 64 kB of memory.

This address bus is accessed using 16 pins on the Intel 8080 processor. The address bus itself is just wires, or tracks on a circuit board.

16-Bit Address Bus Accessed Using Pins A0 to A15

But Space Invaders only uses a 16 kB memory area, and 16 kB can be accessed using only a 14 bit address.

To save time, money and resources, only 14 of the 16 address pins are wired or have tracks present on the circuit board to the memory area. Pins A14 and A15 are left unwired or untracked as reading from or writing to addresses beyond 16 kB will never be required in the Space Invaders cabinet. 

This means that at the memory end of the address bus, A14 and A15 are undefined; they are neither a 1 or a 0.

Because of this, If you write to RAM, all combinations of pins A14 and A15 will be written to RAM.

Example:

A RAM write of the value 0xff to address 0x3988 will result in a write to:

1. 0xff written to 0x3988 (A14=0 A15=0)
2. 0xff written to 0x7988 (A14=1 A15=0)
3. 0xff written to 0xb988 (A14=0 A15=1)
4. 0xff written to 0xf988 (A14=1 A15=1)

The RAM is mirrored every 16 kB and hence the term RAM mirror.

It would be the same case if the processor code was trying to make a write to 0x7988 or 0xb988 or 0xf988. Because the top two bits of the 16 bit memory address are undefined, the memory area will see all 4 addresses as the same address, with two undefined bits.

If it was only the 16th pin (A15) that hadn't been wired, then you would have a RAM mirror that mirrored every 32 kB, and likewise if it was the 14th, 15th and 16th pins (A13, A14 and A15) that hadn't been wired, you would have a RAM mirror that mirrored every 8 kB.

And that's the RAM mirror (or potential RAM mirror) in an arcade Space Invaders cabinet.

Friday, 1 October 2021

Lunchtime Coder #09 - Testing and Debugging your Space Invaders / Intel 8080 Emulator

It can be very frustrating when the code you have written doesn't do what you want it to do!

With an Intel 8080 emulator, you have 256 opcodes to write code for, meaning that all sorts of logic, syntax or just general brain fart errors can be buried in there somewhere causing all manner of unexpected behaviour when you try to run your emulator.

Fortunately help is at hand, and that help comes in the form of CPUDIAG.

CPUDIAG is an old Intel 8080 test program designed for use on CP/M machines. Its visual output is designed for sending ASCII characters to a printer, however once you get a basic idea of how the program works, you can use your own error monitoring to point out groups of opcodes that potentially have an issue.

I thought that a simple tutorial was in order to explain how this program can be used in a far simpler fashion than was originally intended, and to show how to get the most of of this useful test program.

In this tutorial, we will cover the following topics:

1. Preparation for using CPUDIAG.
2. Loading CPUDIAG into your emulator.
3. Interpreting the results.


1. Preparation for using CPUDIAG.

All good emulators should be able to generate a debug log. Its a valuable part of being able to determine your emulator output at a processor level, and can be very useful in tracking down bugs and issues.

A debug log should include all of the processor key parameters such as:

  • An opcode Count
  • Program Counter
  • Stack Pointer
  • Opcode Hex Value
  • Opcode Name
  • Operand or Operands
  • Register Values
  • Flag Values
  • I also include the value in the memory location stored in register pair HL


CPUDIAG only checks the functions of processor Opcodes, so if your Intel 8080 emulation is buried amongst other emulator parts e.g. Shift register / Display Output etc. then these can all be disabled.

CPUDIAG will run clusters of Opcodes and then do comparison checks. If the comparison checks come out incorrect, the program does a conditional call to 0x0689 to give an error report.

Rather than trying to code the output of the error report, it is much simpler to have a monitor program checking to see if Program Counter is set to 0x0689 and stopping the CPUDIAG code running if it does.

You will also need a binary file of CPUDIAG.

Download CPUDIAG.BIN Here!


2. Loading CPUDIAG into your emulator.

CPUDIAG needs loading into your emulators memory with the first byte at 0x0100.

You will then need to set your Program Counter to start reading the code at 0x0100.


3. Interpreting the results.

So, you run your emulator with CPUDIAG loaded into its memory. CPUDIAG runs then stops because the routine that you added to stop emulator execution if Program Counter is set to 0x0689 has activated, meaning that CPUDIAG has found an error in an opcode.

Its time to consult the debug log. Look at where the code has come to a grinding halt, then backtrack up the opcodes to the last opportunity that the program had to CALL to 0x0689 but didn't. The error is in an opcode between these two points.

Example Debug Log Output:

We can see that at Opcode Count 177 (Highlighted Green), a CNZ opcode was run. Fortunately the Z flag was set and so the program didn't call an error.

For the purposes of the example, we can see that at Opcode Count 193 (Highlighted Yellow), again a CNZ is run which will Call to the error routine if Z is not set. If we assume that Z isn't set and that's where your emulator stopped, we can see that there has been 16 Opcodes executed, so the error must lie in one of these opcodes.

Furthermore, we can see what each opcode did, and the affect it had on the processor key values. Opcode Count 180 shows an MVI C, D8 0x03 or move the immediate value 0x03 into register C. We can see on the next line that 0x03 is in register C and so this Opcode appears to be working correctly.

It can be seen that CPUDIAG is a useful tool to debug emulators. Even when I had Space Invaders fully working correctly, CPUDIAG still found two small errors in my Opcodes caused by incorrect syntax, so its always worth running your Intel 8080 emulator through it.

There is also a very good test program for 6502 emulators. Its designed specifically around 6502 emulators for the NES and so doesn't test decimal functionality, but it comes with its own debug log ready run and supports a number of "Illegal" opcodes so its simply a case of comparing your debug log against theirs and applying fixes until they are both the same. The program is called NESTEST.NES and was invaluable when I was trying to get my head around indirect addressing.

Lunchtime Coder #8 - Emulating the Intel 8080 DAA Opcode



There seems to be a real stigma around emulating the DAA opcode for reasons that I cannot understand.

I have even seen websites telling you not to bother emulating either the DAA opcode, or the Auxiliary Carry flag as their uses are limited and its not worth the hassle...

For me it was tricky but not that bad. Once you understand what DAA and the Auxiliary Carry flag do, it becomes pretty straight forward to code.

I decided to write a brief tutorial on both to assist other budding emulator coders and to show that it ain't that bad.

This tutorial assumes that you have an understanding of both Binary and Hexadecimal and will cover the following topics:

  • The basics of the DAA Opcode.
  • Usage of the DAA opcode
  • The Auxiliary Carry Flag.
  • Advanced DAA and Aux. Carry Flag.



1. The Basics Of The DAA Opcode.

The DAA or "Decimal Accumulator Adjuster" opcode is used to make the value in the accumulator look like, and behave like a decimal value.

It takes a binary value and converts it into a "Binary Coded Decimal" value.

As we know, in a byte we have 8 bits that can be set to 1 or 0. This is clumsy and awkward to work with directly and so Base 16 or Hexadecimal is used to represent the 8 bits in a simple two figure value.

Example:

Binary Value - 11010010
Hexadecimal Value - d2 or d2H or 0xd2 or $d2 depending on what notation you are using.
Decimal value - 210

To convert the binary value into a Hexadecimal value, the binary value is split into two 4 bit words known as nibbles. The byte is split into a higher and lower nibble, and each nibble is then converted into a single Hexadecimal digit.

What the DAA Opcode does is check the value of the higher and lower nibble, and if the values are not within the decimal range i.e. 0 to 9, it adds 6 to that value to bring it back into the decimal range.

Example:

Decimal range - 0 1 2 3 4 5 6 7 8 9
Hexadecimal range - 0 1 2 3 4 5 6 7 8 9 a b c d e f
Binary Value - 11010010
Hexadecimal Value - d2
Lower Nibble - 2 falls into the decimal range and so remains unchanged by DAA
Higher Nibble - d falls outside of the decimal range and therefore 6 is added, resulting in the value 3 with a carry.
DAA Hex Value - 32 with a carry

By adding six to a value outside of the Decimal range, the value is "skipping" over the a, b, c, d, e and f Hexadecimal counts, and making each figure look and behave like a decimal value.

Simple Pseudo code example:

Split Accumulator into High and Low Nibble.
If low nibble>9 then low nibble=low nibble+6
If high nibble>9 then high nibble=high nibble+6

This example is a vast over simplification, as there would be a carry from the low to the high nibble if the low nibble were greater than 9 and then 6 was added, and the same would go for the high nibble, but it illustrates the basic principal of the DAA opcode.

The carry from the low to the high nibble every time the value is outside of the decimal range is the important part of both DAA and the auxiliary carry bit. It is saying that if the value is greater than 9, then in decimal, the value would be 10 or above and so skip over the non decimal values to get to 10 or above so that the Hexadecimal looks like decimal, and also carries to the high nibble as if it were decimal.



2. Usage of the DAA Opcode.

Because the DAA opcode can make values in the Accumulator appear to be decimal values, the uses for early video games were normally values that required displaying on screen and were required to increment or decrement in a decimal style.

These would be items like your Score, number of credits remaining, fuel left etc.

Because the Binary Coded Decimal value can only contain numbers in the decimal range, a byte can have a maximum Binary Coded Decimal value of 99.

For routines like decrementing credits, 99 would be added to the current credits value to decrement the value by 1 e.g. 99+99=98 and a carry.

For score routines, the general routine would go something like this:

1. You shoot an invader.
2. The "Current Score" in RAM is read into the accumulator.
3. The fixed score in ROM for the invader you have shot is added to the Accumulator, and the flags set accordingly.
4. The DAA opcode is run to convert the value in the Accumulator into Binary Coded Decimal.
5. The new current score is stored from accumulator back to its RAM location.

Lets look at some examples:

Example 1 - DAA does nothing.

Current score=20 (This is Hexadecimal 20, but looks like Decimal 20).
Score for Invader shot=20 (Again Hexadecimal 20 but looks like Decimal 20)
New score=20H + 20H = 40H (This looks and behaves like Decimal, so no actions for DAA to do)

Example 2 - DAA into action.

Current Score=25 (Hexadecimal 25, but looks like Decimal 25).
Score for Invader shot=25 (The same as above).
New Score= 4a Hexadecimal
The low nibble (a) is greater than 9, so DAA will add 6 to the low nibble making it 0 with a carry to the high nibble.
New Score=50 (Hexadecimal 50 but looks like decimal 50, and 25D + 25D = 50D. All good.).

Example 3 - DAA needs help!

Current score=28 (Hexadecimal 28 but looks like decimal 28)
Score for Invader shot=28 (The same as above)
New Score=50H (The lower nibble is in the decimal range and so DAA does nothing).
Decimal 28 + Decimal 28 does not equal Decimal 50. The final value is 6 out.

Because of the carry between the low nibble and the high nibble during the ADD opcode that DAA isn't aware of, 6 hasn't been added to the lower nibble to skip over the a, b, c, d, e and f hexadecimal values in the range, and therefore the final score is 6 values too low.

DAA needs a "trigger" to tell it to add 6 to the lower nibble if this happens, and that trigger is the Auxiliary Carry Flag.



3. The Auxiliary Carry Flag.

The Auxiliary Carry Flag is similar to the Carry Flag in that it indicates where a carry has occurred, but it indicates a carry from the low nibble to the high nibble of the Accumulator.

Its pretty easy to test this, especially when carrying out an ADD opcode.

Before you add the value to the contents of the Accumulator, add the low nibble of the value to be added to the low nibble of the Accumulator, and if the value is greater than 15D set the Aux. Carry Flag to 1, else set it to 0. Its pretty much that simple.

Then the ADD can be carried out and after this, the Carry flag can be set appropriately.

It is critical that there is no action that will reset either the Carry bit or the Aux. carry bit between it being set by the preceding arithmetic opcode, and it being used within the DAA opcode. The DAA opcode should follow on directly after the Arithmetic opcode, and there should be nothing in your code for the DAA opcode that can reset the Aux. Carry flag before the Accumulator low nibble has been checked or reset the Carry Flag before the High nibble is checked.

Pseudocode for ADD

if Accumulator low nibble + value to be added low nibble > 15 then
    Aux. Carry flag=1
    else
    
Aux. Carry flag=0
end if

Accumulator=Accumulator + value to be added

if Accumulator>255D then
    Accumulator=Accumulator-256D
    Carry flag=1
    else
    Carry flag=0
end if

Zero Flag check
Parity Flag Check
Sign Flag Check



4. Advanced DAA and Aux. Carry Flag.

And so with everything now in place, we can revise our Pseudo code for the DAA opcode, assuming that we have just executed an ADD opcode (All numeric are in decimal):

'Process Accumulator Low Nibble
If Accumulator low nibble>9 or Aux. Carry flag=1 then
    Accumulator low nibble=Accumulator_low_nibble+6
    if Accumulator low nibble>15 then
        Accumulator low nibble=Accumulator_low_nibble-16
        Accumulator high nibble=Accumulator_high_nibble+1
        if Accumulator high nibble>15 then
            Accumulator high nibble=Accumulator high nibble-16
            Carry Flag=1
            ' NOTE - At this point you DO NOT
 DO "Else Carry Flag=0"
            ' There should be no mechanism that can reset Carry flag between being set in the ADD opcode
            ' and being used in the processing of the high nibble in the DAA opcode.

        end if
    end if
end if

'Process Accumulator High Nibble
If Accumulator high nibble>9 or Carry flag=1 then
    Accumulator high nibble=Accumulator_high_nibble+6
    if Accumulator high nibble>15 then
        Accumulator high nibble=Accumulator_high_nibble-16
        Carry Flag=1
        else
        Carry Flag=0
    end if
end if

This process is obviously long winded and functions / binary notation etc would be used in reality, however that is the basic requirement.

And that is how to emulate the DAA Opcode and Auxiliary Carry flag.