Friday 13 May 2016

Part 8: Running a Test Suite

Foreword

In the previous section we implemented the remaining instructions of our CPU emulator.

In this section we will run our emulator against the Klaus Test Suite.

But, before we start, we need to extend the debugging functionality of our emulator a bit...

Extending Debug functionality

Currently the only way to run our emulator is to single step it. A very painful way to run the Klaus Test Suite!

So, it is obvious that we need to add a continuous run feature in our emulator. At the same time, we will also need a feature that will stop continuous execution at any time, allowing the user to single step again from that point onwards.

The stop functionality is important in running the Klaus Test Suite, as you will have no other way to evaluate at which point the tests failed.

Here is a list of two other nice debug features we will implement in this post:

  • Breakpoints: For a first round we will give the option to add a single breakpoint
  • Memory Dump: We will modify the exiting functionality so that you can specify a different area in memory to view

Run/Stop Functionality


Implementation time! Lets start by adding a Run and a Stop button next to the step button in index.html:

<button id="btnStep" onclick="step()">Step</button>
<button id="btnRun" onclick="startEm()">Run</button>
<button id="btnStop" onclick="stopEm()">Stop</button>


Before we head off and wrote code for these two methods, it is important to keep the threading model of JavaScript/Browser in mind.

The most important aspect of the threading model is that each page open in a browser can only have a single thread at any time. With this thread the page in the browser must do all its work: From rendering the content of the page to the screen, checking for button clicks, executing JavaScript code and so on.

This means that when a page is busy executing JavaScript, that page will appear unresponsive.

This single threaded nature has very important implications for our Run/Stop functionality. When our emulator is busy executing instructions continuously, it won't be able to accept the Stop command!

How do you get around this limitation? Well, JavaScript provides you with a method called setInterval(). To understand this method let's look at an example snippet of code:

myTimer = setInterval(runBatch, 200);

This method call basically says: Run the method runBatch every 200 milliseconds. In the runBatch method you will run a small batch of assembly instructions (e.g. calling step() a couple of times) and then exit. RunBatch will again be called when 200 milliseconds lapsed and the whole process will be repeated.

This whole process will continue running, until at some point in time you call clearInterval(). More on this method in a moment.

So, the question is: How many 6502 instructions do you execute in a batch? For this Blog Post, it doesn’t really matter. As long as his emulator web page is kind of responsive.

When I played with this method I started off with a batch of 100 instructions. After a while however, I found that the Test Suite takes ages to complete. So my patience ran out and I ended off with a batch of 100000 :-)

Obviously, in later blog posts where we will add a screen as output, we will want to get everything working at the expected speed of a C64. In this case you will need to tune the batch size and also the interval.

Back to our snippet of code. As you see the returned value of SetInterval() gets assigned to a variable. So the question is: What does this variable contain? It contains a instance of the timer you have just created.

What can you do with this timer instance? This is actually where clearInterval() comes in, as I mentioned earlier on. When you call clearInterval with the Timer Instance as a parameter, you actually stops continuous execution of the runBatch method.

With all this information, we have enough to implement the Run/Stop functionality for the emulator:

      function startEm() {
        document.getElementById("btnStep").disabled = true;
        document.getElementById("btnRun").disabled = true;
        document.getElementById("btnStop").disabled = false;
        myTimer = setInterval(runBatch, 200);
      }

      function stopEm() {
        clearInterval(mytimer);
        displayEmuState();
        document.getElementById("btnStep").disabled = false;
        document.getElementById("btnRun").disabled = false;
        document.getElementById("btnStop").disabled = true;
      }


You will notice a new method displayEmuState().This is basically the display code in index.html's step method that I moved into a method of its own. I was busy with a bit of code cleanup for index.html. You can have a look at the GitHub zip file for this post to get an idea of what I was up to with index.html.

myTimer you will need to declare as a global variable:

      var mymem = new memory();
      var mycpu = new cpu(mymem);
      var mytimer;


Finally, lets implement runBatch:

      function runBatch() {
        for (i=0; i < 100;  i++) { 
          mycpu.step();
        }
      }


Very minimalistic.

Breakpoints

Lets implement the single breakpoint functionality as I mentioned earlier.

At the bottom of the page, lets add a input field allowing the user to enter a address at which the emulator should break:

<button id="btnStep" onclick="step()">Step</button>
<button id="btnRun" onclick="startEm()">Run</button>
<button id="btnStop" onclick="stopEm()">Stop</button>
<br/>
Break at: <input type="text" id="breakpoint">


The runBatch method should check the program counter after each call to Cpu.step() to determine whether this address was reached.

There is two foreseeable issues that we need to resolve first before implementing the checking in runBatch.

Firstly, as our Cpu class is currently written, outsiders don't have access to the program counter. So, lets implement a getter in the CPU class:

    this.getPc = function () {
      return pc;
    }


Lets discuss the second problem. The value of the text input field for the breakpoint address is a String. This means that each time the runBatch method need to first convert the String to an integer, and then do the comparison with the program counter. Sounds like a bit of  waste!

We can do better. When clicking run the startEm() method can actually do this conversion once and assign it to a global variable.  The runBatch method can then use the value of this global variable each time.

Here is how the global variable and the startEm() method will look like:

      var breakpoint = 0;
...

      function startEm() {
        document.getElementById("btnStep").disabled = true;
        document.getElementById("btnRun").disabled = true;
        document.getElementById("btnStop").disabled = false;
        var myBreak = document.getElementById("breakpoint");
        breakpoint = parseInt(myBreak.value, 16);        myTimer = setInterval(runBatch, 200);
      }


We are now ready to implement to breakpoint checking in runBatch:


      function runBatch() {
        for (i=0; i < 100;  i++) { 
          mycpu.step();
          if (mycpu.getPc() == breakpoint) {
            stopEm();
            break;
          }
        }
      }


So, when breakpoint is reached, stopEm() is called which clears the interval timer and update the state display of the emulator. The break exits the loop so that no further instructions is executed for the batch.

Ok, this is about it for implementing breakpoint functionality. Or, is it really? When I used this breakpoint functionality to figure out why some test cases fails on emulator, I stumbled across a weird anomaly. This anomaly entailed that when the breakpoint point fired, clicking step wouldn't take you to the next instruction, but to the tenth or so instruction where you stopped.

It was as if there was a pending timer event that was still queued in the JavaScript event queue.

Googling didn't reveal much info on this issue. There was actually one useful piece of info suggesting the use of a global variable like running. When you click stop, you need to set this variable to false and in your runBatch method you actually check this variable. If runBatch sees running is false, it just exits. Here is how the changes will look like:

      var running = false;

...

      function runBatch() {
        if (!running)
          return;
        for (i=0; i < 100000;  i++) { 
          mycpu.step();
          if (mycpu.getPc() == breakpoint) {
            stopEm();
            break;
          }
        }
      }

      function startEm() {
        document.getElementById("btnStep").disabled = true;
        document.getElementById("btnRun").disabled = true;
        document.getElementById("btnStop").disabled = false;
        var myBreak = document.getElementById("breakpoint");
        breakpoint = parseInt(myBreak.value, 16);
        running = true;
        myTimer = setInterval(runBatch, 200);
      }

      function stopEm() {
        running = false;
        clearInterval(mytimer);
        displayEmuState();

        document.getElementById("btnStep").disabled = false;
        document.getElementById("btnRun").disabled = false;
        document.getElementById("btnStop").disabled = true;

      }


Flexible Memory dump

Currently the emulator's memory dump functionality is limited to viewing memory from location 0. In this section we will add an input text field so that the user can choose to view a different area in memory.

Lets start off by adding the input field and button to index.html:

<textarea id="memory" name="mem" rows="15" cols="60"></textarea>
From Location:
<input type="text" id="frommem">
<button onclick="showMem()">Refresh Dump</button><br/>


The showMem() is one of the methods that I wrote while doing code cleanup index.html. This method also needs to be adjusted to look at the frommem input field for the start address:

      function showMem() {
        var m = document.getElementById("memory");
        var location = document.getElementById("frommem");
        locationInt = parseInt(location.value, 16);        tempmemstr = ""
        for (i = locationInt; i < (160 + locationInt); i++) {
          if ((i % 16) == 0) {
            labelstr = "";
            labelstr = labelstr + "0000" + i.toString(16);
            labelstr = labelstr.slice(-4);

            tempmemstr = tempmemstr + "\n" + labelstr;
          }
          currentByte = "00" + mymem.readMem(i).toString(16);        
          currentByte = currentByte.slice(-2);
          tempmemstr = tempmemstr + " " + currentByte;
        }
        m.value = tempmemstr;

      }


We are now ready for preparing to run the Klaus Test Suite.

Preparing to run Test Suite

Firstly we need to get hold of the Klaus Test Suite. It is available via GitHub:

https://github.com/Klaus2m5/6502_65C02_functional_tests

The assembly files (e.g. the ones with the a65) needs to be compiled with the a65 assembler.

For convenience, compiled binary files is also provided in the bin_files folder. These binaries are memory images that you copy straight to your 64KB emulated memory. I think I am going to take the convenience route :-)

You will also notice that together with compiled binary files in the bin_files folder, there are .lst files. The a65 assembler generates these listings when it is assembling the source. The beauty of these listing files is that it keeps all the comments together with the memory address where each instruction is in memory.

This info in the listing files comes in very handy when a test fails at a particular address, so you can look it up in the listing file and almost immediately know what the test that fail is about. Sadly, I only discovered this after I managed to run the Test Suite successfully :-(


From the assembly listing it stated that you need to start executing at memory location $400. So we will assign this address to the program counter when the CPU class is created.

As in the previous posts will add the Test Suite binary as an array in the Memory class. To assist in this task, I wrote a quick Java program taking the binary and spitting out the contents as a JavaScript array. In the GitHub zipfile for this post the Memory class will be populated with the Test Suite program.

While running the Test suite I also found it handy to have a disassembled listing of the binary at hand. This is nice for quick reference. There is a nice online tool that can assist you in the disassembly of the binary at the following link:

http://e-tradition.net/bytes/6502/disassembler.html

Just open the binary with a hex editor and copy the hex values from $400 till about $370D. You will see the succeeding memory locations is filled with Hex FF's.

Now paste the copied hex values in the left panel (called code) of the online disassembler, enter 400 as opcode start address and click disassemble. Your screen will look similar to the following:


We now have all the necessary resources to start running the Test Suite.

Running the Test Suite

With everything in place, lets hit run to kick off the Test Suite.

The start off not so well. Right at the beginning I get a pop-up: Op code 216 not implemented. PC = 401

216 is D8 in hex and this is the CLD (clear decimal flag) the instruction.

OK, I must admit I didn't consider BCD at all in the emulator up now. To get pass this error let implement the decimal flag private variable and implement the CLD and SED instructions. For now I will not worry about BCD addition or subtraction:

  var decimalflag = 0;
...
/*CLD  Clear Decimal Mode

     0 -> D                           N Z C I D V
                                      - - - - 0 -

     addressing    assembler    opc  bytes  cyles
     --------------------------------------------
     implied       CLD           D8    1     2 */

       case 0xD8:
         decimalflag = 0;
       break;

/*SED  Set Decimal Flag

     1 -> D                           N Z C I D V
                                      - - - - 1 - 

     addressing    assembler    opc  bytes  cyles
     --------------------------------------------
     implied       SED           F8    1     2  */

      case 0xF8:
        decimalflag = 1;
      break;



With this added we run the test again. This time no alert pops up. I let it run for three minutes and then hit stop. At this point, this is how my browser window looks like:



Clicking step from this point onwards keeps jumping back to $5f0. A test has therefore failed. Lets investigate by looking at the surrounding code:

05E7   08         PHP
05E8   BA         TSX
05E9   E0 FE      CPX #$FE
05EB   D0 FE      BNE $05EB
05ED   68         PLA
05EE   C9 FF      CMP #$FF
05F0   D0 FE      BNE $05F0
05F2   BA         TSX
05F3   E0 FF      CPX #$FF
05F5   D0 FE      BNE $05F5


What is happening here? First the status register is pushed unto the stack, and then popped into the accumulator. The accumulator is then compared with $FF

So the applicable test tests that all flags of the status register are set.

From the above screen we see that the accumulator is $C3. This translates to 1100 0011 binary and means that in our case bits 2 - 5 our status register was cleared. These bits represents the following flags:
  • bit 5: Ignored
  • bit 4: Break
  • bit 3: Decimal
  • bit 2: Interrupt
Lets see if we can get some more information about these flags.

From a couple of resources I found that bit 5 is always hardcoded to one.

Bit 4 (the break flag) is used during a interrupt do determine whether the interrupt was caused by an IRQ/NMI or whether was due to executing the BRK instruction. If the interrupt was due to a IRQ/NMI the flag will be cleared. For all other instances the BRK flag will be set. For normal operations it is therefore save to assume a BRK flag value of 1.

Bit 3 (Decimal). Ok, the previous fix we did implement SED and CLD, but we never adjusted the getStatusFlagsAsByte and setStatusFlagsAsByte method!

Bit 2. Interrupt flag. First of all we didn't implement the SEI and CLI instruction. But, our emulator didn't complain about missing these instructions. So it must be something todo with the initial state of the interrupt flag. Googling around it looks like the Interrupt disable flag is set on a fresh 6502 start.

With all this information, we make the following changes in the CPU class:

   var carryflag =0;
   var overflowflag =0; 
   var decimalflag = 0;
   var interruptflag = 1;
   var breakflag = 1;

...

     function getStatusFlagsAsByte() {

      var result = (negativeflag << 7) | (overflowflag << 6) | (1 << 5) | (breakflag << 4) | (decimalflag << 3) | (interruptflag << 2) | (zeroflag << 1) |
         (carryflag);
       return result;
     }

    function setStatusFlagsAsByte(value) {
      negativeflag = (value >> 7) & 1;
      overflowflag = (value >> 6) & 1;
      decimalflag = (value >> 3) & 1;
      interruptflag = (value >> 2) & 1;
      zeroflag = (value >> 1) & 1;
      carryflag = (value) & 1;
    }



With this fixed lets run the emulator again. This time this alerts pops up: Op code 234 not implemented. PC = 870

The NOP is not implemented. So lets do that quickly:

/*NOP  No Operation
     ---                              N Z C I D V
                                      - - - - - -
     addressing    assembler    opc  bytes  cyles
     --------------------------------------------
     implied       NOP           EA    1     2 */

     case 0xEA:
     break;

At the next run, there is a complained about the BRK instruction not implemented. This instruction trigger an interrupt, so lets implement BRK and RTI together:

/*BRK  Force Break

     interrupt,                       N Z C I D V
     push PC+2, push SR               - - - 1 - -

     addressing    assembler    opc  bytes  cyles
     --------------------------------------------
     implied       BRK           00    1     7 */
       
      case 0x00:
        interruptflag = 1;
        var tempVal = pc + 1;
        Push(tempVal >> 8);
        Push(tempVal & 0xff);
        Push(getStatusFlagsAsByte());
        tempVal = localMem.readMem(0xffff) * 256;
        tempVal = tempVal + localMem.readMem(0xfffe);
        pc = tempVal;
      break;

/*RTI  Return from Interrupt

     pull SR, pull PC                 N Z C I D V
                                      from stack

     addressing    assembler    opc  bytes  cyles
     --------------------------------------------
     implied       RTI           40    1     6 */
      
      case 0x40: 
        setStatusFlagsAsByte(Pop());
        var tempVal = Pop();
        tempVal = (Pop() << 8) + tempVal;
        pc = tempVal;
      break;

Next, the emulator gets stuck at $36E5. Lets look again at the surrounding code:

36C9   88         DEY
36CA   88         DEY
36CB   08         PHP
36CC   88         DEY
36CD   88         DEY
36CE   88         DEY
36CF   C9 42      CMP #$42
36D1   D0 FE      BNE $36D1
36D3   E0 52      CPX #$52
36D5   D0 FE      BNE $36D5
36D7   C0 48      CPY #$48
36D9   D0 FE      BNE $36D9
36DB   85 0A      STA $0A
36DD   86 0B      STX $0B
36DF   BA         TSX
36E0   BD 02 01   LDA $0102,X
36E3   C9 30      CMP #$30
36E5   D0 FE      BNE $36E5

Looking further down in the code, I see the code actually ends off with a RTI, so this code is part of a interrupt service routine.

Looking back in the code I copied, I see the stack pointer gets copied to the X register and then the value of memory location $102,X is compared with value $30.

From the debug window I figured out the stackpointer and register X has value $FB. So the effective meomory location is $01FD. Since this is inside an interrupt service routine, we know pushed values on the stack should be the address and the value of the status register. So looking at everything I feel comfortable that the value we compare is a status register. The value at $01FD is $34 and the Test Suite expects $30.

The two values differ by a single bit; Bit 2, which is the interrupt disable register. So, my emulator set the interrupt disable flag and the Test Suite expects this flag to be cleared. After some reasoning I established the following:


  • Calling BRK should set the I flag, but
  • this doesn't necessarily  means that the status register pushed onto the stack will have this bit set.
Keeping this in mind, I actually discovered that I set the interrupt flag to early in the case statement of the BRK instruction. The setting should actually happen after the status byte is pushed onto the stack:


      case 0x00:
        interruptflag = 1;
        var tempVal = pc + 1;
        Push(tempVal >> 8);
        Push(tempVal & 0xff);
        Push(getStatusFlagsAsByte());
        interruptflag = 1;
tempVal = localMem.readMem(0xffff) * 256; tempVal = tempVal + localMem.readMem(0xfffe); pc = tempVal; break;

Next, issue: SEI and CLI not implemented. So lets implement them:

/*CLI  Clear Interrupt Disable Bit

     0 -> I                           N Z C I D V
                                      - - - 0 - -

     addressing    assembler    opc  bytes  cyles
     --------------------------------------------
     implied       CLI           58    1     2 */

      case 0x58: 
        interruptflag = 0;        
      break;

/*SEI  Set Interrupt Disable Status

     1 -> I                           N Z C I D V
                                      - - - 1 - -

     addressing    assembler    opc  bytes  cyles
     --------------------------------------------
     implied       SEI           78    1     2 */

      case 0x78: 
        interruptflag = 1;                
      break;


Next failing point is at address $d23. Let look again at the surrounding code:

0D13   8D 00 02   STA $0200
0D16   A2 01      LDX #$01
0D18   A9 FF      LDA #$FF
0D1A   48         PHA
0D1B   28         PLP
0D1C   9A         TXS
0D1D   08         PHP
0D1E   AD 01 01   LDA $0101
0D21   C9 FF      CMP #$FF
0D23   D0 FE      BNE $0D23

While debugging through this code, it seemed that the TXS effected some flags. Something just didn't seem right. I had a look again at the datasheet from masswerk and they actually stated that TXS should effect some flags:

 X to Stack Register

     X -> SP                          N Z C I D V
                                      + + - - - -

     addressing    assembler    opc  bytes  cyles
     --------------------------------------------
     implied       TXS           9A    1     2

But still, something didn't seem right. I consulted other sources and Bingo! Flags shouldn't be effected for this instruction. I commented out the setting of flags for this instruction and restarted the Test Suite.

Next, the emulator got stuck at address $22B3. Here is the surrounding code:

22A5   28         PLP
22A6   4A         LSR A
22A7   08         PHP
22A8   DD 19 02   CMP $0219,X
22AB   D0 FE      BNE $22AB
22AD   68         PLA
22AE   49 7C      EOR #$7C
22B0   DD 29 02   CMP $0229,X
22B3   D0 FE      BNE $22B3

From this code this test entails doing a LSR and checking effected flags. Id did some debugging and found EOR yields a result of 82. This gets compared to the effective address 22C which has a value of 02. It appears that after the LSR operation, the negative flag is set. This is strange because a LSR operation should always insert a zero in bit 7, yielding a positive number.

I looked carefully at the definition on the masswerk datasheet, and here we go: It states that the negative flag is unaffected. I again consulted other datasheets and all says that the Negative flag is set to 0 as part of the LSR operation.

I adjusted these Instructions accordingly:

      case 0x4A: 
          carryflag = ((acc & 0x1) != 0) ? 1 : 0;
          acc = acc >> 1;
          acc = acc & 0xff;
          zeroflag = (acc == 0) ? 1 : 0;
          negativeflag = 0;
        break;
      case 0x46: 
      case 0x56: 
      case 0x4E: 
      case 0x5E: 
          var tempVal = localMem.readMem(effectiveAdrress);
          carryflag = ((tempVal & 0x1) != 0) ? 1 : 0;
          tempVal = tempVal >> 1;
          tempVal = tempVal & 0xff;
          zeroflag = (tempVal == 0) ? 1 : 0;
          localMem.writeMem(effectiveAdrress, tempVal);
          negativeflag = 0;
        break;


I got two last issues I had to fix in order for all Test cases to succeed. I will try to give a summary on these two issues.

The first issue was that for SBC I applied two's complement for the operand instead of one's complement, so I had a off by one issue with SBC.

Finally I had to implement BCD mode addition and subtraction. For this I made use of code from my Java emulator and adopted it to JavaScript.

In Summary

In this blog we started off by extending the Debugging functionality. We then ran the Klaus Test Suite.

As expected, there was a couple of issues that came out. In the end however, we managed to run all tests.

In my next Blog we will be booting the C64 system with its ROM's.

Till next time!

No comments:

Post a Comment