Saturday 21 May 2016

Part 9: Booting the C64 System

Foreword

In the previous blog we successfully ran the Klaus Test Suite.

In this Blog we are going to boot the C64 system with all its code ROMS.

Loading the ROMs

The Commodore 64 has three ROMs:
  • Kernal
  • Basic
  • Character ROM
In order to boot the C64 system on our emulator, one of the first things you would do is to load at least two of the ROMS (e.g. Kernal and Basic) each into a Uint8array of its own.

But, to populate each Uint8array by means of a zillion line JavaScript array definition, as I did in my previous posts, is starting to sound very clunky :-)

Clearly, there must be a way JavaScript can help us to populate Uint8array's by given it the paths to the Binary ROM images. Indeed there is. There is a JavaScript class called XMLHttpRequest that can do this job for us.

XMLHttpRequest has a downfall, however. It is doesn't work at all when you browse web pages directly from your file system (like opening them up via Windows explorer). This pose a issue for us since up to now we have tested our emulator only via our local file systems.

The obvious solution to this issue is to always deploy to a website and then test. This is not very feasible if you just want to quickly test something the emulator.

A better option would be to install a webserver on your local machine.

Any webserver would basically do, but I found that Tomcat works the best for me. You only need Java installed on your PC and is just a case of unzipping the zip file and starting it up.

In the next section I am going to give a quick crash course on installing Tomcat on your machine.

Installing Tomcat

As mentioned previously, to run Tomcat you need to have Java installed on your machine. If you open a Command Prompt and typing java -version gives you back a version number, you are good to go!

You can get Tomcat via the following link:

https://tomcat.apache.org/download-70.cgi

Download either the zip file for Windows or the .tar.gz for Linux
The contents of the two files are more less the same, but remember .tar.gz preserves file permissions saving you from fiddling with chmod in Linux.

Unzip/Untar the file to any location on your local file system. Then browse to the bin folder via a Command Prompt.

Now, to start tomcat in Linux you need to issue the command ./startup.sh If all went well you'll get a prompt stating that tomcat started successfully.

For Windows you will use the command startup.bat instead.

Now, to see if Tomcat is working properly open up a browser and type the following: localhost:8080 If the following page appears, you installed Tomcat successfully:


 To install your web app is just as easy. In the webapps folder create a folder and stuck all the emulator files (e.g. index.html, Cpu.js and Memory.js) in it as shown here:

To invoke the emulator as per above screen shot, type the following in the address line in the browser: localhost:8080/emu/index.html

We now have a working base to continue with the rest of this blog.

Loading ROMS with XMLHttpRequest

Let us start off with an example from Mozilla.org showing how to load a binary file into an array with XMLHttpRquest:

var oReq = new XMLHttpRequest();
oReq.open("GET", "/myfile.png", true);
oReq.responseType = "arraybuffer";

oReq.onload = function (oEvent) {
  var arrayBuffer = oReq.response; // Note: not oReq.responseText
  if (arrayBuffer) {
    var byteArray = new Uint8Array(arrayBuffer);
    for (var i = 0; i < byteArray.byteLength; i++) {
      // do something with each byte in the array
    }
  }
};

oReq.send(null);

Lets dissect this code. First an instance of XMLHttpRequest is created.

The open method is then invoked on the object. This basically informs the object which file needs to be fetched. The third parameter of open states whether the request should be made asynchronously or synchronously. A true means asynchronously and vice versa.

What is the difference between asynchronously and synchronously? When the request is made asynchronously the request is send to the webserver and immediately returns. So your Javascript program will continue executing even though the whole file wasn't received yet.

When a synchronous call is made, the method call will not return until the whole file is received.

In subsequent Blog posts I will be following the asynchronous way of thinking. The body that governs the JavScript standards (that is W3C) has a big drive to get rid of synchronous calls because it potentially makes web pages unresponsive. My original hopes was in synchronous calls, but after hitting a couple of brick walls, I had to adjust my thinking pattern to an asynchronous one :-(

Back to out snippet of code. How do we know that the file has finished loading? The XMLHttpRequest class defines a callback property called onload. When the file has finished loading it invokes the method you assigned to this property.

When your callback method is eventually executed, the response attribute of your Request object will contain the data. You will see in the code that responseType is set to arraybuffer. In this way you let JavaScript do the work for you and populate a Uint8Array with the data.

Let us now modify this code snippet to fit our purpose. All this code is going to end up in Memory.js

First we need to declare two Uint8Array's that will hold the data of the two ROM's:

  var mainMem = new Uint8Array(65536);
  var basicRom = new Uint8Array(8192);
  var kernalRom = new Uint8Array(8192);

Next we repeat the code snippet for each ROM:

var oReqBasic = new XMLHttpRequest();
oReqBasic.open("GET", "basic.bin", true);
oReqBasic.responseType = "arraybuffer";

oReqBasic.onload = function (oEvent) {
  var arrayBuffer = oReqBasic.response; // Note: not oReq.responseText
  if (arrayBuffer) {
    basicRom = new Uint8Array(arrayBuffer);
  }
};

oReqBasic.send(null);

//------------------------------------------------------------------------

var oReqKernal = new XMLHttpRequest();
oReqKernal.open("GET", "kernal.bin", true);
oReqKernal.responseType = "arraybuffer";

oReqKernal.onload = function (oEvent) {
  var arrayBuffer = oReqKernal.response; // Note: not oReq.responseText
  if (arrayBuffer) {
    kernalRom = new Uint8Array(arrayBuffer);
  }
};

oReqKernal.send(null);






This will take care of loading both ROMs in an array. However, we still need a mechanism to know when both ROMS has loaded so we can kick off the emulator.

To cater for this need we will need to create a global variable called  outstandingDownloads. The name speaks for itself. It will start off with a initial value of 2 (e.g two ROMS to download):

  var outstandingDownloads = 2;

When each download finishes it should decrement this global variable. When this global reached 0 we can call a callback method

function memory(allDownloadedCallback)

{
  var mainMem = new Uint8Array(65536);
  var outstandingDownloads = 2;
  var basicRom = new Uint8Array(8192);
  var kernalRom = new Uint8Array(8192);
  
  function downloadCompleted() {
    outstandingDownloads--;
    if (outstandingDownloads == 0)
      allDownloadedCallback();
  }
var oReqBasic = new XMLHttpRequest();
oReqBasic.open("GET", "basic.bin", true);
oReqBasic.responseType = "arraybuffer";

oReqBasic.onload = function (oEvent) {
  var arrayBuffer = oReqBasic.response; // Note: not oReq.responseText
  if (arrayBuffer) {
    basicRom = new Uint8Array(arrayBuffer);
    downloadCompleted();
  }
};

oReqBasic.send(null);

//------------------------------------------------------------------------

var oReqKernal = new XMLHttpRequest();
oReqKernal.open("GET", "kernal.bin", true);
oReqKernal.responseType = "arraybuffer";

oReqKernal.onload = function (oEvent) {
  var arrayBuffer = oReqKernal.response; // Note: not oReq.responseText
  if (arrayBuffer) {
    kernalRom = new Uint8Array(arrayBuffer);
    downloadCompleted();
  }
};

oReqKernal.send(null);


  this.readMem = function (address) {
    return mainMem[address];
  }

  this.writeMem = function (address, byteval) {
    mainMem[address] = byteval;
  }

}



So, here we first moved the oustandingDownload variable manipulation into a method of its own. The callback method is supplied as parameter when we instantiated the memory object.

As it stands, there is one remaining issue with our memory class: our ROM arrays is not mapped in memory space. So, when you call readMem it will only return information from the mainMem array.

We will need to build some if statements into the readMem method:

  this.readMem = function (address) {
    if ((address >= 0xa000) & (address <=0xbfff))
      return basicRom[address & 0x1fff];
    else if ((address >= 0xe000) & (address <=0xffff))
      return kernalRom[address & 0x1fff];
    return mainMem[address];
  }


Remember, on a C64 you get the BASIC Rom in memory range A000 - BFFF and the KERNAL ROM in memory range E000 - FFFF. For all other memory access you can just return the contents from the mainMem array.

Something else that might be funny is the anding of the address with 1FFF for both ROM's. This operation just keeps the lower 13 bits of the address, so you stay within the bounds of the ROM's address range.

Do we need to worry to modify writeMem? No, the memory model of the C64 is such that if you try to set a value at a memory location where there is ROM, it will write the value to RAM, even though you will not be able to see the written value at point in time.

This conclude the required changes to the Memory class.

The only thing left to do is to create a callback method in index.html and pass it as a parameter when creating an instance of the memory class

      var mymem = new memory(postInit);
      var mycpu = new cpu(mymem);
      var mytimer;
      var running = false;
      var breakpoint = 0;
...

     function postInit() {
     }

Booting

Before we start, lets consider a couple of point that must happen during boot:

  • When the emulator window opens all buttons should be disabled until all ROMS has loaded. This is to prevent the user from firing off the emulator during this process
  • When all ROMS has loaded we can enable all buttons again
To implement the first the first point we adjust the properties off all buttons on the html page:

<textarea id="memory" name="mem" rows="15" cols="60"></textarea>
From Location:
<input type="text" id="frommem">
<button onclick="showMem()" disabled = "true">Refresh Dump</button>
<br/>
<textarea id="diss" name="diss" rows="11" cols="60"></textarea>
<button id="btnStep" disabled = "true" onclick="step()">Step</button>
<button id="btnRun" disabled = "true" onclick="startEm()">Run</button>
<button id="btnStop" disabled = "true" onclick="stopEm()">Stop</button>
<br/>

In our callback method we can enable all buttons when all the ROMS has loaded:

     function postInit() {
        document.getElementById("btnStep").disabled = false;
        document.getElementById("btnRun").disabled = false;
        document.getElementById("btnStop").disabled = false;
     }


When a 6502 starts to execute out of RESET, one of the first things it do is to load the program counter with the value of the reset vector, which is at address FFFC and FFFD. We also need to implement this in our emulator. We do this by implementing a method called reset in the CPU that will do this for us:

    this.reset = function () {
      pc = localMem.readMem(0xfffc);
      pc = pc + localMem.readMem(0xfffd) * 256;
    }


Now, one of the extra responsibilities of our callback method is also to call this reset method:

     function postInit() {
        document.getElementById("btnStep").disabled = false;
        document.getElementById("btnRun").disabled = false;
        document.getElementById("btnStop").disabled = false;
        mycpu.reset();
     }



Our C64 system would now be able to boot if we hit the run button.

Since we currently don't have a emulated screen, how are we going to get some meaningful output? Well, we know that the screen memory (text mode) of the C64 starts at 400h at boot up. So, we can stop execution at any time and inspect this location to check if we can spot the welcome message.

Doing  the above I get the following:



There was definitely stuff happening. The screen is padded with 20h bytes. 20h is the screen code for a space. But no sign of a welcome message!

Let us continue stepping and see if we can find some clues. And, indeed we have a clue! The emulator is stuck in this loop:

ff5e LDA $d012
ff61 BNE $ff5e


The emulator is waiting for memory location D012 to change to a zero.

What is special about memory location D012? This is one of the location within the memory space of the VIC-II display chip. This particular location is the raster counter keeping count at which line position the raster beam is at any point in time on the screen. So probably this code does like waiting for he raster beam to be off screen before refreshing it.

Lets see if we can do a quick hack just to get out of this loop. In later posts we will eventually come back to implement the raster functionality properly. But, for now we just put in a hack to see how far we can get.

The hack I am thinking of is to say after every 10000th instruction, write the value 0 to location D012. Keeping writing 0 to this location after each instruction for 30 instructions. After that just write 1 to this location after each instruction:

      function runBatch() {
        if (!running)
          return;
        for (i=0; i < 100000;  i++) { 
          mycpu.step();
          if ((i % 10000) < 30) {
            mymem.writeMem(0xD012, 0);
          } else {
            mymem.writeMem(0xD012, 1);
          }
          if (mycpu.getPc() == breakpoint) {
            stopEm();
            break;
          }
        }
      }

We have more luck this time:


At the end of address line 420 you start to see none space characters. For the record, the following website will give you a map to which character each screen code maps to: http://sta.c64.org/cbm64scr.html

Lets convert the first 10 screencodes, starting from memory location 42Ch, to characters:


42C 2A *
42D 2A *
42E 2A *
42F 2A *
430 20
431 03 C
432 0F O
433 0D M
434 0D M
435 0F O


Looks more or less what we expect. What I am more interested at ths point is whether our emulator calculated the number of free bytes correctly. This is represented by the sequence of bytes at address line 480:

33 3
38 8
39 9
31 1
31 1

Ok, looks like we are more or less on track with our C64 boot!

In Summary

In this blog we managed to boot the C64 with all its code ROMS.

In the next Blog we are going to emulate the C64 screen. It is going to be very basic though: Monochrome, text mode only, hardcoded to screen memory location 400h.

Till next time!

No comments:

Post a Comment