Thursday, 23 June 2016

Part 14: Tape emulation

Foreword

In the previous blog we managed to integrate our emulator with a physical keyboard.

In this blog we will start to implement functionality to emulate tape input. Well, not from a physical C64 tape, but from a tape image file, which is pretty close to the raw format of a physical C64 tape.

We will start off again with some theory, covering both a bit of the technical details on the tape functionality in the C64 and we will be deciphering the .tap file format. A .tap file is essentially a tape image file.

After the theory we will attempt to start and implement the tape emulation functionality in our emulator. In this exercise we will actually discover some redesign surprises. We will therefore see how far we can get to implement the tape functionality in this blog and continue in the next blog.

Some insight into future posts

Up to this point in time our emulator has enough functionality allowing you to enter a Basic program and running it.

I think it is about time we move our goals to something else. My next goal in mind is to actually see if we can emulate a game.

The target game I have in mind for future posts is Dan Dare - Pilot of the future. This game is available from many websites as a .tap file. Hence, this is the reason why we will be exploring tape emulation in this blog.

In coming posts I will also be following an incremental approach. Here is some sub targets:

  • See if we can emulate the tape loading process up to the point where our emulator shows the message FOUND DAN DARE. If we get this far, we know that our tape emulation is more or less on track.
  • Emulate the loading process. When the DAN DARE game loads, it shows flashing borders and a splash screen. So we will incrementally add functionality to cater for this.
  • Show the game intro screen.
This is more or less the steps we will follow to emulate the game.

C64 Tape Technicalities

Let us begin by finding out how the Commodore Datasette is wired to the C64. Here is a small snippet of the schematic what highlights this for us:


The pins of the Tape connector is shown on the right. Let us briefly pause and discuss the purpose of the pins. There are four pins of importance:

  • Read
  • Write
  • Motor
  • Cassette sense
The read and write pins speak for themselves.

The Motor pin remind us of the remote jack early tape recorders was equipped with, allowing the use of microphones that enables you to stop/resume recording via a switch on the microphone itself.

The sense pin actually informs the C64 when the play, rewind or fastforward is depressed. This aid in only enabling the spindle motor when necessary.

It is important to take note of the functions of the sense and motor pins since they would also effect emulation.

Now, how is the tape drive interfaced to the C64?

Firstly, the motor, sense and write pins are connected directly to the CPU. Remember that the 6510 is a special addition of the 6502 which contains 8 additional pins which you could either read or set bits from. The 6510 dedicates memory locations 0 and 1 for the purpose of these pins.

The Read pin is connected to the FLAG pin of CIA chip number 1. The flag pin triggers an interrupt when the data on the pin transitions from a one to a zero. A transition from a zero to a one doesn't trigger any interrupt at all.

Needless to say, the read pin gets its data from the tape. Simple, but one might wonder: The read pin provide discrete 0's and 1's, whereas the tape is an analogue device that can provide you with any voltage in between. So really, when does the tape unit send a one and when does it send a zero down the read wire? The tape unit makes use of an encoding scheme called zero-crossing. The following image from wikipedia illustrate zero-crossing:

Ok, not shown in this picture is that the blue line represents 0 volts, everything above the line represents a positive voltage and everything below the blue line is a negative voltage. All in all when the voltage from the tape drops below 0 volt and become negative, a zero will be outputted on the read pin. Similarly, when the voltage approach zero volt and become positive, a one will be outputted to the read wire.

As stated earlier, only a transition from a one to a zero will cause an interrupt in the CIA. This is indicated by the 0's on the blue line in the diagram.

Information is extracted from this scheme by measuring the time that lapsed between two transitions. Different time periods have different meanings. As a matter of fact, all that the tape image file formats are storing is a sequence of durations. In the next section we are going discuss the .TAP file format.

The .TAP format

The following web site will give you detailed information on the .TAP format:

http://unusedino.de/ec64/technical/formats/tap.html

The highlights of the format is as follows:

  • Duration data starts at hex offset 14.
  • Each duration is represented by one byte. If you take this value and multiply it by 8, you will get the duration in number of CPU clock cycles.
Let us quickly work through an example. I am going to open a .tap file of Dan Dare in a hex editor:


I have highlighted the beginning of data at location 14h. As you might realise, the first couple of thousands of durations are all the same. If you ever listened to C64 tape, this observation makes perfect sense. Each file is preceded by mono tone of about 10 seconds. Lets see if we can verify this.

The repeating byte stops more or less at 6a13h. Converted to decimal, this equals 27155 durations. How long is a duration? The repeating byte is 30h and this we need to multiply with 8. The answer in decimal is 384 clock cycles.

Now 27155 x 384 = 10427520 clock cycles. We know our CPU is clocking at 1MHz, so the duration of the mono tone is 10.4 seconds.

Emulating a tape in our Emulator

We can summarise the tape emulation process in one sentence: Causing interrupts at predefined time intervals.

Lets take a working example using the Dan Dare .tap file again. For this example we will use the sequence of bytes after the monotone:

Lets convert the sequence of bytes to clock cycles:

56h = 86 * 8 = 688
42h = 66 * 8 = 528
42h = 66 * 8 = 528
30h = 48 * 8 = 384
30h = 48 * 8 = 384
42h = 66 * 8 = 528
30h = 48 * 8 = 384

So, the emulation process will be something like the following:
  • Wait 688 clock cycles
  • Cause an interrupt
  • Wait 528 clock cycles
  • Cause an interrupt
  • Wait another 528 clock cycles
  • Cause an interrupt
  • Wait 384 clock cycles
  • Cause an interrupt
And so on...

Coding the solution

Let us now try to write the above mentioned algorithm in JavaScript.

First, we need to give our emulator access to the binary contents of the .TAP file. The first instinct would be to use a XMLRequestObject for this purpose. But, as we know from previous posts, XMLRequestObject can only use files served by a webserver.

This means if we host our emulator on a web site for people to play on, they would limited to only .tap files that we provide.

Wouldn't it be nice, if we would allow a user to select a .tap file located on their local drive to emulate?

Indeed there is way to do that. HTML provides an input type called file. JavaScript can extract the file name from this element and load the contents of the file into an arraybuffer, even if it is local.

Wonderful! There might be a couple of questions onto why I didn't use these elements to load the C64 ROMS into the our emulator. This would have avoided the installation of a local webserver to test the emulator locally. The answer to this is that there is bit of a snag when using the input type file: You need to select the files manually. This would mean each time you open the emulator in a browser, you would need to manually select all three ROMS, and then boot the system. A bit of a tedious process!

So, let us add a file input element to our index page:
  <body onkeydown="mykeyboard.onkeydown(event)" onkeyup="mykeyboard.onkeyup(event)">
    <h1>6502 Emulator From Scratch</h1>
    <p>This is JavaScript Test</p>
<canvas id="screen" width="320" height="200">

</canvas>
<br/>
<input type="file" id="file" name="myfile"/>
<button onclick="">Attach</button>
</br>
<textarea id="registers" name="reg" rows="1" cols="60"></textarea>
<br/>
<textarea id="memory" name="mem" rows="15" cols="60"></textarea>

I have also added a button, so we can inform the emulator to load the tap file in an array buffer. The onclick event handler for this button this still needs to be defined. We will come back to this in moment.

I would like to encapsulate all the Tape functionality in a class of its own, so lets create the file Tape.js:

function tape() {
}

The first functionality I would like to implement in this class is to load the contents of a .tap file into an arraybuffer. Here is the code:

 var tapeData;

 this.attachTape = function(file) {
       var reader = new FileReader();
       reader.onload = function(e) {
         var arrayBuffer = reader.result;
         tapeData = new Uint8Array(arrayBuffer);
         alert("Tape attached");
       }
       reader.readAsArrayBuffer(file);
  }

Firstly we declare a private variable tapeData to store the contents of the .tap file.

The public method attachTape is the actual method our onclick event should call when we click the attach button. It accepts a Input file element as the parameter. The instance of FileReader is the class that does all the work for us. All in all the functionality to populate the arraybuffer is very similar to what we are doing when loading the C64 ROMS at bootup via XMLHTTPRequest objects.

As an added measure I am just showing an alert box to tell user if the attach process was successful.

Before we continue, we should just populate the onclick event of the Attach button:

  <body onkeydown="mykeyboard.onkeydown(event)" onkeyup="mykeyboard.onkeyup(event)">
    <h1>6502 Emulator From Scratch</h1>
    <p>This is JavaScript Test</p>
<canvas id="screen" width="320" height="200">

</canvas>
<br/>
<input type="file" id="file" name="myfile"/>
<button onclick="myTape.attachTape(document.getElementById('file').files[0])">Attach</button>
</br>
<textarea id="registers" name="reg" rows="1" cols="60"></textarea>
<br/>
<textarea id="memory" name="mem" rows="15" cols="60"></textarea>


So, what do we code next? Well, as we have seen earlier, the core of tape emulation is to wait a certain amount of clock cycles before triggering an interrupt. We are doing something very similar in the runBatch method for our timer interrupt. We could probably do a similar hack in the runBatch method for the tape functionality, but this starting to look a bit clunky.

It would actually be nice to create a class that will manage the need for triggering events at certain clock cycles for us. With this in mind I have created the following class within new file called AlarmManager.js:

function alarmManager() {
  var myCpu;
  var alarms = [];
  var lastCycleCount = 0;

  this.setCpu = function (cpu) {
    myCpu = cpu;
  }

  this.addAlarm = function (alarmObject) {
    alarms.push(alarmObject);
  }

  this.processAlarms = function () {
    var i;
    var numTicks = myCpu.getCycleCount() - lastCycleCount;
    lastCycleCount = myCpu.getCycleCount();
    for (i = 0; i < alarms.length; i++) {
      var currentAlarm = alarms[i];
      if (currentAlarm.getIsEnabled()) {
        var ticksBeforeExpiry = currentAlarm.getTicksBeforeExpiry() - numTicks;
        if (ticksBeforeExpiry > 0) {
          currentAlarm.setTicksBeforeExpiry(ticksBeforeExpiry);
        } else {
          currentAlarm.setTicksBeforeExpiry(0);
          currentAlarm.trigger();
        }
      }
    }
  }
}

Lets discus this class.

Firstly, if a particular class, like tape, have the need to trigger an event at a certain clock cycle, it needs to call addAlarm to add itself to the alarms array.

The next method of interest is the procesAlarms. You need to invoke this method within the runBatch method each time you have executed a cpu instruction.

The processAlarms method starts off by calculating how many clock cycles has passed since the previous time this method was called. This method then goes looping through every alarm and decreasing the remaining clock cycles of each alarm by the number of clock cycles that lapsed since the previous time.

If the remaining time of an alarm reaches zero, the event associated with the alarm is triggered.

When a class wishes to add itself to the list of alarms, it is important for that class to implement the following public methods:
  • getIsEnabled: Return true if you want AlarmManager to process your alarm
  • getTicksBeforeExpiry: Return the number of clock cycles left before calling event
  • setTicksBeforeExpiry: Adjust number of clock cycles remaining with specified value
  • trigger: Alarm manager calls this method on your class when remaining clock cycles has reached 0
With all this information, we can now complete our  tape class:

function tape(alarmManager) {
  var myAlarmManager = alarmManager;
  var tapeData;
  var posInTape;
  var isEnabled = false;
  var ticksBeforeExpiry = 0;
  myAlarmManager.addAlarm(this);

  this.attachTape = function(file) {
       var reader = new FileReader();
       reader.onload = function(e) {
         var arrayBuffer = reader.result;
         tapeData = new Uint8Array(arrayBuffer);
         posInTape = 0x14;
         scheduleNextTrigger();
         alert("Tape attached");
       }
       reader.readAsArrayBuffer(file);

  }

  this.setMotorOn = function(bit) {
    isEnabled = (bit == 0) ? true : false;
  }

  function scheduleNextTrigger() {
    ticksBeforeExpiry = tapeData[posInTape] << 3;
    posInTape++;
  }

  this.getIsEnabled = function() {
    return isEnabled;
  }

  this.getTicksBeforeExpiry = function() {
    return ticksBeforeExpiry;
  }

  this.setTicksBeforeExpiry = function(ticks) {
    ticksBeforeExpiry = ticks;
  }

  this.trigger = function() {
    //trigger interrupt
    scheduleNextTrigger();
  }

}

When we create an instance of tape, we immediately add ourself to the list of alarms.

An interesting addition to our class is the public method setMotorOn. Our memory class should call this method each there is a write to memory location 1. This will be our cue to decide if our tape class should be running or not.

In the trigger method two things should basically happen: Trigger an interrupt and schedule the alarm. You will notice that trigger interrupt I have added as a comment. This is because at this point it is not completely clear how the interrupt functionality should work for this scenario. We will tackle this in the next blog.

In Summary

In this blog we started to implement tape emulation to our emulator.

We ended off with identifying the need to implement CIA interrupt functionality as one of the prerequisites for tape emulation.

Though not mentioned in this blog, for successful Tape emulation, the full Timer A and Timer B functionality of the CIA also needs to be implemented.

Thus, in the next bog, we will continue with implementing the Tape functionality and add the following emulation:

  • CIA interrupts
  • Timer A and Timer B functionality of the CIA
Till next time!


No comments:

Post a Comment