Foreword
In the previous blog I implemented a very simple emulation of the C64 screen.We came to the point where it showed the welcome message, but no flashing cursor.
In this blog we are going to investigate why the cursor is not shown and fix it.
I will be taking a bit of a detour in this blog showing how to get info for an emulator from schematics and chip Data Sheets.
Although not really necessary for this blog for investigating something as simple as a missing flashing cursor, this technique will be really useful for future blogs.
Looking for a clue
So, we don't see a cursor. Where do we start to investigate this issue?
First price would be to have a commented disassembled version of the ROM code. There is indeed a resource on the internet that will provide us with this information:
http://www.ffd2.com/fridge/docs/c64-diss.html
We should now ask the browser to search for the keywords flash or cursor on the commented listing.
When I do the search I get very related hits like read/set XY cursor position, move cursor to previous line and so on.
Finally I found what I found what I was looking for:
; normal IRQ interrupt EA31 20 EA FF JSR $FFEA ; do clock EA34 A5 CC LDA $CC ; flash cursor EA36 D0 29 BNE $EA61 EA38 C6 CD DEC $CD EA3A D0 25 BNE $EA61 EA3C A9 14 LDA #$14 EA3E 85 CD STA $CD EA40 A4 D3 LDY $D3 EA42 46 CF LSR $CF EA44 AE 87 02 LDX $0287 EA47 B1 D1 LDA ($D1),Y EA49 B0 11 BCS $EA5C EA4B E6 CF INC $CF EA4D 85 CE STA $CE EA4F 20 24 EA JSR $EA24 EA52 B1 F3 LDA ($F3),Y EA54 8D 87 02 STA $0287 EA57 AE 86 02 LDX $0286 EA5A A5 CE LDA $CE EA5C 49 80 EOR #$80 EA5E 20 1C EA JSR $EA1C ; display cursor
I have highlighted important parts in bold. It is clear that the flashing of the cursor is part of the job of an IRQ handler.
Aha! We haven't implemented interrupts yet in our emulator, though we already did something similar with the BRK instruction.
Together with discovery comes a secondary question: When should the interrupts fire?
This is perhaps a more difficult question to answer. To answer this question would require some knowledge of the hardware peripheral registers on the C64.
Surely, there are hundreds of C64 memory maps available on the Internet that will provide you with this information, but in this blog I am going to show you another interesting way: Using schematics and DataSheets.
Isn't this an overkill? Maybe, but in my experience with my Java emulator I actually found that there is some information that the C64 memory simply doesn't give you. Especially when you want to emulate hardware.
So, lets get snooping...
Making our own memory map
Let us start by finding out what is connected to the IRQ pin of the CPU. There is some schematics via the following link that will provide us with this information:http://www.zimmers.net/anonftp/pub/cbm/schematics/computers/c64/
Here is a snippet from schematic:
n this snippet I have highlighted in red what is connected to the IRQ pin of the CPU.
In this case we see that the IRQ pin of a 6526 CIA chip is connected to the IRQ pin of the CPU. What is nice about this schematic is that they also wrote on the CIA block which space in memory it is occupying.
Next we need to dig into a DataSheet for the 6526 CIA chip. Many DataSheet web sites will give you the original Datasheet as a scanned copy in PDF.
This DataSheet is written in paragraph form, so it is not very handy as a quick reference. So I tried to give a summarised version as follows:
0 PRA Peripheral Data Register 1 PRB Peripheral Data Register 2 DDRA Data Direction Register 3 DDRB Data Direction Register 4 Timer A Low 5 Timer A High 6 Timer B Low 7 Timer B High 8 TOD Tenths 9 TOD Seconds A TOD Minutes B TOD Hour C SDR (Serial Data) D Interrupt -> IR 00 FLG SP Alarm TB TA E Control Bit 0 = 1 -> Start Timer A Bit 1 = Timer A on PB6 Bit 2 = 1-> Toggle 0=Pulse Bit 3 = 1-> One Shot 0->Continious Bit 4 = Load Force Bit 5 = 1-> Timer A counts CNT signals 0-> Timer A counts 02 Bit 6 = Serial Bit 7 = 1->50Hz 0->60Hz F Same as E Except Bit 1 Timer B on CRB6 CRB5 0 0 Timer B counts 02 0 1 Timer B counts CNT 1 0 Timer B counts Timer A underflows 1 1 Timer B counts Timer A underflows while CNT high Bit7 writing 1 -> Writing to TOD registers set alarm writing 0 -> Writing to TOD registers set TOD
As you can see the 6526 only has 16 registers. On the CIA block on the diagram, however, 256 memory locations is reserved for CIA 1. This means that in the address range DC00 to DCFF only the least four bits will be used.
Another thing also to take note of is that for some registers a read operation and a write operation will each access a different register. More on this to follow.
With all this information at our disposal, what is the next step? A good place to start is to find out which interrupts our emulator attempted to enable. So lets do a memory dump at DC00:
The register to look for is DC0D and its value is 81h. This means an interrupt is only enabled for timer A.
So, lets try get all the settings for timer A. We will start at address DC0E. Bit 0 is set, so we know the ROM code attempted to start the timer.
The next important bit is bit three. We see it is set to 0. This means our timer is running in continuous mode. It will count down to zero, cause an interrupt and then start counting down again from a pre-defined value.
Next, we need to check bit 5. It is set to zero and this means it is counting 02 signals. 02 signals runs at the same clock speed as the CPU, which is more or less 1MHz.
Finally, lets look at the the Timer A Low and Timer A High value. If we puts these two values together, you get 4025h (This is 16421 in decimal). So what does this value mean? We know our CIA is counting at 1Mhz. So an interrupt is thrown every 16421 clock cycles. Lets convert this to seconds:
16421/1000000 = 0.016421
This corresponds to more or less 60 Interrupts per second.
At last! We know now we should trigger an interrupt 60 times per second. This is very close to our batch interval. So for now it would be sufficient to trigger an interrupt once every time we execute the runBatch method.
There goes another hack! In a later blog post we will eventually come back and replace the hack with some proper code.
Implementing IRQ's in our emulator
Firstly we need a way to inform our CPU to perform an interrupt. For this we will create a public method setInterrupt(). This method will merely set a private variable. So firstly, define the private variable:var localMem = memory; var acc = 0; var x = 0; var y = 0; var pc = 0x400; var sp = 0xff; var zeroflag = 0; var negativeflag = 0; var carryflag =0; var overflowflag =0; var decimalflag = 0; var interruptflag = 1; var breakflag = 1; var interruptOcurred = 0;
And next, the public method:
this.setInterrupt = function () { interruptOcurred = 1; }
Next, in our Cpu step method we should actually check this variable and trigger an interrupt if it is set. This trigger should only happen if the interruptFlag is 0:
this.step = function () { if ((interruptOcurred == 1) & (interruptflag == 0)) { } var opcode = localMem.readMem(pc); pc = pc + 1; var iLen = instructionLengths[opcode]; var arg1 = 0;
What do we put inside the if statement? We already wrote similar code when we implemented the BRK instruction. Lets refresh our memory:
case 0x00: 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;
We can almost copy and paste this source just as is. There is just a couple of things we should be aware of:
- Don't increment the program counter by one as done in the first line.
- Remember to set interruptOccured to back to 0
- Be careful of the breakflag!
As you remember we have implemented the BRK instruction while we had been running the Klaus Test Suite. All the tests run through fine when we left the break flag as one.
When implementing an IRQ, however, we should be more cautious. When a IRQ occur, the break flag gets set briefly to 0 just to distinguish itself from a BRK interrupt.
The key here is to ensure that the status byte being pushed onto the stack have the break flag set as zero. So, in our case it would be sufficient if we set the break flag to zero before doing the status flag push. Directly after the push we need to change the break flag back to 1.
With all this in mind the code will look as follows:
if ((interruptOcurred == 1) & (interruptflag == 0)) { interruptOcurred = 0; Push(pc >> 8); Push(pc & 0xff); breakflag = 0; Push(getStatusFlagsAsByte()); breakflag = 1; interruptflag = 1; tempVal = localMem.readMem(0xffff) * 256; tempVal = tempVal + localMem.readMem(0xfffe); pc = tempVal; }
What remains to be done is to cause an interrupt each time the runBatch Method gets executed:
function runBatch() { if (!running) return; myvideo.updateCanvas(); var targetCycleCount = mycpu.getCycleCount() + 20000; mycpu.setInterrupt(); while (mycpu.getCycleCount() < targetCycleCount) { mycpu.step(); var blankingPeriodLow = targetCycleCount - 100; if ((mycpu.getCycleCount() >= blankingPeriodLow) & (mycpu.getCycleCount() <= targetCycleCount)) { mymem.writeMem(0xD012, 0); } else { mymem.writeMem(0xD012, 1); } if (mycpu.getPc() == breakpoint) { stopEm(); return; } } }
If we now rerun the emulator, we see a flashing cursor!
In Summary
In this blog we investigate why our emulator is not showing a flashing cursor. This investigation took us a bit of a detour by finding information from datasheets and schematics.In the end the solution was to implement interrupts in our emulator and triggering an interrupt once every time the runBAtch method is called.
In the next Blog we will see if we could simulate a key press in our emulator. This will be the first step in adding keyboard support to our JavaScript emulator.
Till next time!
No comments:
Post a Comment