Friday 8 July 2016

Part 16: Emulating flashing Borders

Foreword

In the previous blog we finished off with adding tape emulation to our emulator.

In this blog we will try and see if we could emulate the flashing borders of the loader for game Dan Dare. This will necessitate us to jack up our video class by adding colours to the video output. 

The flashing border effect

So, how does the tape loader achieve the flashing border effect?

The tape loader achieves the flashing border effect by constantly changing the border colour while the VIC II video chip draws the frame.

For our emulator to simulate this effect successfully, this would mean that we need to implement some form of scan line rendering.

Currently our emulator doesn't do scan line-based rendering, but frame based rendering. This means, we execute the amount of instructions that would fit into a frame display period, which is 20000 CPU cycles.

So, let us start by doing a bit of research on the relationship of CPU cycles to screen pixels.

There is a very nice picture on the following web site explaining very nicely the relationship:

http://dustlayer.com/vic-ii/2013/4/25/vic-ii-for-beginners-beyond-the-screen-rasters-cycle


First of all this picture tells us that the CPU completes 63 clock cycles per scan line. Also, in one CPU clock cycle, 8 pixels gets drawn to the screen.

It is also interesting to note that for each line of 63 cycles, there is 13 cycles for which there is no pixels drawn. These cycles are during the horizontal blanking period.

Although we don't need to draw anything for these 13 cycles per line, it is still important that we account for these cycles to get our timing right.

As you get a horizontal blanking period, you also get a vertical blanking period. The vertical blanking period is towards the end of the frame, wrapping to the beginning of the next frame. This period amounts to about 28 lines that amounts to nothing drawn unto the screen. These lines we also need to account for to get our timing write.

Implementing scan line rendering

So, lets implement scan line rendering into our emulator.

First, we need to get hold of RGB values for the 16 colours on the C64.

If you do a Internet search for these RGB values, you will get different values all over the show. This is probably as expected, since we all have different memories of these colours on these colours on our television sets at that time when the C64 was popular.

I am settling for the colour tablet from the following website:

https://www.c64-wiki.com/index.php/Color

With all this information, our video class looks as follows:

function video(mycanvas, mem, cpu) {
  var localMem = mem;
  var ctx = mycanvas.getContext("2d");
  var mycpu = cpu;
  var cpuCycles = 0;
  var cycleInLine = 0;
  var cycleline = 0;
  var charPosInMem = 0;  
  var posInCanvas = 0;
  var imgData = ctx.createImageData(400, 284);


  const colors = [[0, 0, 0],
                  [255, 255, 255],
                  [136, 0, 0],
                  [170, 255, 238],
                  [204, 68, 204],
                  [0, 204, 85],
                  [0, 0, 170],
                  [238, 238, 119],
                  [221, 136, 85],
                  [102, 68, 0],
                  [255, 119, 119],
                  [51, 51, 51],
                  [119, 119, 119],
                  [170, 255, 102],
                  [0, 136, 255],
                  [187, 187, 187]];

  this.getCurrentLine = function() {
    return cycleline;
  }

  this.processpixels = function() {
    var numBytes = mycpu.getCycleCount() - cpuCycles;
    cpuCycles = mycpu.getCycleCount();
    var i;
    for (i = 0; i < numBytes; i++) {
      if (isVisibleArea()) {
        if (isPixelArea() & displayEnabled()) {
          drawCharline();
        } else {
          fillBorderColor();
        }
      }

      cycleInLine++;

      if (cycleInLine > 63) {
        cycleInLine = 0;
        cycleline++;
        updateCharPos();
      }
      if (cycleline > 311) {
        cycleline = 0;
        ctx.putImageData(imgData,0,0);
        posInCanvas = 0;
        charPosInMem = 0;
        //imgData = ctx.createImageData(400, 284);
        return true;
      }


    }
      return false;
  }

  function displayEnabled() {
    return ((localMem.readMem(0xd011) & 0x10) != 0)
  }

  function updateCharPos() {
    if ( !((cycleline > 41) & (cycleline < (42 + 200))) )
      return;
    var lineInScreen = cycleline - 42;
    if (lineInScreen == 0) {
      charPosInMem = 0;
      return;
    }
    if ((lineInScreen & 7) == 0) {
      charPosInMem = charPosInMem + 40;
    }
  }


  function drawCharline() {
    var screenCode = localMem.readMem(1024 + charPosInMem + cycleInLine - 5);
    var currentLine = localMem.readCharRom((screenCode << 3) + ((cycleline - 42) & 7));
    var textColor = localMem.readMem(0xd800 + charPosInMem + cycleInLine - 5);
    var backgroundColor = localMem.readMem(0xd021);
    for (currentCol = 0; currentCol < 8; currentCol++) {
      var pixelSet = (currentLine & 0x80) == 0x80;
      if (pixelSet) {
        imgData.data[posInCanvas + 0] = colors[textColor][0];
        imgData.data[posInCanvas + 1] = colors[textColor][1];
        imgData.data[posInCanvas + 2] = colors[textColor][2];
        imgData.data[posInCanvas + 3] = 255;
      } else {
        imgData.data[posInCanvas + 0] = colors[backgroundColor][0];
        imgData.data[posInCanvas + 1] = colors[backgroundColor][1];
        imgData.data[posInCanvas + 2] = colors[backgroundColor][2];
        imgData.data[posInCanvas + 3] = 255;
      }
      currentLine = currentLine << 1;
      posInCanvas = posInCanvas + 4;
   }
  }

  function fillBorderColor() {
    var borderColor = localMem.readMem(0xd020) & 0xf;
    var i;
    for (i = 0; i < 8; i++ ) {
      imgData.data[posInCanvas + 0] = colors[borderColor][0];
      imgData.data[posInCanvas + 1] = colors[borderColor][1];
      imgData.data[posInCanvas + 2] = colors[borderColor][2];
      imgData.data[posInCanvas + 3] = 255;
      posInCanvas = posInCanvas + 4;
    }

  }

  function isVisibleArea() {
    return (cycleInLine < 50) & (cycleline < 284);
  }

  function isPixelArea() {
    var visibleColumn = (cycleInLine > 4) & (cycleInLine < (5+40));
    var visibleRow = (cycleline > 41) & (cycleline < (42 + 200));
    return (visibleColumn & visibleRow);
  }

}

Let us quickly pause and explain this code. The class start with the declaration of a two dimensional array storing the RGB values for our colour tablet.

Obviously, the updateCanvas method has became redundant. We now make use use of a new method called processpixels.

The processpixels method needs to be invoked each time a 6502 instruction was executed. processpixels starts off by determining  how many cycles has passed since executing the previous instruction. For each cycle an 8 pixel lines segment gets drawn. During the looping process we also keep track of the following:
  • Whether the pixel segment is a segment in the border
  • Whether the pixel segment is a segment in the character area.
  • Whether the pixel segment is a segment in the vertical/horizontal blanking area.
You will also noticed that I have added a method called displayEnabled. This method checks the enabled bit in memory location D011. I have added this to emulate the screen blanking during tape searching/loading.
For completeness, let us see how this method will be invoked in the runBatch method:

      function runBatch() {
        if (!running)
          return;
        //myvideo.updateCanvas();
        //var targetCycleCount =  mycpu.getCycleCount() + 20000;
        while (true) { 
          mycpu.step();
          myAlarmManager.processAlarms();
          //if (mycpu.getCycleCount() > 6000000)            
          //  mymem.setSimulateKeypress();
          //var blankingPeriodLow = targetCycleCount - 100;
          if (myvideo.getCurrentLine() > 284) { 
            mymem.writeMem(0xD012, 0);
          } else  {
            mymem.writeMem(0xD012, 1);
          }
          if (mycpu.getPc() == breakpoint) {
            stopEm();
            return;
          }
          var framefinished = myvideo.processpixels();
          if (framefinished)
            return;        }
        //mycpu.setInterrupt();
      }
      

You will notice that the main loop within the runBatch method is now an endless loop, depending on the processpixels method to tell it when to exit.

One fundamental change is also the dimensions of the canvas we are working with. The new dimensions is now 400x284, since we need to cater for the border. This dimension needs to be applied to the video class as well as our index page.

A Test Run

Let us give our changes a test run:


Cool! Everything starts to look very familiar now :-)

Let us now see how our emulator behaves when we load DAN DARE.

This time not so promising. We get up to the point where our emulator says loading/ready and then it just hangs.

It is time we get our fingers dirty and dig into the loader code for Dan Dare.

We need to inspect the header to determine at which memory location the loader code gets loaded into. We know that the header gets loaded into 33Ch, so the only thing we need to do is to hit the STOP button when we see FOUND... From that point we can just do some memory inspection to get the address.


The byte in 33c gives you the type of file. Type 3 corresponds to a non-relocatable file. The next two bytes is the start address and the two bytes following is the end address.

From the screenshot we can deduce that the start address is 2a7 and the end address is 304.

From this address range it is clear that some vector addresses will be overwritten.

The question is which vector address will be overwritten. To get this answer we need to load the rest of the loader and inspect the vectors. We can just resume execution of the emulator. But, before we resume, lets just add a breakpoint on address at fc93. This will halt the emulator just after the loader code is loaded into memory.

Inspecting the first couple of vectors yield the following:

300 = e38b
302 = 351
304 = a57c

We can clearly see that the vector at 302 is overwritten. This is the address of the basic idle loop. The address 351 is a memory address within the cassette buffer. So, it appear that the loader use part of the bytes in the header to store some machine code.

Lets do some disassembly of the code at 351:

                  * = 0351
0351   20 A7 02   JSR $02A7
0354   A5 FD      LDA $FD
0356   05 FE      ORA $FE
0358   F0 F7      BEQ $0351
035A   20 60 03   JSR $0360
035D   4C 51 03   JMP $0351
0360   6C FD 00   JMP ($00FD)
0363   AD 0D DC   LDA $DC0D
0366   8E 0F DC   STX $DC0F
0369   4A         LSR A
036A   4A         LSR A
036B   26 C0      ROL $C0
036D   A5 C0      LDA $C0
036F   88         DEY
0370   F0 00      BEQ $0372
0372   AD 20 D0   LDA $D020
0375   49 05      EOR #$05
0377   8D 20 D0   STA $D020
037A   40         RTI
037B   C9 AA      CMP #$AA
037D   D0 6E      BNE $03ED
037F   C6 C1      DEC $C1
0381   D0 05      BNE $0388
0383   A9 19      LDA #$19
0385   8D 71 03   STA $0371
0388   A0 02      LDY #$02
038A   40         RTI
038B   C8         INY
038C   4A         LSR A
038D   B0 5E      BCS $03ED
038F   C9 50      CMP #$50
0391   D0 F5      BNE $0388
0393   A9 25      LDA #$25
0395   D0 44      BNE $03DB
0397   85 FB      STA $FB
0399   EE 98 03   INC $0398
039C   AD 98 03   LDA $0398
039F   C9 FF      CMP #$FF
03A1   D0 3B      BNE $03DE
03A3   A5 F7      LDA $F7
03A5   CD FD 02   CMP $02FD
03A8   D0 43      BNE $03ED
03AA   AD 11 D0   LDA $D011
03AD   29 EF      AND #$EF
03AF   05 F8      ORA $F8
03B1   8D 11 D0   STA $D011
03B4   A9 46      LDA #$46
03B6   D0 23      BNE $03DB
03B8   84 01      STY $01
03BA   91 F9      STA ($F9),Y
03BC   A0 05      LDY #$05
03BE   84 01      STY $01
03C0   45 AF      EOR $AF
03C2   85 AF      STA $AF
03C4   E6 F9      INC $F9
03C6   D0 05      BNE $03CD
03C8   EE 20 D0   INC $D020
03CB   E6 FA      INC $FA
03CD   A5 F9      LDA $F9
03CF   C5 FB      CMP $FB
03D1   D0 0B      BNE $03DE
03D3   A5 FA      LDA $FA
03D5   C5 FC      CMP $FC
03D7   D0 05      BNE $03DE
03D9   A9 6F      LDA #$6F
03DB   8D 71 03   STA $0371
03DE   A0 08      LDY #$08
03E0   40         RTI
03E1   C6 AE      DEC $AE
03E3   C5 AF      CMP $AF
03E5   F0 F7      BEQ $03DE
03E7   EE 20 D0   INC $D020
03EA   4C E7 03   JMP $03E7
03ED   A9 40      LDA #$40
03EF   85 C1      STA $C1
03F1   A9 09      LDA #$09
03F3   8D 71 03   STA $0371
03F6   C8         INY
03F7   40         RTI
03F8   00         BRK
03F9   00         BRK
03FA   00         BRK
03FB   00         BRK
03FC   00         BRK
03FD   00         BRK
03FE   00         BRK
03FF   00         BRK
0400              .END



The first instruction calls a subroutine at 2a7, so let us also do a disassembly at that location:

                  * = 02A7
02A7   A9 7F      LDA #$7F
02A9   8D 0D DC   STA $DC0D
02AC   A9 5E      LDA #$5E
02AE   8D 06 DC   STA $DC06
02B1   A0 01      LDY #$01
02B3   8C 07 DC   STY $DC07
02B6   88         DEY
02B7   8C 1A D0   STY $D01A
02BA   84 AE      STY $AE
02BC   84 AF      STY $AF
02BE   A9 63      LDA #$63
02C0   8D FE FF   STA $FFFE
02C3   A9 7A      LDA #$7A
02C5   8D FA FF   STA $FFFA
02C8   A9 03      LDA #$03
02CA   8D FF FF   STA $FFFF
02CD   8D FB FF   STA $FFFB
02D0   A9 7B      LDA #$7B
02D2   8D 71 03   STA $0371
02D5   A9 F7      LDA #$F7
02D7   8D 98 03   STA $0398
02DA   A2 19      LDX #$19
02DC   A9 05      LDA #$05
02DE   85 01      STA $01
02E0   A9 90      LDA #$90
02E2   8D 0D DC   STA $DC0D
02E5   24 AE      BIT $AE
02E7   10 FC      BPL $02E5
02E9   A9 7F      LDA #$7F
02EB   8D 0D DC   STA $DC0D
02EE   A9 37      LDA #$37
02F0   85 01      STA $01
02F2   85 C0      STA $C0
02F4   A9 81      LDA #$81
02F6   8D 0D DC   STA $DC0D
02F9   EE FD 02   INC $02FD
02FC   60         RTS
02FD   00         BRK
02FE   00         BRK
02FF   00         BRK
0300   8B         ???
0301   E3         ???
0302   51 03      EOR ($03),Y
0304   7C         ???
0305   A5 1A      LDA $1A
0307   A7         ???
0308   E4 A7      CPX $A7
030A   86 AE      STX $AE
030C   00         BRK
030D   00         BRK
030E   00         BRK
030F   00         BRK
0310   4C 48 B2   JMP $B248
0313   00         BRK
0314   2C F9 66   BIT $66F9
0317   FE 47 FE   INC $FE47,X
031A   4A         LSR A
031B   F3         ???
031C   91 F2      STA ($F2),Y
031E   0E F2 50   ASL $50F2
0321   F2         ???
0322   33         ???
0323   F3         ???
0324   57         ???
0325   F1 CA      SBC ($CA),Y
0327   F1 ED      SBC ($ED),Y
0329   F6 3E      INC $3E,X
032B   F1 2F      SBC ($2F),Y
032D   F3         ???
032E   66 FE      ROR $FE
0330   A5 F4      LDA $F4
0332   ED F5 00   SBC $00F5
0335   00         BRK
0336   00         BRK
0337   00         BRK
0338   00         BRK
0339   00         BRK
033A   00         BRK
033B   00         BRK
033C   03         ???
033D   A7         ???
033E   02         ???
033F   04         ???
0340              .END


Aha! This piece of code change the IRQ address at memory location FFFE an FFFF and switches the kernel ROM out of view via memory location 1 so that the CPU can see the new IRQ address.

We haven't implemented the in/out switching of ROMS in our emulator. SO, lets implement functionality for switching in/out the KERNEL ROM.

We add the following method to our memory class:

  function kernelEnabled() {
    temp = mainMem[1] & 3;
    return (temp >= 2);
  }

We then use this method within readMem:

  this.readMem = function (address) {
    if ((address >= 0xa000) & (address <=0xbfff))
      return basicRom[address & 0x1fff];
    else if ((address >= 0xe000) & (address <=0xffff) & kernelEnabled())
      return kernalRom[address & 0x1fff];
    else if ((address >= 0xdc00) & (address <= 0xdcff)) {
      return ciaRead(address);
    } else if (address == 1) {
      var temp = mainMem[address] & 239;
      if (!playPressed)
        temp = temp | 16;
      return temp;
    }
    return mainMem[address];
  }


It is important to set memory location to 3  when you create the memory class, else the system will not boot:

function memory(allDownloadedCallback, keyboard, timerA, timerB, interruptController,tape)

{
  var mytimerA = timerA;
  var mytimerB = timerB;
  var myinterruptController = interruptController;
  var mytape = tape;
  var mainMem = new Uint8Array(65536);
  mainMem[1] = 3;
  var basicRom = new Uint8Array(8192);
  var kernalRom = new Uint8Array(8192);
 ...
} 

Let us try again. This time there was a faint piece of hope... a second of flashing borders, then it stopped.

Probably the first thing to do is to determine if the Tape class continues to fire interrupts at his point. Another thing to check is also whether the tape class is at the correct  position on tape at this point.

To summarise my findings on this issue. I found that the tape class indeed continued to fire interrupts when the flashing borders stopped. What I did picked up was that the Tape interrupt always fired before TIMER B expired. This meant that only a stream of zeros was was identified, no ones.

After some digging, I found that the culprit was the setTimerLow method in the timer class:

  this.setTimerLow = function(low) {
    timerHigh = low;
  }

The setter was setting the timerHigh value instead of timerLow!

This time we have better luck. Here is a set of screen shots:



In the last screenshot, the emulator was actually supposed to switch the screen to a high resolution multi-color mode for the splash screen. We will make the high resolution mode the task of our next blog.

In Summary

In this blog we jacked up our Video class so it can output colors. When then attempted to emulate the flashing flashing borders of the Dan Dare tape loader.

We encountered a couple of bugs in our emulators, which we have resolved.

In the next blog we will implement high resolution  mode and see if we can display the splash screen of Dan Dare tape loader.

Till next time!

1 comment: