Saturday, 12 January 2013

In Which Tweaking Continues


I continued to look for ZP entries that could be moved elsewhere yesterday, and found two candidates straight-away - firstly the flag byte I use during debugging which causes the border colour to change during IRQ processing (so I can see how much time the CPU is spending doing all the background tasks it has to do every frame) and secondly the bitmap byte that holds bits set during RESET processing, which tells the OS which memory blocks have RAM in them. Both had an interesting property that meant I could do something fun with them when relocating - that is, move them to unused locations in the 2K Colour RAM area.

This might sound odd at first glance, as you may think that storing stuff in Colour RAM would have an obvious and undesirable side-effect - there would be a two-way clash between different parts of the system, in which unexpected values in Colour RAM would yield display glitches on the screen, and conversely that parts of the screen-handling logic that set screen colours would then overwrite these carefully-set values. However, this is not the case, because the VIC only looks at one half of the Colour RAM, either the upper or lower 1K, depending on where the screen base is. In VIC++ the OS sets the VIC up to use the lower 1K, which means there's a whole 1024 locations above it that are never used - so we can stash things in there without fear of any clash occurring.

But there's a tiny little caveat to this idea, and it stems from a quirk in the hardware design of the VIC-20. You might have noticed that I've carefully avoided using the term 'bytes' when talking about the Colour RAM locations - that's because the VIC itself only recognises values from 0-15 for screen colour values, and that of course means you only need four bits (3-0) to hold those values. Consequently the Commodore engineers saw a way to shave a little cost off the machine by using half-width (4-bit) RAM for the colour memory. In other words, that Colour RAM can only hold values in the 0-15 range, with any higher values (using the upper four bits, 7-4) being lost, or more accurately truncated to just the contents of the four lower bits. For example, storing a value of 15 sets all four bits of the lower nybble (the correct, if little-used nowadays, term for half-a-byte) and can be read back as you'd expect. But set the value to 16, which in binary has bit 4 set and the lower four clear, and when you read it back you get zero - bits 7-4 have been lost, so your bit 4 setting isn't there, and you just get zero back in bits 3-0.

It then gets a tad more interesting when you realise what the designers did with the upper four data lines that aren't connected to bits 7-4 in this half-width RAM. What they did was ... nothing! Those four lines were left 'floating', not connected to anything, and thus subject to a curious condition in which a read from Colour RAM could (and often does) return 'junk' in bits 7-4, where whatever value happens to be left on the data bus after the previous operation could erroneously be mapped over the value coming from the RAM - giving a combination of the actual four lower bits, and some random garbage from the bus in the upper four bits!

The VIC doesn't care about this, as it only uses the lower nybble so junk in the upper nybble is irrelevant. But if you're using that memory for anything other than screen colour data, then as well as remembering that this RAM area will only hold values 0-15, you must also be mindful that you might need to mask anything you read (a simple AND #$0F is all that's needed) in order to get rid of the junk in the upper four bits. This isn't always necessary - if your code is written in such a way that it only ever looks at the lower nybble and ignores the upper four bits, you don't need to do the mask step - and arguably, if you've chosen to use Colour RAM as a storage area in the first place, your code should be written this way anyway.

The upshot is that we're totally free to use spare Colour RAM for storage, provided we recognise that the RAM is only 4 bits wide and can therefore only store a limited range of values (and that bits 7-4 are unreliable and must be ignored). Which brings us to the two ZP bytes I identified which can move out of Zero Page and up into Colour RAM - so here's what I did...

The IRQ Debug Flag byte is dead simple - the debug logic in the IRQ handler just sets the byte to a non-zero value and changes the border colour to denote the fact that the CPU is doing IRQ stuff. The main loop that currently just puts the system into a wait-state is a tight iterative routine that waits for the IRQ flag to be non-zero, and then (after the IRQ handler has finished) resets the flag to zero and puts the border colour back to it's normal state. Since the flag is either zero or any non-zero value, we can shift it to Colour RAM quite safely because 4-bit RAM is just as good as conventional 8-bit RAM for this purpose - provided the non-zero value isn't above 15 (it isn't). As ever there's a tiny speed/space price to pay for moving to Absolute addressing, but two cycles and two bytes is nothing to write home about.

The other byte, which holds the bits indicating where expansion RAM has been detected during the test sequence in RESET, was slightly more tricky because my original design used 5 bits - one each for blocks 0, 1, 2, 3 and 5 (corresponding to the 3K expansion in BLK0, the three 8K areas in BLK1, BLK2 and BLK3, and BLK5 - notionally for cartridge ROM but which VIC++ can map as RAM). It doesn't take a genius to spot that 5 bits won't fit into 4-bit Colour RAM. However, as the OS evolved you'll recall we reached a point where BLK0 became a mandatory requirement for the system, and I'd had it mind for some time to drop the BLK0 bit from the bitmap.

And so I've taken the opportunity to do it now - the RESET logic still uses the original ZP location during the test, and sets the five block bits in it as the memory test progresses, but the OSINIT routine that runs immediately after RESET now checks bit 0 (the BLK0 flag) and halts the system if it's not set. It then shifts that bit out and stashes the resulting four bits in Colour RAM, freeing-up the ZP location for use by something else. The memory count routine, currently only called during the startup-message display (but intended for use at any later point too) needed a little tweak to recognise the fact that there are now only four bits in the bitmap, but I didn't need to add a mask step since the routine only cares about those four bits - so junk in the upper nybble from the floating bus has no effect.

And I think that means we're now ready to tackle the dirty-row table!

No comments:

Post a Comment