Please note that this is all based on intelligent guesses made when looking through the code. It is a very incomplete description, and there may certainly be lots of errors in it.
The boot menu, or the Selector, as I will call it, is what normally greets you whenever you turn on the power to your C-one.
It normally lives inside the BOOT.BIN-file in the root directory of your CF-card and takes care of reading the icon images from the core directories, displaying them on the screen and letting you choose one to load. But how is all this happening? What is really going on on the inside of the machine?
Turns out there is a neat little boot environment or system inside it. The whole thing is written in 6502 assembler and runs on a virtual CPU that is set up inside the 1K30 (The "smaller" ACEX FPGA next to the IDE-connectors and SIMM-slot).
The BOOT.BIN file is only the code (and font) for the actual Selector menu system, there is also code in a ROM used to load the Selector in the first place, or any other cores you might have on your CF-card.
The virtual CPU inside this environment can access at least 1 MB of memory in the SIMM-slot, the first 64kB is in the CPUs own address space and is directly addressable. The rest of it is accessed through a few new CPU instructions that have been added. They can store the accumulator to RAM, or load the accumulator from RAM, and work using three byte pointer somewhere on the zero page, where the first byte indicates which "page" of 64 KiB RAM, and the two following is the address in ordinary little endian.
Here is sort of some kind of memory map. It may be partially incorrect and is certainly incomplete. Most of the areas in higher memory here apply to the Selector. Other cores may very well use the RAM differently, allthough the ROM routines that load the cores are always the same.
$0000 - $00FF: Zero page, variables go here
$0100 - $01FF: Stack
$0400 : Input of joystick port A
$0401 : Input of joystick port B
$0402 : Input of scancodes (set 2) from the PS/2 keyboard
$0403 : Reading bit 1 here indicates there is data from the keyboard to be read,
while writing to bit 0 and 1 appears to do something with the IEC bus?
$0440 : Appears to be used to set the address of the "screen map". Put #$10 here
to place it at $10000.
$0480 : Used for communicating with the rest of the board somehow,
both for setting up the screen and configuring the other FPGAs.
$04C0 : Somehow used to freeze the boot system. Used when the 1K100 has been loaded and it is ready to run.
$0600 - : Load FPGA data for the 1K100 here to configure it
$0A00 - : Load FPGA data for the Extender here to configure it
$2000 - $xxxx: This is where `BOOT.BIN` files will loaded to from disk to be executed.
$2000 : JMP configure
$2003 : JMP menu
$F800 - $F812: Six JMP-instructions to jump to different ROM routines for IDE, FAT and icon-copy.
$F803 - $FFF9: ROM-code loaded up from the Instant On Flash at start sits here.
Contains FAT and IDE routines and code to load the `BOOT.BIN` file and start it.
$FFFA - $FFFB: CPU warmstart vector
$FFFC - $FFFD: CPU reset vector
$FFFE - $FFFF: CPU IRQ vector (not used)
$10000 - $11FFFF: Here we initialize the screen by writing some
sort of map of where to read the actual screen data for each scanline.
$18000 - $1E400: Area used to load an icon image temporary. 160x160 pixels.
It is loaded here before we print text on it and then copy it
to the screen buffer.
$20000 - : Here we temporarily place the contents of the first CRT-file found in the core
directory, to extract a name from it, while building the menu, or the name of the first PRG-file.
$28000 - $2E400: Load the Marker image here. 160x160 pixels.
Data is swapped between screen and here when
the marker moves around (to save the contents of the screen under it).
Screen buffer. The BOOT.BIN sets up the screen to be 640x480, 8bit. Every byte in the buffer sets color according to %bbbgggrr. Actual screen area starts at $80000, the rest is outside the viewable area and you should probably not put any data there.
$7BA00 - $7FFFF: Invisible area above the screen. 28 raster lines.
$80000 - $CAFFF: Visible screen buffer. 480 raster lines. Write data here and it will show up on the screen.
$CB000 - $CD2FF: Invisible area below the screen. 14 raster lines.
$CD300 - $CD7FF: VSYNC area, to make 525 lines in total.
$CD800 - $11B9FF: ?
At $F800 we have what I will call ROM. It appears to be a part of the 1K30 configuration that is loaded from the Instant On Board flash ROM when the system powers on.
First it contains six JMP-instructions for different routines in the ROM. Since the addresses of these six instructions are hardcoded and defined, we always know where to JSR to get at the routines. Then we have the IDE- and FAT-routines. And finally, bootcode.
The boot code contains two different entry points, RESET and WARMSTART. The addresses for these two locations are specified in the ROM at locations $FFFA and up, the CPU vectors.
RESET and WARMSTART will start at pretty much the same place, but RESET will disable interrupts and put #$FF in the stack pointer before starting to run at the same place WARMSTART does.
After that, the code tries to initiate the four IDE devices that may be connected. It tries them one by one, and as soon as it finds a disk on one of the channels, it will try to get a FAT partition and file system from it. If anything goes wrong, it will lock up in an infinite loop, doing nothing at all.
As soon as that has succeeded, it will try to load BOOT.BIN from the root directory and write it to address $2000 in page $0. If it finds the file, it sets a little flag to remember that it has been loaded and simply jumps to address $2000, the "Init-vector" of the BOOT.BIN.
BOOT.BIN init-functionThe BOOT.BIN has two JMP vectors at the start. At $2000 is a JMP config, and at $2003 is a JMP menu.
Immediately after the ROM loads the Selector BOOT.BIN to RAM, it will jump to $2000. The Selector will initiate the screen by setting up a sort of scan line map at $10000, with an address for each scan line that points to the display data in RAM at page $7 and up. See the "memory map" above.
Once the screen has been initiated, Selector will use the FAT-routines of the ROM to load the SELECTBG.RAW background image to the display RAM at $80000 and the MARKER.RAW to $28000.
It will then copy any old zero page (256 bytes) to $1000 and RTS to the ROM.
The ROM code will then start configuring the other cores.
First the the Extender will be loaded with the EXTENDER.RBF file from the root of the CF-card. If an extender is present and this is not done, there will be no video signal to the monitor, since the Extender board sits "in the way" of it, so to speak.
The default EXTENDER.RBF will configure the FPGA on the Extender to simply just let the 16 video out bits from the 1K30 pass right through to the DAC and VGA-port.
To load the Extender FPGA, a few bytes of commands are written to $0480 and then the RBF file is loaded to $0A00- at page $0.
If the EXTENDER.RBF file is not found, the board will skip that and proceed to configure the 1K100.
The 1K100 is then configured using the SUPPORT.RBF file. This time we load the file to $0600 on page $0.
The SUPPORT.RBF is a core that can be used to write data to the SRAM on the 65816 CPU card in the CPU card slot.
Now, when that is done, the system will go look for a file called EMU.BIN and try to load it to $80000. I have no idéa what that might do, and it would collide with the screen buffer that Selector has set up, but it might be used by some other core, or the old boot system. I don't know.
Then we load, if it exist and if the 1K100 was initiated with the SUPPORT.RBF, a file called SUPPORT.BIN. This is sent through the 1K100 to be stored in the SRAM mentioned above, by writing it to $0600- of page $0.
The SUPPORT.BIN is usable as a simple way of preloading ROM-data and other stuff a core might need to the SRAM before the actual core is loaded to the 1K100. It can also be used if we want to reprogram the 1K30. Since all this is running on the 1K30, we cant change it directly, but we can put the config in the SRAM, and then have a small bit of logic in the real 1K100-core to configure the 1K30 with it.
Now we load the actual core to the 1K100, the one that contains the actual machine that we want to synthesise. The file is called TARGET.RBF, since it contains our "target" machine, and it is loaded pretty much like before, by writing the file to $0600- and putting some bytes in $0480.
Now all files have been loaded. Note that any of these files are optional, and different cores will use different files in different ways. The boot system is flexible and allows you to put any core straight at the root of the CF, not just the Selector.
If the Selector is at the root you will get the nice menu for choosing other cores. The Selector uses the standard EXTENDER.RBF (to let video through if we have the extender board), SUPPORT.RBF (to allow it to write to SRAM), and the BOOT.BIN (which contain the actual menu program).
At this point, if a BOOT.BIN was loaded, the CPU will be instructed to JSR $2003 to let the BOOT.BIN carry on with whatever it like to do. When returning from the menu-routine, the boot code will jump back up and start loading everything up again, starting with the BOOT.BIN and so on, but in this case from whatever directory on the CF the Selector has set up for it. That way, the system will load and configure from a new core directory and start a new system.
If there was no BOOT.BIN, the boot system will freeze to allow the newly loaded 1K100 or Extender to do whatever it likes to do. The freeze is done by writing to $04C0.
Now that the boot and loading process is out of the way, lets have a closer look at the Selector.
As you may recall, the Selector usually lives in the BOOT.BIN file in the root of the CF-card and is the first thing loaded by the boot ROM. Once loaded, the routing at $2000 is executed to give the menu a chance to set up the screen.
This is done by setting $0440 to #$10 (pointer to the table?) and writing a table at $10000. This table has 2048 entries. Probably because it was easier to do that way, but I suspect that it need not really be that large.
Every entry in the table is four bytes large and seems to describe a scan- or rasterline on the screen. The first three bytes is a memory pointer (low byte, high byte and page byte) to the place in the RAM where we like to have the screen data read. The fourth byte is some kind of indicator on what purpose this scan line has. $82 seems to be display area, while $80 is area above and below that, and $00 may be vertical synch. $40 is also something and comes after the v-synch.
Once the table is ready, #$81 is written to $0480, and then #$02 is written to $0480.
We then load the background image to out newly set up screen buffer at $80000 and load the cursor icon, or marker, to $28000 for later use.
Then we RTS to the boot ROM.
The boot ROM finishes loading stuff up as per above, and then passes controll back to boot.bin at $2003. This runs the menu.
The first thing it does it to look through all the directories in C1DCORES. Or at least the 12 first that it can find.
As it goes along, it will count them. For each directory found, it will load the SELECT.RAW file (which is the cores icon image) temporarily to $18000. This is a 160x160 pixel icon with 8 bits per pixel.
If no icon is found, it will instead clear out the same memory area with zeroes.
Then the SELECT.TXT is loaded and printed on top of the icon using a font that is included with the BOOT.BIN.
If no SELECT.TXT is found, it will instead look for the first file it finds ending with .CRT, a cartridge image file for the C64. The FPGA64 core can read these files, but is limited to using only the first CHIP section in them, so only very simple carts will work.
A cart-file stores the name of the software on the cart at position $0020 in the file, terminated by a zero. If a cart-file is found, the entire file is loaded to $20000, and the name at $20020 is printed on the bottom row of the icon.
If no cart-file was found, we instead try to locat a .PRG file, since FPGA64 can load (some of) thease too. In this case, there is no name inside the file, so instead of loading the file, we grab the filename, store it on $20020 and call the same routine that could do the cart-name printing.
Then, the original Selector will copy the icon to the screen buffer at the correct position depending on what number the core counter is on. I have however written a modified Selector that will go on and try to write the name of a .TAP file in the same way as the .PRG file. This is for use with the ZX One core.
The font embedded in the BOOT.BIN has 8x8 pixel characters, but it is written out expanded to 8x16.
A maximum of 19 x 9 characters will be written to the icon if a SELECT.TXT is found. Filenames on the other hand is written to the last line of the icon, and may therefore be at most 19 characters. Of course, the only way to get that many charaters is if the name comes from a CRT-file, as the FAT-routines does not handle long file names.
Linebreaks can be either #$0D or #$0D #$0A. Text will automatically linebreak if more than 19 characters are on a single row, and any text after row 9 anything left will be discarded.
The 8x16 pixel characters are written to the 160x160 pixel icon positioned four pixels to the left and four pixels down from the top. Then it puts one pixel space between each row of characters.
Once the icon is ready, it is copied to the screen buffer and we proceed with the next directory, until there are no more, or we have done 12 of them.
The number of cores done is stored on the zeropage, and we also have a cariable there to keep track of which core the cursor is currently over. We set that to the number of cores divided by 2 (in my modified version it is set to 0 instead).
The marker that shows what core is selected sits in memory at $28000. To display the marker, we copy any bytes of it that is none zero to screen RAM at the correct position. At the same time, though, to avoid having to reload the icon from disk when we want to move the cursor away from it, any changed byte is copied from screen RAM to the marker at $28000. In other words, we let tha marker and the icon in screen RAM trade places.
When it is time to move to another icon, we do the exact same thing again, which will restore the icon in screen RAM and the marker at $28000.
After the marker has been drawn on the initial position, we enter the key loop.
The key loop simply checks the six least significant bits of joystick A and B by reading from $0400 and $0401 respectively. We save these to variables on the zero page and compare the new read with an old saved value on every loop. As soon as we see a change on the joystick input from our stored value, we store this new value instead and load a counter with $FF. This counter is then decremented by one on every loop where the joystick is still in the same position. As soon as the counter reaches 0 we have a steady signal.
This is done for both stick A and B. When we have a good joystick reading, we will shift through the bits to see what action to take. If left, we decrement the "selected core" variable by one. If right, we increment. If up we subtract four, if down we add four. Then we go back up and redraw the marker by first drawing it to the old position, and then do the same drawing routine to the new position. If we have fire button, we run the Enter routine as below.
At the same time as checking the joysticks, the loop will also check the keyboard by reading bit 1 from $0403. As soon as we get a one there, we will read $0402 to see what "make" scancode was sent. The scancodes we get are untranslated set 2 codes from the keyboard. We compare them to check for the keys we are interested in. Up, down, left, right, enter and escape. The directional buttons work just like the joystick up above.
The Enter routine is what will set us up to load the selected core.
The first thing it does is to restore the icon on the screen. Then if gets the number of the selected core and enters the directory of the core, makes a copy of the zero page to $1000, sets something to $0480 that may have to do with the screenmode and returns with a RTS to the boot code.
When we return to the boot code, as you might remember from above, the boot code will try to reconfig everything just as it did the first time around. This time, however, it will no longer be in the root directory. Instead, the active directory in the directory pointers will be the directory of the selected core, and that is what will be loaded.
The core in the directory may of course be another copy of the selector, with yet another level of core directories, or it can be any normal core.
If we instead push escape, the menu will attempt to go to the parent directory instead, as long as the directory depth is not already 1. If so, we are at the root, and will just go back to the keyloop.
There you have it. A very simplified description of how the boot works.
If you read this far, have a cookie.