Friday, 7 October 2016

Part 9: Running a Test Suite

Foreword

In the previous post we have added the remaining 6502 instructions to our emulator.

In this post we will be putting our emulator through its paces by means of a Test Suite. We will be using the Klaus Test Suite for this purpose.

During my C64 Android Emulator series I have been copying and pasting most of the source code from my JavaScript emulator series, so one can expect that one will get more or less the same bugs when running the Test Suite as in my JavaScript series.For this reason, I will not be focusing so much on how to debug the Android emulator in this post.

What I will be focusing on in this post, however, is how to extend our Android Emulator to provide the necessary debugging functionality.

A quick Introduction to the Klaus Test Suite

Before we jump into coding I quickly want to give a quick introduction to the Klaus Test Suite.

The Klaus Test Suite is a very Comprehensive Test Suite that you can use to test any 6502 implementation.

This Test Suite is available on GitHub: https://github.com/Klaus2m5/6502_65C02_functional_tests

The Test Suite is provided in both 6502 assembly source or binary form.

Personally I prefer using the binary form since it is less painful to use.

The binary is a 64kilobyte binary file that you can just assign to a memory array.

When running the Test Suite, execution should start at memory address 0x400.

When a particular Test Suite fails, the emulator been tested will enter  an endless loop at a particular memory location. So, checking at which memory location the emulator is at an endless loop, will be an indication of which test failed.

If all tests succeeded, the emulator will running in an endless loop at address 0x3399.

GUI changes

To extend the debug functionality of our emulator we will adding some extra controls to the GUI of our emulator. The picture below show how the new GUI will look like:


I have marked in red the new controls. Let us quickly explain them:
  • A run button for continuous execution.
  • A Stop button pause continuous execution any point in time. You can then view the state of the emulator the moment execution was paused. You can then single step, if desired, or resume continuous execution by hitting RUN again.
  • Break edit box: You can specify an address (in hex) at which your emulator should pause continuous execution when reached.
To add these controls, you just need to add the following lines at the bottom of content_main.xml:

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="RUN"
            android:id="@+id/button3"
            android:layout_row="6"
            android:layout_column="0"
            android:onClick="onRunClick" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="STOP"
            android:id="@+id/button4"
            android:layout_row="6"
            android:layout_column="1"
            android:onClick="onStopClick" />

        <EditText
            android:id="@+id/breakAddress"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:ellipsize="start"

            android:hint="Break"
            android:inputType="text"
            android:background="@drawable/edittextstyle"
            android:layout_row="7"
            android:layout_column="0"/>

 

The highlighted onClick events (onRunClick and onStopClick), will be implemented in subsequent sections.

Continuous execution

Up to this point in time, our emulator was only able to manual step one instruction at a time.

The time has come that we implement automatic program execution without involving you to hit step for each and every instruction.

The simplest way for this would be to just call step() from within an endless loop. This would, however, make your front end unresponsive and you will soon get a popup message confirmation this situation.

A solution to this problem would be to break down the instructions to be executed in small batches and running them at regular time intervals. When emulating a C64 having a clock speed of 1Mhz and displaying 50 frames per second, assuming a PAL model, you would run batches of 20000 cycles, executing 50 times per second.

Within Android scheduling of running batches in regular time intervals can be achieved with timers. The following changes need to be done within the class MainActivity:
...
    Timer timer;
    TimerTask timerTask;
...
    public void onRunClick(View v) {
        timer = new Timer();

        timerTask = new TimerTask() {
            @Override
            public void run() {
              runBatch();
            }
        };

        timer.schedule(timerTask, 20, 20);
    }


The onRunClick method will be invoked when tapping the RUN button. Within this method we set up the timer so that a task gets run every 20 milliseconds, that is 50 times per second.

In above mentioned code, the instantiation of a TimerTask instance might look a bit confusing. In the Java world, the term for what is going on here is anonymous classes.

When an instance of TimerTask gets created via the code above, a new class gets created extending TimerTask and overriding the Run method. This new class is a called an anonymous class because the new class isn't given a name. The rest of the code doesn't care of the fact that the new doesn't have a name. All that matters is that the timerTask variable now points to an instance that has a run method that you can call.

So, in effect every 20 milliseconds a method runBatch() will be called. We haven't defined runBatch() yet, so let us do that quickly.

As mentioned earlier, runBatch() is supposed to run 20000 cycles worth of instructions, so it is worthwhile to run this method within native code for minimum overhead.

So, firstly we need to create a native stub for runBatch() within MainActivity:

public native void runBatch();

Before we implement the runBatch method within cpu.c, we need to implement a mechanism for keeping track of the number of cycles that has passed. So, we need to modify cpu.c as follows:

...
    int remainingCycles;
...
  int step() {
      int opcode = memory_read(pc);
      remainingCycles -= instructionCycles[opcode];
...
  }

...

Now we can implement the runBatch method:

void runBatch() {
  remainingCycles = 20000;
  while (remainingCycles > 0) {
    step();
  }
}

void Java_com_johan_emulator_MainActivity_runBatch(JNIEnv* pEnv, jobject pObj) {
  runBatch();
}

Pausing Code Execution

It would also be nice to pause execution at any point in time and inspect the current state of the emulator.

To pause execution, one needs to call the cancel() method on the timer object to stop the timer from scheduling any future runs of runBatch()

Where do you need to call cancel()? The first instinct would be to call it within the onStopClick event handler.

There is, however, an issue calling cancel() within onStopClick: onStopClick and a timerTask runs in two different threads. This may cause potential threading issues when you immediately want to read the state of the emulator for display purposes, after calling cancel on the timer object. The state that you might get back in such a situation might not truly reflect the real emulator state, since the timerTask might still be running a half completed batch.

A way to solve this issue, would be to introduce a global variable and do the cancelling of the timerTask within the run method of the timer Task.

At first everything will sound a bit confusing, but it will become clear in a moment.

First we introduce a global variable called running within MainActivity:

 private boolean running = false;

Next, we alter the existing onRunClick method and add a new method onStopClick:

    public void onRunClick(View v) {
        running = true;
        timer = new Timer();

        timerTask = new TimerTask() {
            @Override
            public void run() {
              runBatch();
              if (!running) {
                handler.post(new Runnable() {
                      @Override
                      public void run() {
                          timer.cancel();
                          refreshControls();
                      }
                  });      
               }
        };

        timer.schedule(timerTask, 20, 20);
    }

    public void onStopClick(View v) {
        running = false;
    }


As you can see the variable running gets set to true when the Run Button is click and gets set to false when the Stop Button gets clicked.

The state of the run variable gets checked just after the runBatch method was called.

You will an interesting piece of code getting executed when the running variable is set to false. First of all, handler is also a global variable, so let us quickly define it within MainActivity:

final Handler handler = new Handler();

What does handler.post do? In effect this method call adds a piece of code to be executed to the  event queue of the Main Thread, which is the Thread of the GUI. What we want to achieve if running is false, is to stop the timer and to refresh the controls showing the state of the emulator when it stopped.

However, Android is a funny chap. Other threads is not allowed to make any changes to the GUI. We get around this limitation by posting an instance of a Runnable object to the event queue of the Main Thread.

All events, whether it is swiping or tapping on the screen gets stored as an event on above mentioned queue. The GUI thread process these events one by one. Our Runnable object can also be viewed as an event to be processed by the GUI thread.

Opcode not supported Popups

When we ran the Klaus Test suite on my JavaScript Emulator, one type issue that serviced a number of times was opcodes that we forgot to implement. Luckily we could catch these missing op codes by means of a default case selector within the opcode switch statement in the step method. Within the default case selector we just added an alert() that would popup a message informing the user of the opcode that wasn't implemented.

A similar pop up mechanism would be nice to have within our Android C64 emulator. It should be noted, however, that within our Android Emulator the step() method lives within native C code, which is shielded a bit away from the Android Popups.

Therefore, to implement the missing opcode popups in our Android Emulator, we need to depend on result codes bubbling up all the to our Java code.

Let us start to implement this functionality by first tackling cpu.c:

  int step() {
      int result = 0;
      int opcode = memory_read(pc);

      switch (opcode)
      {
...
        default:
          result = (opcode << 16) | pc;
        break;
...
      }

      return result;
    }

int runBatch() {
  remainingCycles = 20000;
  int lastResult = 0;
  while ((remainingCycles > 0) && (lastResult == 0)) {
    lastResult = step();
    if (lastResult != 0)
      break;
  }

  return lastResult;
}

jint Java_com_johan_emulator_MainActivity_runBatch(JNIEnv* pEnv, jobject pObj) {
  return runBatch();
}

For starters, the signatures of a couple of methods was changed to return a result code.

You might find the build up of the result code interesting. The resulting result code can be viewed as three bytes with the first byte been the offending opcode, and the last two bytes the address where it happened.

Lets move on to the MainActivity class. We need to make the following changes to it:

    public native int runBatch(int address);

    public void onRunClick(View v) {
        running = true;
        timer = new Timer();

        timerTask = new TimerTask() {
            @Override
            public void run() {

              final int result = runBatch();

              if (result > 0) {
                  handler.post(new Runnable() {
                      @Override
                      public void run() {
                          timer.cancel();
                          doAlert(result);
                      }
                  });
              } 
...
        };

        timer.schedule(timerTask, 20, 20);
    }

    public void doAlert(int result) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder
                .setTitle("Error")
                .setMessage(result+"")
                .setPositiveButton("Yes",null)
                .show();
    }

If result code is bigger than zero, a Runnable class gets added to event queue of the main thread, stopping the timer and popping up a message box. Interesting to note how a popup gets created in Android.

At this point, it might be interesting to see how the popup looks like in practice. If you use the instruction implementation of the previous post, you will get a popup right at the start:


To get meaning from the error, you first need to convert the number to hexadecimal, which is D80401. This means the unimplemented opcode is D8, which is CLD, and it happened more or less at address 401.  The address will usually point to the next instruction. Of course results may differ if it is an undocumented instruction.

Adding Breakpoint

Time to implement the breakpoint functionality.

To implement the breakpoint functionality, we start off adding an extra paramater to the runBatch() method within cpu.c. This will change our cpu.c as follows:

int runBatch(int address) {
  remainingCycles = 20000;
  int lastResult = 0;
  while ((remainingCycles > 0) && (lastResult == 0)) {
    lastResult = step();
    if (lastResult != 0)
      break;
    if ((address > 0) && (pc == address)) {
      lastResult = -1;
      break;
    }  }

  return lastResult;
}

jint Java_com_johan_emulator_MainActivity_runBatch(JNIEnv* pEnv, jobject pObj, jint address) {
  return runBatch(address);
}


You will notice that within the while loop of runBatch, we keep checking whether the target address has been reached, and if so, we break out of the while loop.

If  the applicable breakpoint address has been reached, a -1 gets returned as a result code.

There is also a number of things we need to do within the class MainActivity for the breakpoint functionality, so let tackle that quickly.

First, we need to write a method to convert the string contents of the breakpoint edit box, and convert it to an integer:

    private int getBreakAddress () {
        EditText editText = (EditText) findViewById(R.id.breakAddress);
        String editAddress = "0"+editText.getText().toString();

        int intaddress = Integer.parseInt(editAddress,16);
        return intaddress;
    }

There is also a couple of changes we need to make to onRunClick:

    public void onRunClick(View v) {
        running = true;
        timer = new Timer();
        breakAddress = getBreakAddress();

        timerTask = new TimerTask() {
            @Override
            public void run() {
              //System.out.println("In Beginning: " + System.currentTimeMillis());
              final int result = runBatch(breakAddress);
              //System.out.println("In End: " + System.currentTimeMillis());
              if (result > 0) {
                  handler.post(new Runnable() {
                      @Override
                      public void run() {
                          timer.cancel();
                          doAlert(result);
                      }
                  });
              } else if (!running | (result < 0)) {
                  handler.post(new Runnable() {
                      @Override
                      public void run() {
                          timer.cancel();
                          refreshControls();
                      }
                  });

              }
            }
        };

        timer.schedule(timerTask, 20, 20);
    }

So, in effect the same process gets followed when a breakpoint fired as when you tap the STOP button.

In Summary

In this post we did the necessary preparation for running the Klaus Test Suite on our Android emulator. The preparation included implementing continuous running running together with some debugging functionality like breakpoints, Stopping and unsupported opcode alerts.

In this post I haven't focussed so much on how to debug our Emulator since the nature of the bugs was more or less the same as in my JavaScript emulator.

However, I managed to fix all bugs so that the Klaus Test Suite runs to completion. As with the other posts I have provided the source for download via GitHub.

In the next post we will be trying to boot the C64 with all its assosiated code ROMS: Kernal and Basic.

One of the members at 6502.org, @WhiteFlame, gave me a very interesting suggestion where one could make use of functioning inlining to try and compact the code for the instruction decoding switch statement. So, I am going to play around with the suggestion and give some feedback in the next post.

Till next time!

No comments:

Post a Comment