Friday, 2 March 2012

In Which We Finalise The Memory Configuration


Just before we get into the bit where we finish-off the memory test stuff, Muttley (an eagle-eyed subscriber) clued me in on this little byte-saver:

.critfail
DEC .SCRNCOL ; [5] set VIC register for red border
BNE * ; [3/3] spinloop (VICE spits a JAM exception if we use HLT)

In case you can't spot the difference, we've altered the code in .critfail so that we just decrement .SCRNCOL instead of doing the LDA/STA we used to. If we set the border colour to cyan instead of green way back at the beginning (i.e. #$03 instead of #$05) then we can simply decrement the VIC colour register to #$02 for red, saving 2 bytes and 1 cycle. That saved cycle doesn't really count for much here, given that we're essentially telling the user that Something Bad Happened and we're going to spinloop forever - but 2 bytes is worthwhile. Thanks Muttley!

Now, onward - the memory test is finished, and unless we hit a critical failure we've proved that all the RAM in the system is working, initialised it all to zero, and set a bitmap to show which blocks are populated. So all that's left to do is define a few ZP locations as markers for where things start and end, because the rest of the OS is going to need those pieces of information when we start doing higher-level stuff. Here's the code:

.initexp
LDX #$10 ; [2] screen start page
STX .SCREEN ; [3] ZP set screen start page
LDX #$12 ; [2] user code space start page (unexpanded or 8K+)
STX .USERCODE ; [3] ZP set user code space start page
LDY #$20 ; [2] top of symbol table page (unexpanded)
LDA .EXPBITS ; [3] ZP get expansion RAM bitmap
BEQ .noexp ; [2/3] no expansion RAM
LSR ; [2] shift 3K bit out
BCC .no3k ; [2/3] skip next instruction if no 3K
LDX #$04 ; [2] change user code space start page (3K)
STX .USERCODE ; [3] ZP reset user code space start page
.no3k
LDX #$02 ; [2] index pointer into table of pages
.nextbit
LSR ; [2] shift 8K BLK1/2/3 bits out
BCC .skipbit ; [2/3] skip the block when we get a clear bit
LDY .pagetab+3,X ; [4] get top of user code space page for this 8K block
.skipbit
DEX ; [2] decrement index pointer
BPL .nextbit ; [3/2] loop for next bit
LSR ; [2] shift 8K $A000 bit out
BCC .noexp ; [2/3] no change if we get a clear bit
LDY #$C0 ; [2] top of symbol table page ($A000)
.noexp
STY .SYMTAB ; [3] ZP set top of symbol table page
DEY ; [2] top of user code space page (one down from top of symbol table)
STY .USERTOP ; [3] ZP set top of user code space page
; 42 bytes 26 cycles (minimum, unexpanded) or 80 cycles (worst-case, fully expanded)

The objective of this routine is to define settings for four key memory variables - .SCREEN, .USERCODE, .USERTOP and .SYMTAB. These represent the address hi-bytes for where the display memory starts, the start of user code space, the end of user code space, and end of the symbol table. The OS knows where the fixed 'holes' in the memory space are - they're where the Character Generator ROM is, and where the VIC and VIA chips appear (I include the colour RAM nybble block in this space too, because although it's RAM it can't be used for anything other than display colour data). So in order to be able to manage the available RAM as a single unit, it'll also need to know where user code can go, where the screen will be in the middle of it (so it can work around it) and where the 'reserved' chunk of RAM for the language Symbol Table* will be.

The screen will be fixed to start at $1000, so we set that first. In practice we may decide to allow the screen to move around, subject to VIC hardware constraints, but the baseline configuration will put it in a specific location regardless of how much RAM there is - unlike the Commodore ROM, which moves it depending on the population of the 8K block(s). We then assume that space for user code will start 1K up from the screen at $1200 and that there is no expansion RAM fitted at all, which would put the end of RAM (thus, the end of the Symbol Table) at $2000. If that assumption holds true, and no expansion bits are set in .EXPBITS, then we drop through to the bottom of the routine to set the Symbol Table end-point, and the end of user code space one page (256 bytes) below it.

If the 3K expansion bit is set, we drop the start of user code space down to $0400 to take advantage of it. Incidentally, this is the only part of the routine I don't like, because I have to re-set the .USERCODE variable after having set it initially - originally I used a little trick here, by stashing X in the SP register and recovering it later to set the variable once at the end, but then I decided to make this routine re-usable (so you can arbitrarily change the memory configuration later by adjusting the bitmap in .EXPBITS and calling it) and that meant I couldn't use SP any more because whilst it's technically 'free' during POST, it would certainly not be if this code was called at any other point.

For scenarios in which any of the 8K blocks have RAM in them, we just LSR the bits out in a little loop and re-access .pagetab to get the address of the end of the block. We don't care if there are holes, because the OS will reference .EXPBITS later to work around them in the same way as the 'fixed' holes - whatever ends-up in Y will be the effective 'top' of memory regardless. This also holds true for the $A000 expansion area, except that we have to set the top of memory to $C000 explicitly due to the way the page table works (i.e. the end of the $6000 block is $8000, so the next 'end-point' looks like $A000 itself because that's the next page during the test stage, which breaks the logic here).

We finish with Y holding whatever the highest page is, and fall into the same code as described previously to set the top-of-memory variables. The OS now has all the information it needs to manage however much RAM is installed, even if there are holes in it. Simples!

We can now proceed to initialise the rest of the system, having established the memory parameters...




* We'll discuss what the Symbol Table is for, and what goes in it, at a later date.

No comments:

Post a Comment