Hi! I've been a lurker here for a few weeks, not wanting to post an introduction thread 'til I had some source code to show... I'm a student, and I usually program in C, but I'm trying to broaden my mind so I've decided to try -- emphasis on try -- to learn to program in Z80 assembly language, specifically the Arnor MAXAM dialect, specifically for the 6128+ (I played a little on my older cousin's when I was younger); I've posted below the source code to my first assembly language program (which just prints "Hello!" to the screen -- I have a lot to learn...) -- it assembles/runs without any warnings or errors, but I'd appreciate it if anybody could tell me if I've made any egregious errors!
Hello!.Z80:
; The name of the assembled executable
write "Hello!"
; After starting the 6128+
; print himem
; 42619
; So I've assumed that using addresses
; 42600..42606 (storage for the string
; "Hello!" plus terminator) is okay...?
; Register pair HL holds the address
; 42600, to be set to the first
; character in the string
ld hl, 42600
; The memory at the address held by
; HL is set to the value 72 -
; - corresponding to the letter 'H' -
; then the address held by HL is
; incremented ready for the next
; character
ld (hl), 72 ; 'H'
inc hl
ld (hl), 101 ; 'e'
inc hl
ld (hl), 108 ; 'l'
inc hl
ld (hl), 108 ; 'l'
inc hl
ld (hl), 111 ; 'o'
inc hl
ld (hl), 33 ; '!'
inc hl
; Lastly, the memory at the address
; held by HL is set to the value 0,
; terminating the string
ld (hl), 0
ld hl, 42600
.loop
ld a, 0
; Compare the value at the address
; held by HL to 0; if it *is* 0,
; that's the string terminator
cp a, (hl)
jp z, forever
; Set A to the value at the address
; held by HL, and use a firmware
; function to print A to the screen
ld a, (hl)
call &bb5a
inc hl
jp loop
.forever
jp forever
Welcome and nice to see you're learning Z80, it can only make your coding generally better and more efficient.
Your code is fine and works, so it's a good attempt. Locating it beneath HIMEM is fine but generally code is placed somewhere in 'normal' RAM.
Here is an optimised version for you to play with:
org &4000
ld hl,message
.loop
ld a,(hl)
cp 0
ret z
call &bb5a
inc hl
jr loop
.message
defb "Hello World!",0
...and you can replace the CP 0 here with OR A.
For bonus points can you work out why...? ;)
Welcome in the forum and a happy 2014 full of CPCs.
@redbox: Thanks for the welcome :) Comparing your version to mine... I knew my version was bad; I didn't know how bad :( (I didn't know about the defb directive; and I didn't think enough to realise that labels could be loaded into registers.)
(And I think cp 0 could be replaced with or a as or a would essentially be a no-op but would still set the z flag if the result was 0...?)
@TFM: Thanks for the welcome :)
Presenting my first (vaguely :blush:) useful assembly language routine (to print to the screen an 8bit value as binary):
print8b.Z80:
.print8b
; B - The value to be printed (binary
; representation)
; HL - The address to return to after
; printing B
; Corrupts A, C, D
ld c, &80
.p8b_loop
ld a, b
and a, c
ld d, &30 ; Set d to '0'
jp nz, p8b_nz
.p8b_nz_return
ld a, d
call &bb5a
srl c
ld a, c
cp a, 0
jp nz, p8b_loop
ld a, &62 ; Set a to 'b'
call &bb5a
jp (hl)
.p8b_nz
ld d, &31 ; Set d to '1'
jp p8b_nz_return
print8b.tst (to test the above):
write "print8b"
ld b, 21
ld hl, goodbye
jp print8b
.goodbye
jp goodbye
read "print8b.z80"
Nice work, you're getting there. Here are another couple of variants you might like to make sure you understand... (IMHO, a good way to learn is to understand something that seems magical before!)
BTW, I'm not sure why you'd want to use HL as a return address here. Obviously there are occasions where it's useful, but more often you'd want to keep HL available as it's the second or first most useful register depending on what you're trying to do... In all these examples, I'll assume you're calling this function and using RET to return.
So, a simple example with RL and ADC to demonstrate carry:
; entry: B=value to be printed
; exit: A=corrupted, C=0
print8b:
ld c,8
print8bloop:
ld a,#30
rl b ; try to think about what happens to B each loop and at the end of the loop
adc a,0 ; and why this works
call &bb5a
dec c
jr nz,print8bloop
ld a,#62
jp &bb5a
a slight optimsation:
; entry: B=value to be printed
; exit: A=corrupted, C=0
print8b:
ld c,8
print8bloop:
ld a,#18
rlc b
rla ; try to understand why this works too!
call &bb5a
dec c
jr nz,print8bloop
ld a,#62
jp &bb5a
and now:
; entry: B=value to be printed
; exit: A=corrupted, B=0
print8b:
scf ; notice this!
print8bloop:
ld a,#18
rl b ; what happens to B here the first time through the loop and then the subsequent times
jr z,print8end ; if you didn't want the b at the end, ret z could be used
rla
call &bb5a
and a ; think why this is needed
jr print8bloop
print8end:
ld a,#62
jp &bb5a
Happy learning! :)
Welcome to the marvellous world of Z80 programming (and CPC Z80 world where JR is faster than JP :P )
Note CPC firmware does not use C termination for string but it sets the last character bit 7 as string terminator.
Quote from: the graveborn on 13:57, 29 December 13(And I think cp 0 could be replaced with or a as or a would essentially be a no-op but would still set the z flag if the result was 0...?)
It is 1µs and 1 byte instead of 2µs and 2 bytes for cp 0.What is the result if you OR (or AND) a bit with ifself ?
@ralferoo: Thanks for those examples; I'm still working to understand them, but after seeing the first of those examples I realised that --
ld a, &80
add a, &80 ; a is now 00000000b
adc a, 0 ; a is now 00000001b
-- thanks! :)
Quote from: ralferoo on 14:33, 29 December 13BTW, I'm not sure why you'd want to use HL as a return address here.
Because I haven't (yet...) figured out how to use
ret to return from a
jp :blush:
And after writing that I figured it out -- I shouldn't have been using
jp, but
call :blush:
(I'm trying to learn from pretty much a listing of assembler mnemonics -- a little less than ideal ;) I still have no idea what the
m,
p,
pe, or
po flags are for...)
@fano: Thanks for the welcome :)
I guessed that or instead of cp would be a time or memory saving, or redbox wouldn't have suggested it ;)
What I meant by no-op was that or a wouldn't change a, but would have the side-effect of setting z (and clearing nz) if a was 0... correct?
Exact ;)
Quote from: the graveborn on 16:54, 29 December 13I still have no idea what the m, p, pe, or po flags are for...)
pe and po stands for parity even and parity odd (bit count parity in result), related to P/V (or P) flag.m and p stands for negative or positive result (high bit of the operation result), related to S flag.
Everything Z80 you could possibly need will be at Thomas Scherrer Z80-Family Official Support Page (http://z80.info/) (although obviously nothing that's CPC specific)