Over the weekend, I got the emulator running nicely - I set it so that it would 'power-on' and read the hard-wired RESET address at $FFFC, which on a VIC-20 vectors to $FD22 - the startup entry point for the Kernal OS ROM. To begin with, no instructions were implemented, so I configured the code to execute until it hit an instruction I hadn't catered-for yet, and then break with a short disassembly of the area so I could see which instruction needed something written for it. This was a really good feedback/incentive mechanism, because the more instructions I got working, the further through the ROM the emulator ran before stopping again.
This worked wonderfully, right up until the ROM went into an infinite loop:
This worked wonderfully, right up until the ROM went into an infinite loop:
$FDEB 20 C3 E5 JSR $E5C3 ; VIC register init. subroutine
$FDEE 4C EB FD JMP $FDEB ; Jump back to previous JSR
The call to the routine at $E5C3 happens at the end of the bit of code that checks to see how much memory the VIC-20 has installed, and whether it's all working properly. At the point of this infinite loop, the subroutine is just a short sequence to initialise some registers on the VIC chip (the Video Interface Chip for which the VIC-20 is named) and then it returns, after which that JMP put us right back at the subroutine call again. This was obviously Not Good, and indicated that I either had a dodgy ROM image, or a bug in my decode logic that was giving the JMP the wrong address - after all, the real VIC-20 doesn't go into this infinite loop when starting, otherwise it'd be a fairly disappointing experience for anyone trying to use it!
The first thing I did was fire-up VICE (a very good emulator of a variety of Commodore machines) in VIC-20 mode and had a PEEK at the ROM locations my code was getting stuck at. To my surprise, they matched byte-for-byte - so my ROM image was not broken in some way, and the JMP address was being correctly decoded. A real VIC-20 -does- have this infinite loop in its ROM. So how the hell does it get out of it and finish booting-up?
I wondered if some hardware event took place that would reset the Program Counter to something else - maybe the ROM authors deliberately wanted the boot process to pause here until some other part of the computer was properly awake and fired an interrupt. But at this point in the ROM (which I'd been tracing very carefully as I debugged my emulator) I knew we hadn't enabled interrupts yet, and no timers had been set running either. Maybe the NMI (Non-Maskable Interrupt) was being fired by something - that's a hardware interrupt that overrides whatever else the CPU is doing, but which my emulator hasn't got implemented yet...
Well, I pondered and wondered for a bit, and re-read a lot of documentation about the VIC-20 boot process (including an annotated ROM disassembly) and didn't find anything helpful. So I posted the question to Denial and YakYak in the hope that someone on one of those forums would have a deeper understanding of what was going on. And 'Mike' on Denial did indeed have a cunning insight into the problem:
Mike: "The VIC ends up in this endless loop if it detects that RAM 'ends' before $2000. Normally this would indicate a defect in the built-in RAM. But in your case, there must be a bug in your RAM emulation."
What? A bug? In my memory emulation code?? Impossible!
Well, I went back and checked, of course. There's not actually that much code in the class that handles emulated memory, and it was all working fine. So I dug deeper into the ROM code that does the memory check, and confirmed that the branch into the endless loop is triggered if a certain zero-page location ($C2) contains a value of less than #$20 by the time the test ends, which indicates that somewhere below $2000 the memory failed a test and the counter wasn't incremented. So I re-ran the emulator with memory diagnostics enabled, so I could see which memory locations were being accessed and what their contents were, and sure enough we were falling into the infinite loop because $C2 contained #$1F at the end of the test.
Now we're emulating memory here. There's no real RAM that could fail a test - it's just a C# array of objects, representing the memory location and its type. The type is there so I can tell whether the memory location has been defined as RAM, ROM, Memory-Mapped I/O, or if it hasn't been defined as anything, because there isn't any actual memory present there. In each case, as accesses to the location occur, the type is checked to make sure the access is permitted - so we allow reads and writes to RAM and I/O, reads but NOT writes to ROM, and a read which always returns zero to Undefined (and no writes, of course).
Scanning back through the emulator output, I could see that the memory test ROM code was doing a series of non-destructive writes and reads through the RAM to make sure it was working, but right at the end of the test, as it was checking the screen memory space, the reads were returning zero instead of the test value, and so the test failed and $C2 didn't get incremented. Why would the memory emulation work for all other areas of RAM that were being tested, but not that last little section. Just six bytes were failing. WTF?
Well, after a minute or two, the light dawned. I knew, from experience, that the VIC-20 screen RAM is 506 bytes long, from $1E00 to $1FF9. Whatever documentation you care to look at confirms this, and my memory emulation was precisely assigning that space correctly - as we can see on the sixth allocation row from the diagnostics:
Yep - the ROM memory test was failing because of those six 'lost' bytes between the end of screen RAM and the expected end of memory. In the real VIC-20, those bytes are never referred to - they exist as RAM, but have essentially slipped down the back of the sofa because the screen doesn't use them. However, the ROM memory test knows they're there, and tests them, and thus got very upset when my precision-engineered memory emulation reported them as Undefined. A two-second change to the initialiser, and the screen now gets 512 bytes of RAM to take its endpoint to $1FFF, which means $C2 gets incremented as expected, and the ROM doesn't then branch to an infinite loop.
Phew!
The first thing I did was fire-up VICE (a very good emulator of a variety of Commodore machines) in VIC-20 mode and had a PEEK at the ROM locations my code was getting stuck at. To my surprise, they matched byte-for-byte - so my ROM image was not broken in some way, and the JMP address was being correctly decoded. A real VIC-20 -does- have this infinite loop in its ROM. So how the hell does it get out of it and finish booting-up?
I wondered if some hardware event took place that would reset the Program Counter to something else - maybe the ROM authors deliberately wanted the boot process to pause here until some other part of the computer was properly awake and fired an interrupt. But at this point in the ROM (which I'd been tracing very carefully as I debugged my emulator) I knew we hadn't enabled interrupts yet, and no timers had been set running either. Maybe the NMI (Non-Maskable Interrupt) was being fired by something - that's a hardware interrupt that overrides whatever else the CPU is doing, but which my emulator hasn't got implemented yet...
Well, I pondered and wondered for a bit, and re-read a lot of documentation about the VIC-20 boot process (including an annotated ROM disassembly) and didn't find anything helpful. So I posted the question to Denial and YakYak in the hope that someone on one of those forums would have a deeper understanding of what was going on. And 'Mike' on Denial did indeed have a cunning insight into the problem:
Mike: "The VIC ends up in this endless loop if it detects that RAM 'ends' before $2000. Normally this would indicate a defect in the built-in RAM. But in your case, there must be a bug in your RAM emulation."
What? A bug? In my memory emulation code?? Impossible!
Well, I went back and checked, of course. There's not actually that much code in the class that handles emulated memory, and it was all working fine. So I dug deeper into the ROM code that does the memory check, and confirmed that the branch into the endless loop is triggered if a certain zero-page location ($C2) contains a value of less than #$20 by the time the test ends, which indicates that somewhere below $2000 the memory failed a test and the counter wasn't incremented. So I re-ran the emulator with memory diagnostics enabled, so I could see which memory locations were being accessed and what their contents were, and sure enough we were falling into the infinite loop because $C2 contained #$1F at the end of the test.
Now we're emulating memory here. There's no real RAM that could fail a test - it's just a C# array of objects, representing the memory location and its type. The type is there so I can tell whether the memory location has been defined as RAM, ROM, Memory-Mapped I/O, or if it hasn't been defined as anything, because there isn't any actual memory present there. In each case, as accesses to the location occur, the type is checked to make sure the access is permitted - so we allow reads and writes to RAM and I/O, reads but NOT writes to ROM, and a read which always returns zero to Undefined (and no writes, of course).
Scanning back through the emulator output, I could see that the memory test ROM code was doing a series of non-destructive writes and reads through the RAM to make sure it was working, but right at the end of the test, as it was checking the screen memory space, the reads were returning zero instead of the test value, and so the test failed and $C2 didn't get incremented. Why would the memory emulation work for all other areas of RAM that were being tested, but not that last little section. Just six bytes were failing. WTF?
Well, after a minute or two, the light dawned. I knew, from experience, that the VIC-20 screen RAM is 506 bytes long, from $1E00 to $1FF9. Whatever documentation you care to look at confirms this, and my memory emulation was precisely assigning that space correctly - as we can see on the sixth allocation row from the diagnostics:
; Memory Block AllocationNo problem there. But wait - that means there are a further six Undefined bytes between $1FFA and $2000. Which will always return zero when they're read...
; 0000 - 00FF RAM (00256 / 0x0100 bytes) BASIC Working Storage
; 0100 - 01FF RAM (00256 / 0x0100 bytes) 6502 Processor Stack
; 0200 - 03FF RAM (00512 / 0x0200 bytes) BASIC Working Storage
; 0400 - 0FFF --- (03072 / 0x0C00 bytes) -- Undefined --
; 1000 - 1DFF RAM (03584 / 0x0E00 bytes) User Memory
; 1E00 - 1FF9 RAM (00506 / 0x01FA bytes) Screen Memory
; 1FFA - 7FFF --- (24582 / 0x6006 bytes) -- Undefined --
; 8000 - 8FFF ROM (04096 / 0x1000 bytes) Character Set Generator
; 9000 - 900F I/O (00016 / 0x0010 bytes) 6561 VIC Registers
; 9010 - 910F --- (00256 / 0x0100 bytes) -- Undefined --
; 9110 - 912F I/O (00032 / 0x0020 bytes) 6522 VIA Registers
; 9130 - 95FF --- (01232 / 0x04D0 bytes) -- Undefined --
; 9600 - 97FF RAM (00512 / 0x0200 bytes) Colour Memory
; 9800 - BFFF --- (10240 / 0x2800 bytes) -- Undefined --
; C000 - DFFF ROM (08192 / 0x2000 bytes) BASIC Interpreter
; E000 - FFFF ROM (08192 / 0x2000 bytes) KERNAL Operating System
Yep - the ROM memory test was failing because of those six 'lost' bytes between the end of screen RAM and the expected end of memory. In the real VIC-20, those bytes are never referred to - they exist as RAM, but have essentially slipped down the back of the sofa because the screen doesn't use them. However, the ROM memory test knows they're there, and tests them, and thus got very upset when my precision-engineered memory emulation reported them as Undefined. A two-second change to the initialiser, and the screen now gets 512 bytes of RAM to take its endpoint to $1FFF, which means $C2 gets incremented as expected, and the ROM doesn't then branch to an infinite loop.
Phew!

1 comments:
A good lesson in implementing emulated hardware... I'm right now gearing up for a similar project (Atari ST emulation), and the I/O memory access to the designated chips scares me more than emulating the CPU - there's less documentation and a lot of implicit knowledge from the original engineers that is now lost in time...
Post a Comment