Bootstrapping CP/M via simh Apr 2017

The next step is actually more of a giant leap: building a working CP/M system from scratch…

This can’t be done in one go. Let’s start as simply as possible - which is already pretty steep:

The CCP (Console Command Processor) and BDOS (Basic Disk Operating System) are part of the CP/M distribution, and need not be changed other than to assemble the code to reside in a specific address range in memory, i.e. just below our BIOS code.

But first, let’s investigate what bootstrapping really entails:

Fortunately, “loading” is really simple in this case: using special eZ80 instructions, we can simply copy bytes from one area of the eZ80’s 16 MB address space to RAM - there’s no real I/O involved at all, since the disk is virtual and simply stored somewhere in flash memory.

Unfortunately, we’ll have to play by CP/M’s rules and implement everything in terms of 128-byte sectors being “read” from “tracks” and “sectors”. As a disk format, we’ll use something supported by cpmtools - a package which runs under Windows, MacOS, and Linux to create disk images, structured in exactly the way needed by CP/M. This way, we can prepare a disk image to upload into eZ80 flash memory, all from the comfort of a modern laptop.

In terms of a pure retro-Z80 approach, this is definitely cheating a bit. But the goal here is not to go through all the pain of yesteryear - all we’re after is to end up with a working retro setup!

Onwards then. The disk format we’ll use must match the 256 KB size of our flash memory:

diskdef memotech-type50
  seclen 128
  tracks 79
  sectrk 26
  blocksize 1024
  maxdir 64
  skew 1
  boottrk 2
  os 2.2

This corresponds to the following “Disk Parameter Block” definition in CP/M:

dpb:    dw 26  ; SPT - sectors per track
        db 3   ; BSH - block shift factor
        db 7   ; BLM - block mask
        db 0   ; EXM - Extent mask
        dw 248 ; DSM - Storage size (blocks - 1)
        dw 63  ; DRM - Number of directory entries - 1
        db 192 ; AL0 - 1 bit set per directory block
        db 0   ; AL1 - ... 8 more bits
        dw 0   ; CKS - DIR check vector size (DRM+1)/4 (0=fixed disk)
        dw 2   ; OFF - Reserved tracks

So by implementing this disk map in the BIOS on the Z80 side, we’ll have a way to access flash memory as a disk, which has previously been created with cpmtools using this command:

mkfs.cpm -f memotech-type50 $bins disk.img

This places a number of binaries on the “system tracks” of the disk, and formats the rest to appear as an empty directory in CP/M. The two system tracks can hold up to 6.5 KB of code.

The first code to run is RLOAD, the ROM Loader - followed by SLOAD, the System Loader (both yet to be written). The reason these have been split in two will become clear later on:

Here is how everything needs to line up: in RAM, on the (virtual) disk, and in flash memory:

From To Use On disk In flash
$E300 $E37F RLOAD Track 0, sector 0 $000000
$E380 $E3FF SLOAD Track 0, sector 1 $000080
$E400 $EBFF CCP Track 0, sector 2 $000100
$EC00 $F9FF BDOS Track 0, sector 18 $000900
$FA00 $FCFF BIOS Track 1, sector 20 $001700
$FD00 $FFFF BIOS (RAM only) - -

All the Z80 assembly code can be found on GitHub: rload.z80, sload.z80, and minbios.z80.

We still have two puzzles left to solve: 1) how to transform these three files into machine code, and 2) how to actually upload the final disk image to the eZ80’s flash memory.

Part 1 can be solved with simh, a “highly portable, multi-system emulator”, including a superb Z80 emulation by Peter Shorn. This is a luxury and yet another cheat, of course: an accurate, full-scale (and blindingly fast) emulation of a Z80 system which runs many versions of CP/M.

All we need to do is create a submit file, which loads each source file into the simulator, runs a Z80 assembler, and writes out the results to the host again. After that, assembly is a matter of keeping the simulator running in a terminal window and entering “do a” to build everything.

After that, we run the above mkfs.cpm command, and end up with a disk image.

The last piece of the puzzle is how to get this into flash memory. We have PokeMon, but how do we get the data to Mecrisp Forth? Well… given that Folie is used to talk to Forth, we can use its “!send <file>” command to send … ehm, text commands over serial to the Blue Pill.

The missing piece is a conversion from a binary disk image file to text commands we can send to PokeMon, and that’s what bin2fs.go is for: it takes any binary file and turns it into reams and reams of “m“ commands, which transfer 32 bytes of data at a time to speed up the process. The output of “go run bin2fs.go disk.img disk.fs” will be something like this:

5B3AFF60 3AFE0328 3C3EE55B 2100E023 5B1101E0 235B01FF 1F004977 49EDB05B m
21000000 5B110060 3A5B0100 1A0049ED B05B2100 7A3A5B11 017A3A5B 01FF0700 m
497749ED B05B2100 613A5B11 00E4205B 01001900 49EDB0C3 00FA0000 00000000 m

There are a few more details in this process, but basically, we can now erase flash memory and then use “!s disk.fs” to send the complete disk image to PokeMon, which in turn will write it into successive memory locations of flash memory. Then we reset, enter “z t”, and wait…


Bingo, we have the CP/M prompt - it looks like it’s actually working!

(yes, it works - though it took several days to iron out all the wrinkles and squash all the bugs)

Note: Folie’s line-edit mode is not optimal for CP/M. Better to switch to picocom or similar.

No file

But now what? We have a working system, but it’s empty. How do we get applications onto this thing? What about saving stuff? What if we want to configure the board for a certain use?

This bootstrap process is starting to look more and more like an adventure game:


Which of course it is. So let’s figure out a way to add writable / persistent storage to this thing.

Weblog © Jean-Claude Wippler. Generated by Hugo.