• Please review our updated Terms and Rules here

Guidelines for writing software for CP/M?

nockieboy

Experienced Member
Joined
May 12, 2017
Messages
261
Location
UK - Kent
Hi everyone,

Apologies if that has been asked or answered elsewhere, but I've search the interweb, skim-read a couple of CP/M books and can't find a concise answer to my question.

I've recently built a single-board computer running CP/M 2.2 on a Z80, mostly as an education in digital electronics but also as an excuse to learn Z80 assembly. What I'd really like to do right now is write my own CP/M programs. I have SLR Z80ASM on the system (which can compile straight to COM file) and the usual software for CP/M 2.2, along with TASM that I'm using on the PC to assemble the monitor ROM program etc, so these are both at my disposal.

What I really need though is a brief overview of what is required to write a basic program that I can run as a file in CP/M. My understanding so far is that the code should start at 0100h, the start of the TPA. But that's about the limit of my knowledge. I know there are routines that can be called in BDOS and CPM, but I'm not sure what is available, where it is and what its requirements are.

So, really, an example of a CP/M 'Hello World!' program would be ideal - as would any links to reference material or suggestions where I could look myself for this sort of info.

Any help would be appreciated!

Thanks :D
 
Hi,

As this is your first post, welcome to VCFED.

Most of what you need can be found at http://www.seasip.info/Cpm/index.html. Look for "BDOS". I would suggest starting here
http://www.seasip.info/Cpm/bdos.html To start with.

Basically:

1. Set up your own stack and stack pointer.

2. Set up register DE to point to your string. Don't forget to terminate your string with '$'.

3. Call BDOS function 9.

4. Exit your program. Jump to location 0, call BDOS function 0, or use RST 0.

Just watch which BDOS calls you are using and compatibility with CP/M 2.2.

Ask more questions if you need to.

Dave
 
Last edited:
Personally, I'd keep things simple and use the DRI-provided tools to start--ASM, LOAD and DDT. While using the Z80 instruction set may be convenient, there's little benefit to using it for simple tasks--and it's considerably more complex for a beginner. Many commercial programs were written in 8080, not Z80 code to appeal to the widest possible group of 8-bit CP/M users. Remember that the Intel 8085 existed at the same time as the Zilog Z80.
 
Here is a link to the CPM BDOS calls (if you don't have them). http://www.gaby.de/cpm/manuals/archive/cpm22htm/ch5.htm The BDOS calls do much of the interface work for you. You do not have to know port numbers etal. I use WordStar for my editor. EDLIN will work but it takes a little extra thinking. I'm not familiar with VEDIT. But you need to have the editor save in the text mode not a document mode. Once you have your file say test.asm you need to run ASM test. If there are errors, fix them else run LOAD which will produce the COM file. It's not rocket science, but can be confusing until you've struggled thru a few times. Try it and ask questions. Good Luck, Mike
 
Last edited:
Easiest thing to do is to start with a "Hello World" test using ASM. It's best if you have experience with assembly language in general.

First edit and create a program file called "HELLO.ASM":

Code:
        ORG     100H

BDOS    EQU     0005H                   ; LOCATION OF BDOS ENTRY POINT
BOOT    EQU     0000H                   ; LOCATION OF BOOT REQUEST

START:
        MVI     C,9                     ; BDOS REQUEST 9 - PRINT STRING
        LXI     D,MESSAGE               ; OUR STRING TO PRING
        CALL    BDOS
        JMP     BOOT                    ; EXIT TO CP/M

MESSAGE:
        DB      13,10,'HELLO WORLD',13,10,'$'

        END     START




To assemble and run the program (assuming that your system is on drive A:

Code:
A>ASM HELLO.AAZ
CP/M ASSEMBLER - VER 2.0
011B
000H USE FACTOR
END OF ASSEMBLY
A>LOAD HELLO

FIRST ADDRESS 0100
LAST  ADDRESS 011A
BYTES READ    001B
RECORDS WRITTEN 01

A>HELLO

HELLO WORLD
A>

Just that simple!
 
Firstly, thanks everyone for the replies - there's plenty for me to be getting on with. Thanks Chuck for the code example - that's precisely what I needed to get me up and running. I'm certainly no assembler expert, but I'm learning! :)

I wrote the first part of a multi-part article for 300 Baud Magazine on ASM for CP/M. The magazine is no longer available, but they released the PDFs for free:

http://filedump.glitchwrks.com/zines/300_baud/300_Baud_03.pdf

I stuck to the DRI tools included with CP/M 2.2. I believe I was using VEDIT as my programmer's editor at the time.

Thanks for the link to the 300 Baud magazines, glitch - they're really interesting! I'm assuming 300 Baud never got to issue 4? :(
 
Error compiling source

Error compiling source

Hmm... I'm trying the example provided by Chuck(G) above and getting the following error:

Code:
A>ASM HELLO.AAZ
CP/M ASSEMBLER - VER 2.0
NO SOURCE FILE PRESENT

A>

This is despite there being an .AAZ file present and correctly named. I've tried writing it in ED and still get the same error (I'm using the AltairZ80 emulator, so I initially tried importing a Notepad file.) Pretty much the same error when I use ZASM80 too, although that indicates line 1 as being the cause of the error (line 1 is just ORG 100H)...? :huh:
 
Have you called the file HELLO.ASM or HELLO.AAZ?

The file should be called HELLO.ASM. The AAZ are the parameters to assemble the source file against. You have told the assembler that the source file HELLO.ASM is on drive A. Is this actually correct?

Checking the TDL ZASM manual, I can't see an ORG directive (all of the pseudo operations begin with a '.'), so I suspect the error that has been returned is correct in this case! The equivalent to ORG is .LOC in this assembler...

Do not keep switching assemblers - they are not compatible.

The question is, do you want to learn 8080 or Z80 mnemonics? You originally stated you wanted to learn Z80 assembly, so my recommendation would be to learn the Zilog mnemonics and pick a compatible assembler (preferably a macro assembler). Z80ASM seems good. If you want to learn to program for CP/M, then 8080 mnemonics would be a good choice, so pick your assembler accordingly (ASM or MAC will be suitable). If I can find some time today I will write a Z80 HELLO WORLD for you.

Dave
 
Last edited:
This would be my attempt for the SLR Z80ASM assembler.

Name the file "HELLO.Z80" and assemble it with the command:

A>Z80ASM HELLO

This should take source file HELLO.Z80 and produce HELLO.COM.

Run it with:

A>HELLO

I have not run this (we don't have Z80 systems at work...). Any problems or questions, let me know and I will try and sort them out for you.

Regards,

Dave

Code:
	TITLE	Hello World!

; Define some ASCII control characters.
;
CR	EQU	0DH
LF	EQU	0AH

; Define the CP/M terminating character for BDOS function 9 (WRITESTR).
;
TERM	EQU	'$'

; Define some useful CP/M BDOS function numbers.
;
TERMCPM	 EQU	0
WRITESTR EQU	9

; Define my local stack size.
;
STACK	EQU	128

; Define a useful macro to 'wrap' the call to BDOS.
;
BDOS	MACRO	FUNC

	LD	C,FUNC	; The desired BDOS function.
	CALL	5	; Entry point into CP/M BDOS.

	ENDM

; This is the main program...
;
START::

	; Initialise the local stack to be
	; at the end of the program as loaded
	; into the TPA.

	LD	HL,PROGEND	; Adress of end of program.
	ADD	HL,STACK	; Advance to allow space for stack.
	LD	SP,HL		; Set up the Stack Pointer.

	; Display our message.
	;
	LD	DE,HELLO	; DE points to the message.
	BDOS	WRITESTR	; Display the message.

	; Bye bye...
	;
	BDOS	TERMCPM		; Exit back to CP/M.

	; Should never get here!

HELLO:	DB	CR,LF,"Hello World!",CR,LF,TERM

PROGEND:

	END	START
 
Last edited:
Have you called the file HELLO.ASM or HELLO.AAZ?

The file should be called HELLO.ASM. The AAZ are the parameters to assemble the source file against. You have told the assembler that the source file HELLO.ASM is on drive A. Is this actually correct?

Ah, thanks Dave - that's the problem! :rolleyes: I'd called the assembly source HELLO.AAZ... was slightly confused by the ASM syntax!! ;)

It has now compiled happily and I've got a working 'hello world!' COM file! :D

Checking the TDL ZASM manual, I can't see an ORG directive (all of the pseudo operations begin with a '.'), so I suspect the error that has been returned is correct in this case! The equivalent to ORG is .LOC in this assembler...

Do not keep switching assemblers - they are not compatible.

Yes, I'm trying to stick with what I know (or am learning), which in my case is the TASM assembler as that's what I'm using (via DOSBOX) to compile my monitor ROM code for the SBC.

The question is, do you want to learn 8080 or Z80 mnemonics? You originally stated you wanted to learn Z80 assembly, so my recommendation would be to learn the Zilog mnemonics and pick a compatible assembler (preferably a macro assembler). Z80ASM seems good. If you want to learn to program for CP/M, then 8080 mnemonics would be a good choice, so pick your assembler accordingly (ASM or MAC will be suitable). If I can find some time today I will write a Z80 HELLO WORLD for you.

Dave

Well, I (currently) have no interest whatsoever in the 8080 (why would I when I have a Z80?) ;) I grew up in the 80's with an Amstrad CPC464 and dabbled with assembly back then (though got nowhere near as far as I have now), and have now built a Z80-based computer, so Z80 assembly is the natural choice for me. NOTE: I'm not trying to start an 8080/Z80 war, I know the Z80 shares commands with the 8080 and I'm sure the 8080 was a capable processor in its own right, I'm just more familiar with the Z80. :)

Also, thanks for the Z80ASM code sample you just posted while I was writing this! My reply probably won't get through moderation for a good few hours, but I'll definitely be using Z80ASM in future as it makes more sense - compiles straight to COM and looks like it has some interesting features (the macro you've declared is interesting - don't know if TASM supports those (haven't looked) but could make the code much prettier!)
 
It works!

It works!

OK, I entered the above program via ED (whoa.. talk about having to learn keyboard shortcuts!) and tried compiling. The first error was that Z80ASM expects the file to end in .Z80 instead of .ASM - that was easily fixed. I also had to make a small addition to the assembly to get it to work as it seems ADD doesn't like adding a value to HL, so I loaded STACK into DE first then added DE to HL. (I picked DE at random - let me know if it's likely to break anything!)

Code:
        LD      HL,PROGEND              ; ADDRESS OF THE END OF PROGRAM.
        LD      DE,STACK                ; LOAD STACK SIZE INTO DE.
        ADD     HL,DE                   ; ADVANCE TO ALLOW SPACE FOR STACK.
        LD      SP,HL                   ; SET UP THE STACK POINTER.

The example works perfectly - thanks very much! :D
 
You may find this reference quite useful:

http://www.cpm.z80.de/manuals/cpm22-m.pdf

(especially section 5.1 onwards) as it avoids the confusion of different versions of CP/M (e.g. MP/M and 8086 variants) and concentrates on CP/M 2.2 (albeit with 8080 instead of Z80 mnemonics).

It covers where and how the command line is stored (for passing to a transient program) and how the first two file references on the command line are handled by CP/M itself on your behalf. There are also useful examples that you should be able to follow (perhaps with a bit of help).

I notice you are this side of the pond in the UK. Just bear with the moderators for a short while, they will soon let you post without moderation shortly...

Dave
 
Last edited:
Hmm... I'm trying the example provided by Chuck(G) above and getting the following error:

Code:
A>ASM HELLO.AAZ
CP/M ASSEMBLER - VER 2.0
NO SOURCE FILE PRESENT

A>

This is despite there being an .AAZ file present and correctly named. I've tried writing it in ED and still get the same error (I'm using the AltairZ80 emulator, so I initially tried importing a Notepad file.) Pretty much the same error when I use ZASM80 too, although that indicates line 1 as being the cause of the error (line 1 is just ORG 100H)...? :huh:

You'll note that I said this:

First edit and create a program file called "HELLO.ASM":

No--ASM assumes file extensions of .ASM .HEX and .LST and uses the stuff after the dot to indicate where to find or create files. So HELLO.AAZ says that you'll find HELLO.ASM on drive A, HELLO.HEX will be created on drive A and there will be no listing file created. It's in the manual. :) AFAIK, ASM is the only program that uses this convention.
 
>>> Z80 assembly is the natural choice for me.

That's fine, I just wanted to confirm what it was that you were trying to achieve...

>>> Z80ASM expects the file to end in .Z80 instead of .ASM.

Yep, I did say that...

>>> ADD doesn't like adding a value to HL, so I loaded STACK into DE first then added DE to HL. (I picked DE at random - let me know if it's likely to break anything!)

My original program that I sketched out on paper had exactly that - but I decided to check with a document on the internet and changed my mind! What you did is exactly what I would have done... You can use any register you like within your own program. It is only necessary to be careful what registers get 'trashed' across a BDOS call (hence the reason I usually save/restore all my registers in use across a BDOS call and only optimise that if I need performance (see an example shortly)).

>>> The macro you've declared is interesting.

Yes, I try to make my programs readable and maintainable. I find using MACROS helps (providing you don't go too mad with them).

What do you want to know next? How to handle the command line? See code excerpt below (again, with a government health warning that I can't try it out).

Code:
Add the following definitions:

; This is where CP/M stores the command line for you.
CMDLINE	EQU	80H

; This is the BDOS function to display the character in the 'E' register.
C_WRITE	EQU	2

After the code to display Hello World - but before we terminate (!) enter the following code:

	LD	DE,COM1		; Display the first half of our message.
	BDOS	WRITESTR

	; Display the command line (if present).

	LD	HL,CMDLINE	; Start of buffer.
	LD	A,(HL)		; Length of command line.
	INC	HL		; Move to first real character of command line.
	OR	A		; Set flags.
	JR	Z,COMDONE	; No command line to process.

	; A command line was entered.

	LD	B,A		; Get to correct Z80 register for a DJNZ.

COMMORE:

	LD	E,(HL)		; Pick up the first/next character from the command line.
	INC	HL		; Bump command line pointer.
	PUSH	BC		; Save my registers.
	PUSH	HL		; -- ditto --
	BDOS	C_WRITE		; Output the character in the 'E' register.
	POP	HL		; Recover my registers.
	POP	BC		; -- ditto --
	DJNZ	COMMORE		; Repeat as necessary.

COMDONE:

	LD	DE,COM2		; Display the second half of our message.
	BDOS	WRITESTR

Finally, place the following strings at the end.

COM1:	DB	"You entered """,TERM

COM2:	DB	""" on the command line.",CR,LF,TERM

Next - file handling? Or something else?

Enjoy,

Dave

PS: If you get a few more posts under your belt, the mods will let you run free...
 
Last edited:
That's awesome, thanks Dave!

Apologies for the huge delay replying - I've been busy with sorting out hardware and other improvements to the SBC, but I'm back to CP/M again now and working on trying to write a couple of programs using the framework you've kindly provided.

My SBC runs at either 4 or 8 MHz (software selectable), so it would be nice to have a little COM program I can run to show me what speed the SBC is running at. I store a simple ASCII '4' or '8' at address CFFF to denote whether the system is currently running at 4 or 8 MHz, so it should be simple enough for me to write a program that grabs that character and prints it to the terminal. Or at least so you'd think. :confused:

Here's what I've got so far:

Code:
TITLE SHOWSPD

; An attempt at creating a functioning and useful CP/M program.
; This program will show you what speed the SBC is running at by
; retrieving the value at #CFFF.

; DEFINE SOME ASCII CONTROL CHARACTERS.
;
CR				EQU		0DH
LF				EQU		0AH

; DEFINE THE CP/M TERMINATING CHARACTER FOR BDOS FUNCTION 9 (WRITESTR).
;
TERM			EQU		'$'

; DEFINE SOME USEFUL CP/M BDOS FUNCTION NUMBERS.
;
TERMCPM		EQU		0
C_WRITE 		EQU 		2		; This is the BDOS function to display the character in the 'E' register.
WRITESTR		EQU		9		; Write the string pointed to by DE until TERM char is reached

; DEFINE MY LOCAL STACK SIZE.
;
STACK		EQU		128

; DEFINE A USEFUL MACRO TO 'WRAP' THE CALL TO BDOS.
;
BDOS	MACRO	FUNC

			LD		C,FUNC
			CALL	        5

		ENDM

; THIS IS THE MAIN PROGRAM...
;
PROGENT::
	; INITIALISE THE LOCAL STACK TO BE
	; AT THE END OF THE PROGRAM AS LOADED
	; INTO THE TPA.

	LD	HL,PROGEND		; ADDRESS OF THE END OF PROGRAM.
	LD	DE,STACK		; LOAD STACK SIZE INTO DE.
	ADD	HL,DE			; ADVANCE TO ALLOW SPACE FOR STACK.
	LD	SP,HL			; SET UP THE STACK POINTER.

	; ACTUAL PROGRAM CODE GOES HERE
	LD	DE,RESP1		; DE POINTS TO THE MESSAGE.
	BDOS	WRITESTR	        ; DISPLAY THE MESSAGE.
	LD	A,(0CFFFH)         ; LOAD CLOCK SPEED CHAR INTO A
	LD	E,A                    ; MOVE IT INTO E
	BDOS	C_WRITE            ; WRITE THE CHAR
	LD	DE,RESP2            ; DISPLAY REST OF MESSAGE
	BDOS	WRITESTR	

	; BYE BYE...
	;
	BDOS	TERMCPM		; EXIT BACK TO CP/M.

	; DATA
RESP1:	
	DB	"System is running at ",TERM

RESP2:
	DB	" MHz",TERM

PROGEND:

	END

Problem is, when I run the program in CP/M all I get is this:

`System is running at MHz`

Any ideas what I'm doing wrong? (The value at #CFFF is either #34 or #38.)
 
Hello,

There's a specific book that I've found quite useful, and I see that Amazon UK list second hand copies for about £13.50:

Mastering CP/M by Alan R Miller (Sybex 1983).

Fairly concerned with the CP/M system generally, and organised towards building up a substantial macro library. From what you say, the main problem would be that it's primarily 8080 rather than Z80, but it makes lots of reference to both sets of mnemonics.

Apart from that, the best way I've found to get into assembly is to find the source code for existing progs. There are many such on the web. Find something that does some/part of what you're interested in. Study it, get the hang of what it does, and how it works. Then start making changes/additions to it. Gradually you'll take over the whole prog, and you'll get very familiar with it, as you do the edit/compile/run cycle over and over again. If the prog WAS working, and you change something, and now it's NOT working, then YOU've done something, and it's just the bit you changed, so don't make too many changes at once!!

Geoff
 
One thought I had, looking at your program, is that 0CFFFH seems sort of low in memory. Is that part of the CCP? I'm assuming this is CP/M 2.2... Maybe run DDT and make sure 0CFFFH contains what you think it does. But if 0CFFFH is part of the CCP, then that will get overwritten by DDT.

While it appears as though no character is being output between RESP1 and RESP2 messages, it could be that some "garbage" (non-printable) character is being output, if 0CFFFH does not contain what you think. That is difficult to prove unless your terminal has a mode where it prints all characters including control/invalid characters. One thing to try is maybe grab (0CFFFH) before making any BDOS calls and save it on your stack, in case this location is getting overwritten by BDOS.

Basically, go back and prove that 0CFFFH does actually contain what you think at the time a program gets executed.
 
Further to the above, if you've got the correct data, if it IS a printable character then it may show as such, but that may NOT be a lot of help.

You need an extra process, which will take the HEX char, and convert it into a three digit decimal number, and display that. That number may be more useful anyway? I seem to remember that the Miller book mentioned above shows a macro for doing this.

Geoff
 
Back
Top