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:
- decide how to structure the system tracks for booting
- implement a flash loader to copy things to RAM
- implement a minimal BIOS which does console I/O and reads the virtual flash disk
- compile this assembly code using a Z80 assembler
- build a CP/M disk image which contains the CCP BDOS, and our newly crafted BIOS
- convert the resulting bytes to instructions we can send to PokeMon
- connect to the eZ80 and write this data into its flash memory
- reset and enter terminal mode…
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:
- set up the external RAM and built-in RAM mapping
- copy a section of flash to RAM, to load CP/M into upper RAM memory
- implement the BIOS cold boot, which has to initialise the serial port
- implement the BIOS warm boot, which needs to re-load the CCP from disk (i.e. flash)
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 end
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:
bins='-b RLOAD.COM -b SLOAD.COM -b BDOS22.COM -b MINBIOS.COM' 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:
RLOAD sets up Z80 memory so that
$20000..20FFFFbecomes the default 64K map, copies the System Loader to address
$20E380..20E3FF, and starts it up in Z80 mode
SLOAD copies the CCP, BDOS, and BIOS from flash to
$E400..FDFF, and jumps to the BIOS
$FA00cold start address to kick off the standard CP/M power-up sequence
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)||-||-|
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
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
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
is for: it takes any binary file and turns it into reams and reams of “
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 [...etc...]
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.
A>dir No file A>
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:
YOU ARE IN A MAZE OF TWISTY LITTLE PASSAGES, ALL ALIKE…
Which of course it is. So let’s figure out a way to add writable / persistent storage to this thing.