The last piece of the puzzle is to make all the parts line up and work together. This turns out to be quite involved. A large part is due to the “virtualness” of this whole CP/M setup:

Fortunately, this problem is not new. Every “system implementor” who has built a CP/M system from scratch in the past, has had to deal with the same issue. Another good thing, is that there is a very detailed “CP/M Operating System Manual” with information about all the steps needed to “bring up” CP/M (a quick search should bring up several hits).

System tracks

The standard layout for CP/M disks on 8" SSSD media, is to reserve the first (i.e. outer) two tracks for “The System”. Then comes the disk directory, then the actual file storage. These last two are fully managed by CP/M, but they still have to be initialised correctly on all fresh floppy disks (including virtual ones).

With 26 sectors per track and 128 bytes per sector, two tracks can hold 6.5 KB of data. The CP/M core (i.e. CCP+BDOS) is 5.5 KB. Our (tiny) BIOS is 0.5 KB. That leaves us at most 0.5 KB, i.e. 512 bytes (4 sectors) for storing bootstrap code to bring everything up.

Two bootstraps

There are different ways to implement the bootstrap mechanism. I’m going to use a very traditional 2-level approach:

At this point, all of CP/M will be in high memory (CCP, BDOS, and BIOS), exactly where it should be, and the normal CP/M “cold boot” starts. This concludes the bootstrap process.

Power-up boot

This is the very first code executed by the (virtual) Z80 after power-up. The question is: where does it come from and where should it be stored?

On a “real” Z80, the answer is: in ROM, starting at address 0x0000. Because that’s where the Z80 starts fetching its very first instructions. As part of the bootstrap, there must then be a way to disable the ROM and map low memory onto RAM.

On a virtual Z80, things are much simpler. We could copy some constant bytes into RAM before starting the emulation. But that’d be silly, as the code would then use a Z80 I/O instruction to load disk 0 / track 0 / sector 0 into low memory. We can sidestep all that and directly load the first (virtual) disk sector into RAM, in C++. Or, to be precise:

    FlashWear disk;
    disk.init();, memPtr(&context, 0x0000));

When the emulator starts, it will run the system bootstrap we just loaded from the disk.

System bootstrap

So this is a second bootstrap. It needs to be very small since it must fit entirely within one disk sector. Furthermore, it cannot rely on any other code being present in RAM. That’s the point of bootstrapping: to load fresh code into RAM which takes control of the system.

Here is a simple system bootstrap in Z80 assembly language:

; Cold bootstrap for z80 emulator

CCP     equ 0E800h
BIOS    equ CCP + 1600h

        org 0000h

boot:	ld de,greet
        in a,(3)                ; display greeting

        ld a,0                  ; first drive
        ld b,48                 ; read CCP + BDOS + BIOS into memory
        ld de,1                 ; skip first sector on first track
        ld hl,CCP               ; starting load address
	in a,(4)                ; disk read request

        jp BIOS                 ; jump to bios cold start

greet:  db 13,10,'[JeeLabs Retro Z80]',13,10,0

        ds 007Fh-$
        db 0                    ; so generated code is exactly 128 bytes


Since the emulator provides a convenient multi-sector read system call via the in a,(4) instruction, the size of this bootstrap code is only a few dozen bytes. And because there’s plenty of room anyway, this bootstrap also prints a little startup greeting on the console.

The startup disk

The proper layout of an 8" SSSD CP/M startup disk is as follows:

Location Contents
track 0, sector 0 the above system bootstrap
track 0, sectors 1 .. 25 first part of the CP/M system
track 1, sectors 0 .. 22 second part of the CP/M system
track 1, sectors 23 .. 25 currently unused

The disk must also have a valid “disk directory” area, even if there are no files on it yet:

Location Contents
track 2, sectors 0 .. 15 filled with 0xE5 bytes

The rest of the disk content is ignored. It will normally get overwritten before use.

Final setup

All the parts have now been identified. We need a disk with a system bootstrap in the first sector, followed by the CCP, BDOS, and BIOS, and the disk must be set up to contain a valid directory. What has not been described yet, though, is how to generate these parts.

This is where some form of external help is required. We need to be able to run an assembler which takes the Z80 assembly code and turns it into binary code.

There are several ways to do this, such as:

  1. find a working CP/M setup, build your new system there (as well as the separate system loader), and figure out a way to transfer the resulting binary files back out
  2. use an existing CP/M emulator (such as simh or yaze, and build the files there
  3. get hold of one of many available “cross assemblers”, i.e. an assembler for Z80 mnemonics, which runs on a different system (DOS, Windows, MacOS, Linux, etc)
  4. assemble the code by hand, looking up all the codes (tedious, but it can be done)
  5. write your own assembler in your favourite language (it’s not as hard as it sounds)

I’ve used various approaches in the past, but usually 2) or 3) above are the easiest to do.

The last part of this hurdle is to get the data onto our virtual disk, i.e. into the proper location of the F407’s internal flash memory. This is not very different from getting the compiled emulator firmware itself into flash memory. We just need to figure out what goes where, and what the corresponding addresses are in flash.