Yes, it is finally time to sit down with the mini5HA again. My goal was to figure out how to make a working program for CP/M using its built-in assembler, and I wasn’t going to give up until I had at least a “hello world.”

The mini5HA is set up on my dining room table, in the classiest possible way. Hey, what are you saying about my table? It was free.

Once I got comfortable with using the mini5HA in the previous entry, it was time to get down to business. I had little or no experience with CP/M, so it was time to consult the documentation before going any further.

CP/Mers, Assemble?

The first thing I noticed when reading about CP/M is that there’s a tool called asm which purports to be an assembler, but it failed to launch, complaining first that the boot disk was read-only (it was) and then that the M: drive could not be selected.

What was I trying to assemble, you may ask? I wrote a quick program based off some code that I found on the web. Not knowing how to use ED very well, and not particularly enjoying the Intel format, I was extremely careful not to make any typos. I will share the “final” program later in this entry, because as you’re about to see, I made a lot of mistakes.

The M: drive, presumably the RAM (“Memory”) disk, indeed could not be selected. I couldn’t even format it. I was really confused about this, but I didn’t have much else to go on. I assumed that the Japanese hobbyists had been using those alternative Amstrad tools, rather than what came with the machine, because of something like this.

Eventually, I looked at the documentation. It turns out that I was typing this:

A> ASM HELLO.ASM

But in the case of ASM specifically, that extension to the filename is not necessary. ASM – and as far as I can tell, only ASM – interprets everything after the period as a command-line switch. What I was actually telling it to do was:

  1. Take the source file from drive A
  2. Put the resulting assembled hex file on drive S
  3. Put the resulting listing (“print”) file on drive M.

Two out of three of those drives don’t exist, so of course it was angry! After I changed it to just

A> ASM HELLO

I ended up successfully assembling the source file, and then getting a HELLO.HEX and a HELLO.PRN file on the disk. Sweet! But did it work?

It Didn’t Work!

The HEX file isn’t quite a binary. It’s the Intel Hex format, which is an ASCII representation of the hex bytes of the executable.

To load the hex file and run it, you can use the DDT (“Dynamic Debugging Tool”) interactive debugger, which is very well-featured1. I think it has more gadgets than MSDOS DEBUG, to be honest.

When I ran it with G, however, nothing happened. The program would just immediately exit. I tried a few different things in the program to try and figure out what was going on, but ultimately settled on the program listing you’ll read later.

When I was tinkering around with ways to end the program, it would sometimes even get stuck in an infinite loop and I’d have to power-cycle the machine to stop it.

That part I mentioned up above about “ASCII representation” tripped me up - I kept trying to use the CP/M DUMP utility to read the HEX file as a binary, and wondering why “HELLO BUNGO” wasn’t showing up in the resulting hex dump. It was in the PRN printout:

The PRN file clearly shows that the string ("DB") directive generated some code.

Blog friend Chartreuse set me right about the proper use of DUMP, and also explained to me that DDT doesn’t actually know where I have set my program to start in memory.

Debugging my Debugging

That ORG 100h statement only mattered to the assembler and (later) the program loader, but the debugger didn’t know to go there. It’s starting execution of the program at PC = $0000 , as clearly indicated on the screen before I started my debugging session:

It clearly says "PC 0000," but if you can't read, like me, there's a red arrow pointing to it. Full disclosure: the red arrow was added in post-production.

Instead, I had to type this to set the program counter to $100 and begin execution:

- G100

HELLO BUNGO from the debugger, thanks to the g100 command.

Success. But how do I make a COM file out of this? The answer is the confusingly-named LOAD, which takes in a hex file and does the dance required to produce a COM executable.

The program has been LOADed, and now you can just type HELLO12 at the prompt to get HELLO BUNGO.

The Program

Okay, let’s go over the program and analyze it. I cobbled this together from the assembler manual and the aforelinked “please help me with CP/M hello world” forum post, which didn’t seem to have a solid conclusion, but got me down the right road.

WRITESTR    EQU 9H
BDOS        EQU 5H
ORG 100H
    MVI C, WRITESTR
    LXI D, HELLO
    CALL BDOS
    RST 0H
HELLO:
    DB 'HELLO BUNGO$'
    END

Pretty simple program, even if you don’t like the Intel mnemonics and register names. We’ll go over it line by line here:

WRITESTR    EQU 9H
BDOS        EQU 5H

This sets up defines for where the program can find the BDOS syscall and the WRITESTR function number to invoke. EQU is the rough equivalent of #define in C, except obviously without macro magic.

Like in MS-DOS, calling operating-system services in CP/M mostly consists of setting registers to indicate the function ($9 ) and the arguments (the address to the string,) and then calling into the operating system to handle it.

Please note that there are two sets of system calls: BIOS (the hardware abstraction layer) and BDOS (the disk-drive and “OS” services.) There aren’t a huge number of syscalls available from the CP/M standard, but I’m sure NEC has snuck a bunch in there as well to make their lives a little easier.

ORG 100H

The ORG directive tells the assembler where it should expect the machine code that is generated after the directive to be placed in memory at run-time. Since everything before this is just defines, no code has been generated yet. CP/M has a “transient program area” (TPA) that starts at $100 , and so that’s where we will assume our program will end up.

    MVI C, WRITESTR
    LXI D, HELLO
    CALL BDOS

Here we are moving the function number for WRITESTR into the “C” register, followed by the starting address of the string we want to print. Then we call BDOS, which reads these registers and calls the code for writing a string to the screen. Knowing print routines as I do, this is probably lengthy and incredibly complex, and luckily I’m not the guy who has to write that part.

    RST 0H

We are done. RST (reset) does basically a warm-restart of the CP/M command interpreter. To the human, it seems like we’ve returned to the operating system.

Now for the last chunk:

HELLO:
    DB 'HELLO BUNGO$'
    END

This defines a label HELLO so we don’t have to figure out the exact address of where the string will end up in the resulting executable. That’s the assembler’s job. DB is define byte, and lets us write our string. We terminate that string with $ because that’s what WRITESTR expects.

The CP/M assembler manual says that END is optional, but if it is included, it must be the last instruction in the file. So I added it for luck, and that’s where it goes.

It’s Mr. ED

ED is the built-in text editor that comes with CP/M. It’s designed to work like an old-school line editor. Some of you may be familiar with ed and sed on Unix, which have a common ancestry with CP/M’s ED.

Chartreuse was again a huge help, and linked me to the I Love ED on CP/M blog post, which has a great command reference.

I ended up creating new files every time (hence HELLO12 in the pictures) because I didn’t know how to load the “old” file contents into the buffer in order to edit it, and I also had a lot of problems going back and “fixing” a line if I made a typo and then hit enter. Unfortunately, I only figured out how to operate ED after I got a program that worked.

Here’s the commands in ED that I used most often:

  • E: Exit and save file to disk
  • I: Insert at current line, entering editor mode
  • Ctrl-Z to exit editor mode
  • {Line}:K to kill a line (delete it)
  • #A: load the file into the buffer from disk
  • #T: display all lines of the buffer (if at start)
  • B: go to start of file
  • -B: go to end of file
  • KI: Delete and replace a line, entering editor mode

Conclusion

My journey with CP/M is just beginning, but I’m overjoyed that the mini5HA comes with good (or at least existent) low-level development tools, unlike most computers that I’ve gotten in the past. I was having no luck at all trying to get Nevada Edit and another assembler onto floppy disks for the mini5HA2, and now I might not even need them if I can just deal with the cozy rustic charm of ED.

Obviously there’s a big gap between “hello world” and anything else, but I hope at some point to be able to make at least a character-mode version of some game for this computer. Who knows? Maybe I’ll end up doing a port of CP/M to the Soggy-1000.

  1. It has a very useful D (Dump) command which I am currently using to explore the mini5HA’s memory space, to try and figure out how to dump the ROM from software. Hey, I’m too lazy to discharge a tube and completely dismantle this machine in order to get the motherboard out. 

  2. I do now have a good 22disk format definition, so maybe there is still hope for writing a floppy with a working Zork. Stay tuned…