Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

CP/M Z-80 Assembler Help

152 views
Skip to first unread message

Nate Brazil

unread,
Apr 14, 2006, 12:27:38 PM4/14/06
to
Greetings!

I'm attempting to learn Z-80 Assembler language in a CP/M+ operating system
environment (Commodore 128 in CP/M mode). I'm a beginning programmer so I'm
writing lots of non-working code. Is this an appropriate place to perhaps
post my source code and receive help? I've looked for Z-80 specific boards
and am not finding any, and the assembler language programming board seems
to be rather inactive and not specific enough to the Zilog-80 chip.

My code is VERY basic at this time; mostly register experiments. Right now
I'm simply trying to write a .com file that will display what is in the
accumuator to the screen. (I preload the accumulator with decimal 127 as a
test seed, but all I'm getting back is 100 and a hung system??)

I could really use some help with this unusual and driving obsession of mine
to try to learn Z80 assembler programming. :-)

Nate


Tom Lake

unread,
Apr 14, 2006, 1:13:44 PM4/14/06
to
> My code is VERY basic at this time; mostly register experiments. Right
> now I'm simply trying to write a .com file that will display what is in
> the accumuator to the screen. (I preload the accumulator with decimal 127
> as a test seed, but all I'm getting back is 100 and a hung system??)

Do you end every program with a RET instruction? You need to in order
to return to the calling program (in your case, the C> prompt).

Tom Lake


Terry Gulczynski

unread,
Apr 14, 2006, 1:51:43 PM4/14/06
to

"Nate Brazil" <nateb...@hotmail.com> wrote in message
news:L6mdnW3sPKHnUKLZ...@comcast.com...

> My code is VERY basic at this time; mostly register experiments.
> Right now I'm simply trying to write a .com file that will display
> what is in the accumuator to the screen. (I preload the accumulator
> with decimal 127 as a test seed, but all I'm getting back is 100 and
> a hung system??)

Decimal 127 is the ASCII code for DELETE. You may not see anything
obvious on the screen when you get the rest of the program working.
You might consider using an actual displayable character.

Also, if you're using BDOS calls to display data, you need to load the
display character in the 'E' register rather than the accumulator.

Example:

START:
LD E,'G'
LD C,2
CALL 0005H
RET

This will display the letter 'G' on your terminal and exit back to
CP/M.


Terry


Nate Brazil

unread,
Apr 14, 2006, 2:03:28 PM4/14/06
to

"Tom Lake" <tl...@twcny.rr.com> wrote in message
news:cLQ%f.47545$Da7....@twister.nyroc.rr.com...

Actually I don't, but my code usually seems to work anyway. In those cases
where it doesn't, RET doesn't prevent my system from hanging in those
situations where it does get hung - I already tried this with my 'Hello
World' program. (Which I finally did get working, btw... Yeahh!!)

I do end with an END statement though. Perhaps this generates a RET during
compile.

Here is a strange note... Sometimes my programs will hang or my prompt will
become corrupt (e.g. 35S> instead of C>) unless I add this to the end of my
code:

JP Done
Done: END

But this is not always the case.

Here's my current program that does not work. It's simply supposed to
display what's in the Accumulator. Remember that I'm an absolute beginner:

ORG 0100H
BDOS EQU 05H ; Call 5 to run BDOS functions
LD A,137 ; Seed accumulator with test value
CP 200 ; Compare accumulator to 200
JP P,S200 ; Jump if A >=200
JP C100 ; otherwise jump to C100
S200: SUB 200 ; Subtract 200 from Accumulator
LD C,2 ; C holds BDOS routine to run (2
is 'Output 1 char to console')
LD E,'2' ; E holds what will be output.
'2' in this case.
CALL BDOS ; Print a '2'
JP TENS ; Now let's display the 10s digit
C100: CP 100 ;Compare accumulator to 100
JP P,S100 ; Jump if A >= 100
JP TENS ; Otherwise let's display the 10s
digit
S100: SUB 100 ; Subtract 100 from Accumulator
LD C,2 ; Load C with BDOS routine to run
LD E,'1' ; Load E with byte to display
CALL BDOS ; and run routine to output char to
screen
TENS LD B,0 ; init counter for 10's (register B)
LOOP CP 10 ; See result of A minus 10
JP C,PRT10S ; If carry bit is set, A is less than
10 so print 10's
SUB 10 ; otherwise subtract 10 from A,
count++
INC B
JP LOOP ; Loop until A is less than 10
PRT10S: PUSH AF ; Store what's in A
LD A,B ; Load A with B so we can...
ADD A,'0' ; Convert to ASCII
LD B,A ; put result back into B
POP AF ; reload A with our number
LD E,B ; Load 10's count into E
LD C,2 ; Load C with routine to display
char on screen
CALL BDOS ; print 10's digit
ADD A,'0' ; Convert whats left in A to ASCII
LD E,A ; Load data to show (1's) into E
LD C,2 ; Load C with routine to display
char on screen
CALL BDOS ; print 1's
END

I can't figure out why this wont work. All I get is 100. Am I somehow
overwriting my Accumulator?

Nate


Nate Brazil

unread,
Apr 14, 2006, 2:06:48 PM4/14/06
to

"Terry Gulczynski" <tgcons...@cfl.rr.com> wrote in message
news:PiR%f.139704$Fw6.1...@tornado.tampabay.rr.com...

Terry,
Thanks for your help. I load A with 127 but use additional code to examine
A to show what is in the hundreds, tens and ones location. I'm also loading
data to display in E.

I posted my code a minute ago in this thread - if you have time, please take
a glance at it. I hope it displays correctly.

Again, thanks for helping!

Nate


Tom Lake

unread,
Apr 14, 2006, 4:07:20 PM4/14/06
to

> I do end with an END statement though. Perhaps this generates a RET
> during
> compile.

No, END is only an instruction to the assembler that that's the end of the
source.

Depending what was in memory before, a RET instruction could be somewhere
after your program and make it seem to work OK sometimes.

Tom Lake


pe...@nospam.demon.co.uk

unread,
Apr 14, 2006, 5:10:12 PM4/14/06
to
In article <vbidnTZnIvxtfqLZ...@comcast.com>
nateb...@hotmail.com "Nate Brazil" writes:

> "Tom Lake" <tl...@twcny.rr.com> wrote in message
> news:cLQ%f.47545$Da7....@twister.nyroc.rr.com...

[..]


> > Do you end every program with a RET instruction? You need to in order
> > to return to the calling program (in your case, the C> prompt).
> >
> > Tom Lake
>
> Actually I don't, but my code usually seems to work anyway. In those cases

Well it shouldn't! You might just have been lucky fluking into a
JP 0 or whatever. You _must_ hand back to CP/M cleanly.

> where it doesn't, RET doesn't prevent my system from hanging in those
> situations where it does get hung - I already tried this with my 'Hello
> World' program. (Which I finally did get working, btw... Yeahh!!)
>
> I do end with an END statement though. Perhaps this generates a RET during
> compile.

I'd be surprised; END is usually simply a directive to the
assembler to stop assembling (which some assemblers bitch about
if it's not there).

> Here is a strange note... Sometimes my programs will hang or my prompt will
> become corrupt (e.g. 35S> instead of C>) unless I add this to the end of my
> code:
>
> JP Done
> Done: END
>
> But this is not always the case.

Perhaps because whatever happens to be in memory after your
program is loaded is sometimes serendipitous and sometimes sends
the IP off to wherever. You should always have a RET or call
function 0 (terminate), like e.g.

ld c, 0
call BDOS (or JP BDOS)

or even a JP 0 (to the warm boot entry point).

> Here's my current program that does not work. It's simply supposed to
> display what's in the Accumulator. Remember that I'm an absolute beginner:
>
> ORG 0100H
> BDOS EQU 05H ; Call 5 to run BDOS functions
> LD A,137 ; Seed accumulator with test value
> CP 200 ; Compare accumulator to 200

[rest snipped - your tabs are horrible :-( ]

Here's your code again, somewhat tidied with a few extra bits:

BDOS EQU 05H ; Call 5 to run BDOS functions

ORG 100H

LD A,137 ; Seed accumulator with test value
CP 200 ; Compare accumulator to 200
JP P,S200 ; Jump if A >=200
JP C100 ; otherwise jump to C100
S200: SUB 200 ; Subtract 200 from Accumulator
LD C,2 ; C holds BDOS routine to run (2 is 'Output 1 char to console')
LD E,'2' ; E holds what will be output. '2' in this case.

-> push af


CALL BDOS ; Print a '2'

-> pop af


JP TENS ; Now let's display the 10s digit
C100: CP 100 ; Compare accumulator to 100
JP P,S100 ; Jump if A >= 100
JP TENS ; Otherwise let's display the 10s digit
S100: SUB 100 ; Subtract 100 from Accumulator
LD C,2 ; Load C with BDOS routine to run
LD E,'1' ; Load E with byte to display

-> push af


CALL BDOS ; and run routine to output char to screen

-> pop af


TENS LD B,0 ; init counter for 10's (register B)
LOOP CP 10 ; See result of A minus 10
JP C,PRT10S ; If carry bit is set, A is less than 10 so print 10's
SUB 10 ; otherwise subtract 10 from A, count++
INC B
JP LOOP ; Loop until A is less than 10
PRT10S: PUSH AF ; Store what's in A
LD A,B ; Load A with B so we can...
ADD A,'0' ; Convert to ASCII
LD B,A ; put result back into B
POP AF ; reload A with our number
LD E,B ; Load 10's count into E
LD C,2 ; Load C with routine to display char on screen

-> push af


CALL BDOS ; print 10's digit

-> pop af


ADD A,'0' ; Convert whats left in A to ASCII
LD E,A ; Load data to show (1's) into E
LD C,2 ; Load C with routine to display char on screen

->(1) push af


CALL BDOS ; print 1's

->(1) pop af

-> ret ; IMPORTANT!
END

This works here, writing 137 (not 127) on the screen.

;I can't figure out why this wont work. All I get is 100. Am I somehow
;overwriting my Accumulator?

Not so much overwriting, but A is getting clobbered across the
BDOS calls. Well, perhaps not clobbered, but conventionally
reset to zero if the BDOS call is successful or to 0FFh if not.
The important changes I made were simply to preserve it across
those calls; not an ideal solution -- it would be better to have
your 127 in a data byte, and load it as required. Note too that
the last puh/pop pair(1) are not strictly needed, since at this
point you no longer care what A contains.

One other small point: much better to use JR rather than JP where
you can as the code is smaller and quicker to execute. There are
a few extra JP condition opcodes that JR doesn't have but the
common ones (C, NC, Z, NZ) are all OK. The big limitation is
that the JR displacement is a signed byte, so you can only jump
to a label +/-127 bytes away (or -125/+128 or some such due to
the origin of the original jump after assembling).

Hope that helps,
Pete
--
demon.ip.support.pc is for discussion of IP-related issues
using plain DOS software - See the Which Group FAQ:-
ftp://ftp.demon.co.uk/pub/doc/general/whichgrp.faq

"We have not inherited the earth from our ancestors,
we have borrowed it from our descendants."

[check header entries if trying to reply by email]

Bruce Morgen

unread,
Apr 14, 2006, 9:10:52 PM4/14/06
to
pe...@nospam.demon.co.uk wrote:

...or even an RST 0, which is
a one-byte version of CALL 0,
which also works because the
CCP's stack is reestablished
on warm boots.

NNB


................................................................
Posted via TITANnews - Uncensored Newsgroups Access
>>>> at http://www.TitanNews.com <<<<
-=Every Newsgroup - Anonymous, UNCENSORED, BROADBAND Downloads=-

French Luser

unread,
Apr 15, 2006, 8:19:35 AM4/15/06
to
"Nate Brazil" wrote:

> I'm attempting to learn Z-80 Assembler language in a CP/M+ operating
> system environment (Commodore 128 in CP/M mode).

Ho, boy! So many things to say, and I only have a few minutes at this
cybercafe!

Here are a few thoughts, unsorted:

Z-80 assembler is much harder (in my humble opinion) than the 8080
because of the alternate set of registers. Also, at the beginning, there
were 4 variants of the mnemonics used widely, so it was a mess until
MicroShit's M80 became the standard Z-80 assembler.

Since you mention CP/M Plus, you should absolutely get a copy of
"The Amstrad CP/M Plus" by David Powys-Lybbe and Andrew
R.M. Clarke (of the defunct CP/M User Group (UK)). This is
the best book ever written for CP/M Plus, and it contains several
Z-80 subroutines that will be especially useful for you.

When you are writing in assembly language, you are either
dealing directly with the hardware, or with a software layer
(the operating system) standardising the behaviour of the hardware.
In your case, this is simpler (and more portable), since you use
CP/M Plus. CP/M Plus is a very good OS. Be sure to get a
copy of the 4 manuals:

- "CP/M Plus Programmer's Guide"
- "CP/M Plus User's Guide"
- "CP/M Plus System Guide"
- "Programmer's Utility Guide for CP/M"

(This last one is the manual of MAC, the standard 8080
macro assembler for CP/M. As said, the 8080 is simpler.
For example, M80 is written in 8080...)

However, if you insist on managing the Z-80,
there were also 2 books by Lance Levanthal:
- "Z-80 assembly language programming"
- "Z-80 subroutines"
(I don't remember the exact titles or publisher: do a search)

(By the way, this reminds me that the standard book
was "Z-80 programming" by Zaks, but I never liked it.)

With those 7 books, you should have enough doc.
For instance, I never had the "Zilog Z-80 assembly
language programming manual" in my 8-bits days.

Now, a quick look at the tools.

The assembler alone is useless. You need several programs,
each one interacting with the others. Under CP/M 2.2,
those were ED (the context editor), MAC (the 8080 macro-assembler),
LOAD (the loader), and SID (the debugger). MAC and SID,
being improvements upon ASM and DDT, were sold separately
for serious assembly language programmers.

Under CP/M Plus, you can either use ED or WS (ED has the
big advantage of being only 10K big: I used it during several
years. It has many built-in commands that are very useful
for assembly language programmers.), M80 (the standard
Z-80 assembler, but you will need to use LINK-80 (better
than L80) each time before debugging your programs...,
and ZSID (M80 has a switch to generate SYM files
to be used by Digital Research ZSID, since MicroShit
never had a Z-80 debugguer (MicroShit was developing
its CP/M stuff using emulators running on DEC VAXes
under Unix, while Digital Research was really using
S-100 Bus microcomputers under CP/M...).)

Another idea: you could get all the Intel HEX files that
I uploaded on the comp.os.cpm Newsgroup (using
Google -- Groups -- Advanced search), and try to
disassemble them. One of them is DUMPASC,
which displays the contents of a file in ASCII.
(I think that you learn more when disassembling
than when tinkering at random, since, by definition,
you don't know what you have to learn. By disassembling
programs already debuggued, you concentrate on the
HOW this program is built this way, rather than
WHY this program is not working...)

Phew! This will be all. Print this text, find all the books
listed, disassemble all the CP/M COMmand files
less than 2K that you will be able to find, then
come back and ask us interesting questions.

Good luck! Me too, I learned alone (in a foreign
language), thanks to the 2 books by Alan Miller
- "8080 assembly language programming"
- "Mastering CP/M"
(Those 2 books deal with 8080 and macros.)

Yours Sincerely,
"French Luser"

CBFalconer

unread,
Apr 15, 2006, 5:48:05 PM4/15/06
to
French Luser wrote:
> "Nate Brazil" wrote:
>
>> I'm attempting to learn Z-80 Assembler language in a CP/M+ operating
>> system environment (Commodore 128 in CP/M mode).
>
... snip ...

>
> Under CP/M Plus, you can either use ED or WS (ED has the
> big advantage of being only 10K big: I used it during several
> years. It has many built-in commands that are very useful
> for assembly language programmers.), M80 (the standard
> Z-80 assembler, but you will need to use LINK-80 (better
> than L80) each time before debugging your programs...,
> and ZSID (M80 has a switch to generate SYM files
> to be used by Digital Research ZSID, since MicroShit
> never had a Z-80 debugguer (MicroShit was developing
> its CP/M stuff using emulators running on DEC VAXes
> under Unix, while Digital Research was really using
> S-100 Bus microcomputers under CP/M...).)

Ignore M80. Get SLRMAC, or Z80ASM, with SLRLNK or SLRLNK+, from
SLR systems. For a debugger, get DDTZ, available on my page in
source and assembled form. The data format output by M80 is very
poor, SLR does much better.

<http://cbfalconer.home.att.net/download/cpm/ddtz.zip>

--
"If you want to post a followup via groups.google.com, don't use
the broken "Reply" link at the bottom of the article. Click on
"show options" at the top of the article, then click on the
"Reply" at the bottom of the article headers." - Keith Thompson
More details at: <http://cfaj.freeshell.org/google/>
Also see <http://www.safalra.com/special/googlegroupsreply/>


Nate Brazil

unread,
Apr 15, 2006, 8:38:04 PM4/15/06
to

<pe...@nospam.demon.co.uk> wrote in message
news:114504...@nospam.demon.co.uk...

> In article <vbidnTZnIvxtfqLZ...@comcast.com>
> nateb...@hotmail.com "Nate Brazil" writes:
>

[snip...]

> Not so much overwriting, but A is getting clobbered across the
> BDOS calls. Well, perhaps not clobbered, but conventionally
> reset to zero if the BDOS call is successful or to 0FFh if not.
> The important changes I made were simply to preserve it across
> those calls; not an ideal solution -- it would be better to have
> your 127 in a data byte, and load it as required. Note too that
> the last puh/pop pair(1) are not strictly needed, since at this
> point you no longer care what A contains.
>
> One other small point: much better to use JR rather than JP where
> you can as the code is smaller and quicker to execute. There are
> a few extra JP condition opcodes that JR doesn't have but the
> common ones (C, NC, Z, NZ) are all OK. The big limitation is
> that the JR displacement is a signed byte, so you can only jump
> to a label +/-127 bytes away (or -125/+128 or some such due to
> the origin of the original jump after assembling).
>
> Hope that helps,
> Pete
> --
> demon.ip.support.pc is for discussion of IP-related issues
> using plain DOS software - See the Which Group FAQ:-
> ftp://ftp.demon.co.uk/pub/doc/general/whichgrp.faq
>
> "We have not inherited the earth from our ancestors,
> we have borrowed it from our descendants."
>
> [check header entries if trying to reply by email]

Thanks for your help Pete! Although my program still has some logic bugs in
it, the pushing of my Accumulator before calling BDOS definitely helped! My
program is now almost complete. Right now I load the accumulator with a 7
but my output is 27. I've traced it and everything looks correct, but
there's got to be something going on to cause that extra 2 to be showing up.

Again, thanks!

Nate


Nate Brazil

unread,
Apr 15, 2006, 8:39:50 PM4/15/06
to
[snip...]

Thanks everyone for your help! I've learned much since starting this
discussion. I appreciate your patience in working with this newbie to CP/M
Assemlber!

Nate


Jack Crenshaw

unread,
Apr 15, 2006, 9:17:51 PM4/15/06
to
I would start with a _MUCH_ simpler program. Perhaps you did that
first, but if not ...

My first program was something like

LD A, 3
LD B, 2
ADD B
RET

Hey, you did say you wanted to experiment with registers.

To test the software, load it and single-step through it, using DDT.

Jack

Lee Hart

unread,
Apr 15, 2006, 10:46:52 PM4/15/06
to
Nate Brazil wrote:
> I'm attempting to learn Z-80 Assembler language in a CP/M+ operating
> system environment (Commodore 128 in CP/M mode). I'm a beginning
> programmer so I'm writing lots of non-working code.

When I was learning 8080 assembler, I used the little applications that
came with Alan Bomberger's Write-Hand-Man as test cases. These are
little 1.5k programs that do simple things like a notepad, rolodex file,
calculator, DIR listing, etc. First, I figured out how to assemble,
load, and run the examples as-is. Then, I read the assembler until I
could figure out what it was doing. Then I made changes, assembled,
loaded, and tested them. This proved much easier than trying to write
new programs from scratch.

You could do the same thing for the Z80. Find some very small simple
programs, and modify them.
--
Ring the bells that still can ring
Forget the perfect offering
There is a crack in everything
That's how the light gets in -- Leonard Cohen
--
Lee A. Hart, 814 8th Ave N, Sartell MN 56377, leeahart_at_earthlink.net

pe...@nospam.demon.co.uk

unread,
Apr 16, 2006, 2:23:22 AM4/16/06
to
In article <Fq6dnSX3J6F...@comcast.com>
nateb...@hotmail.com "Nate Brazil" writes:
[..]

>
> Thanks for your help Pete! Although my program still has some logic bugs in
> it, the pushing of my Accumulator before calling BDOS definitely helped! My
> program is now almost complete. Right now I load the accumulator with a 7
> but my output is 27. I've traced it and everything looks correct, but
> there's got to be something going on to cause that extra 2 to be showing up.

It's your use of 'JP P,xxx':

ld a, 7
cp 200

will evaluate 7 - 200 (you think!) but 200 is really (-56)
because it is signed. So the CP is really evaluating 7 - (-56)
which is +ve, so your code takes that branch. Better to use the
carry flag:

ld a, 7
cp 200
jr nc, S200
jr C100
S200: ...

Also, see two jumps there? By rearranging your code you can
eliminate one of them:

ld a, 7
cp 200
jr c, C100
S200: ... ; else drop through to 'S200' code
...
jr TENS
C100: ...

As a bonus, you no longer need the S200 label; arguably this
makes your source less descriptive, but as your projects get
larger label space and label name conflicts can become important.
Better to put stuff in a comment to maintain readability.

See if you can change your code replacing all the JPs with JRs
using the carry flag for testing...

Pete
--

Nate Brazil

unread,
Apr 16, 2006, 9:47:05 PM4/16/06
to

<pe...@nospam.demon.co.uk> wrote in message
news:114516...@nospam.demon.co.uk...

> In article <Fq6dnSX3J6F...@comcast.com>
> nateb...@hotmail.com "Nate Brazil" writes:

[snip...]

> It's your use of 'JP P,xxx':
>
> ld a, 7
> cp 200
>
> will evaluate 7 - 200 (you think!) but 200 is really (-56)
> because it is signed. So the CP is really evaluating 7 - (-56)
> which is +ve, so your code takes that branch. Better to use the
> carry flag:
>

[snip...]

Thanks for your help Pete. I don't understand why 200 evaluates to -56.
Could you explain this further? 200 is being translated as a decimal
number, correct?

But I do agree that I should be using the cary flag instead. I'm going to
rewrite this app.

Nate

pe...@nospam.demon.co.uk

unread,
Apr 17, 2006, 1:27:41 AM4/17/06
to
In article <N66dnWVnTbQ...@comcast.com>
nateb...@hotmail.com "Nate Brazil" writes:

[..]


> Thanks for your help Pete. I don't understand why 200 evaluates to -56.
> Could you explain this further? 200 is being translated as a decimal
> number, correct?

Well, it's not really either 200 or -56 -- just a pattern of
bits. The subtraction in the CP instruction looks like

0000_0111 decimal 7
1100_1000 decimal 200, hex C8
--------- subtract
(1) 0011_1111 decimal 63, hex 3F, with carry set

The carry is set showing that a borrow occurred, but the sign bit
(bit 7, far left) is still zero. JP P,xx and JP M,xx test the
sign bit of the result -- not the carry. If that doesn't make
immediate sense, it will eventually!

> But I do agree that I should be using the cary flag instead. I'm going to
> rewrite this app.
>
> Nate

There isn't much to rewrite -- just a bit of cleaning up :-)

As an aside, the messing around I did here highlighted a problem
using a simple RET to exit the program, as nowhere was the stack
we were using initialised :-( There are two ways (probably more)
to get around this: (1) make sure there is a 0000 pushed right at
the start of the program so that RET has an defined address to
return to, or (2) end the program with "ld c,0" and "call BDOS"
to call the terminate service.

I prefer (2) but ymmv.

0 new messages