Z80 program I've written

Started by Christian Johansson, January 06, 2007, 05:23 AM

Previous topic - Next topic

0 Members and 2 Guests are viewing this topic.

Christian Johansson

During the last days I've been reading a little about Z80 programming on the Commodore 128 and today I wrote a program that sets the border color and screen color to black and prints "HELLO WORLD!" at the top of the VIC screen. Here is the source code, which is for Power Assembler (Buddy):

10 REM START PROGRAM WITH BANK0:SYS65488 AFTER ASSEMBLING IT, THE NEXT LINE STARTS THE ASSEMBLER
40 SYS4000
45 .BANK 0   ;ASSEMBLE TO BANK 0
50 .ORG $3000   ;PUT THE PROGRAM AT $3000
60 .MEM   ;ASSEMBLE TO MEMORY (NOT TO FILE)
62 LD A,0   ;PUT 0 (BLACK) IN ACCUMULATOR
64 LD BC,$D020   ;PUT $D020 IN REGISTERS B AND C, NOTE HOW THE Z80 CAN USE 2 8-BIT REGS AS 1 16-BIT REG
66 OUT (C),A   ;SET BORDER COLOR, OUT MUST BE USED RATHER THAN LD FOR I/O REGS TO AVOID BLEED-THROUGH TO UNDERLYING RAM
68 INC BC   ;INCREASE BC REGS TO $D021
70 OUT (C),A   ;SET BACKGROUND COLOR
80 LD HL,MESSAGETXT   ;LOAD HL REGS WITH SOURCE ADDRESS
90 LD DE,$0400   ;LOAD DE REGS WITH DESTINATION ADDRESS (START OF SCREEN MEMORY)
100 LD BC,MESSAGELEN   ;LOAD BC REGS WITH NUMBER OF BYTES TO COPY
110 LDIR   ;BLOCK COPY INSTRUCTION
120 ETERNAL'LOOP JP ETERNAL'LOOP   ;LET'S STAY IN Z80 MODE
200 MESSAGETXT .SCR "HELLO WORLD!"
210 MESSAGELEN =*-MESSAGETXT
250 * =$FFD2
260 .BYT $BE   ;CHANGE BANK 0 TO BANK 2 IN 8502->Z80 ROUTINE TO AVOID CP/M BOOT ROM AT ADR 0 (CONFLICTS WITH SCREEN MEM)
300 * =$FFEE
310 .BYT 195,0,48   ;THE Z80 RESUMES AT $FFEE, LET'S PUT JP $3000 THERE SO THAT OUR CODE IS CALLED

What I want to do now is to learn about how to program Z80 interrupts on the C128. The problem is that I have no books containing that information. I know very little about interrupt handling on the Z80. Perhaps you here at the forum have some information.

Stephane Richard

A tutorial on Z80 assembly
http://users.hszk.bme.hu/~pg429/z80guide/

Complete Z80 reference in PDF format:
http://www.myquest.nl/z80undocumented/z80-documented-v0.91.pdf

The complete Z80 instruction set:
http://www.ticalc.org/archives/files/fileinfo/128/12883.html

Suymmary of Z80 Instruction set for quick reference
http://www.ticalc.org/archives/files/fileinfo/1/109.html

And finally, for the non faint of heart ;-) the Z80 Family CPU User Manual
http://www.myquest.nl/z80undocumented/z80cpu_um.pdf

hope this helps.
When God created light, so too was born, the first Shadow!

MystikShadows

Christian Johansson

Thank you for the links, mystikshadows.

Stephane Richard

You're very welcome.  Let me know if this still doesn't have the obvious answer to your interrupts we'll see what else can be found. :-)
When God created light, so too was born, the first Shadow!

MystikShadows

nikoniko

Christian, also check out the books here. Included among them is the well-regarded Rodney Zaks book, Programming the Z-80. I'm not sure how in-depth his treatment of interrupts is, but since a number of people consider it the bible of Z-80 programming, it's probably worth a look.

Christian Johansson

Some Z80 memory configuration information that is hardly documented anywhere:

1. When the Z80 processor is active with the MMU set so there is I/O at $D000-$DFFF and RAM bank 0 or 2 in the rest of the 64 kB of address space, color RAM is at $1000-$13FF and NOT at $D800-$DBFF where it is on the C64 and on the C128 with the 8502 active. $1000-$13FF is a part of the I/O space so just as for $D000-$DFFF, "OUT (C),A" should be used to write a byte and "IN A,(C)" to read a byte.

2. The difference between RAM bank 0 and RAM bank 2 when the Z80 is active is that in RAM bank 0, the Z80 ROM (containing reset and CP/M boot code) is seen at $0000-$0FFF while in RAM bank 2, RAM is seen in the same area.

Christian Johansson

Now I've got Z80 IRQs working :D so here comes the Power Assembler source code for a small example program I've written. The program sets up CIA #1 timer A to generate an IRQ 60 times per second. At each interrupt, the border color is increased by 1.

50 REM START PROGRAM WITH BANK0:SYS65488 AFTER ASSEMBLING IT, THE NEXT LINE STARTS THE ASSEMBLER
100 SYS4000
150 .BANK 0 ;ASSEMBLE TO BANK 0
200 .ORG $3000 ;PUT THE PROGRAM AT $3000
250 .MEM ;ASSEMBLE TO MEMORY (NOT TO FILE)
300 LD SP,$2000 ;SET STACK POINTER, NOTE THAT THE Z80 HAS A 16-BIT STACK POINTER
320 LD A,$31
350 LD I,A ;SET THAT THE IRQ VECTOR WILL BE TAKEN FROM SOMEWHERE BETWEEN $3100-$31FF IF IM 2 IS USED
400 IM 2 ;SET INTERRUPT MODE 2 (IM 1 SHOULD PROBABLY WORK ON THE C128 AS WELL BUT I HAVEN'T GOT THAT WORKING)
402 ;THE FOLLOWING 6 LINES FILL $3100-$3200 WITH $32, THE IRQ ROUTINE THEN HAS TO BE AT $3232
405 LD A,$32
410 LD BC,$3100
412 - DEC C
415 LD (BC),A
420 JR NZ,-
430 LD ($3200),A
450 LD A,0
500 LD BC,$D01A
550 OUT (C),A ;DISABLE VIC II IRQs
600 LD A,$25 ;LO BYTE OF 1/60 S FOR PAL, USE $95 INSTEAD FOR NTSC
650 LD BC,$DC04
700 OUT (C),A ;SET LO BYTE OF CIA #1 TIMER A
750 LD A,$40 ;HI BYTE OF 1/60 S FOR PAL, USE $42 INSTEAD FOR NTSC
800 INC C
850 OUT (C),A ;SET HI BYTE OF CIA #1 TIMER A
900 LD A,$81
950 LD BC,$DC0D
1000 OUT (C),A ;ENABLE CIA #1 TIMER A IRQ
1050 LD A,$01
1100 INC C
1150 OUT (C),A ;START CIA #1 TIMER A IN CONTINUOUS MODE
1200 EI ;ENABLE Z80 INTERRUPTS
1250 ETERNAL'LOOP JP ETERNAL'LOOP
1300 * =$3232 ;IRQ ROUTINE BEGINS HERE
1350 LD BC,$DC0D
1400 IN A,(C) ;CLEAR CIA TIMER A INTERRUPT
1420 EI ;Z80 INTERRUPTS HAVE TO BE ENABLED AGAIN IN THE IRQ ROUTINE, ELSE NO MORE INTERRUPTS WILL OCCUR
1450 LD BC,$D020
1500 IN A,(C) ;READ BORDER COLOR
1550 INC A ;INCREASE BORDER COLOR BY 1
1600 OUT (C),A ;WRITE NEW BORDER COLOR
1650 RETI ;RETURN FROM IRQ
1700 * =$FFD2
1750 .BYT $BE ;CHANGE BANK 0 TO BANK 2 IN 8502->Z80 ROUTINE TO AVOID Z80 BOOT ROM AT ADR 0
1800 * =$FFEE
1850 JP $3000 ;THE Z80 RESUMES AT $FFEE, LET'S PUT A JUMP INSTRUCTION TO OUR CODE THERE

nikoniko

Pretty nifty! I'll have to try that out later today.

Stephane Richard

Hmmm, where can I get this Power Assembler?  I haven't found it on this site...don't mean it's not there just means I didn't see it ... yet.....(points to his empty cup of coffee) ;-).

If not, anyone happen to have that in crt or dXX format handy?  As always, if it's legal of course. if not point me to a site that sells it or has the right to distribute it.
When God created light, so too was born, the first Shadow!

MystikShadows

nikoniko

I owned it at one point, later had it as an emulator file, then deleted it by accident. Can't seem to find it on the net again, but I haven't searched very hard.

It's not difficult to use MXASS to assemble Z80 (and 6502/10/c02/ce02/sc02 and HuC6580) code targetted for the 128. The format is different, but for a short program it's not difficult to translate by hand. The docs for it have an example program demonstrating setting up the processor switch, executing a short Z80 routine, and returning to the 8502.

adric22

I know the topic has been brought up before and I've seen people argue over it.  But I still would like to see how fast a program written for the 128 for operating on the Z80 would actually run.  I mean something like a demo or a GUI or something like that.   I realize the CPU has a few bottleneck issues, but I bet it could do some hardcore math better than the 6502 code.

hydrophilic

I've got Z80 code for drawing to the VIC bit map.  My idea was you could do killer flight sim with the 2MHz power of the Z80; I thought with that speed and all those 16-bit registers it would really fly.  But no.  2MHz Z80 is about as efficient as 8502 at 1MHz.  Of course it's not 'hard core' math, and I don't claim to be an expert Z80 programmer... I'll see if I can dig it up.

Quote from: Christian JohanssonThe difference between RAM bank 0 and RAM bank 2 when the Z80 is active is that in RAM bank 0, the Z80 ROM (containing reset and CP/M boot code) is seen at $0000-$0FFF while in RAM bank 2, RAM is seen in the same area.
Can somebody confirm this (my c128 is dead)?  I pretty sure the MMU has no output for Bank 2/3 (only internal registers), so how would the PLA / ROM know to vanish in Bank 2 but appear in Bank 0 ??

nikoniko

I think the only way to get full performance out of the Z80 is to turn off the VIC and use the VDC, which is one speedup technique used in the enhanced CP/M which is around here somewhere. Even then, as you pointed out, the 8502 is more efficient at many operations, so the higher clock speed doesn't help that much overall. Sometime when I have a 128 again, though, I'd like to do some real benchmarks. Or maybe Christian has done some comparison already? I'm also curious how long it takes to switch control from the 8502 to the Z80 and vice versa.

There was an 8Mhz Z80 replacement chip for the 128, manufactured by a German company if I remember correctly, but the name escapes me at the moment.

Guest

What clock speed can the VDC handle?  I was messing around with MESS last night and it says that the PET 30xx, 40xx, and 80xx series all use a 6502 clocked over 7 mhz!  I didn't realize any of Commodore's 8-bit systems were that fast.

nikoniko

I wonder if they're confusing the PET with the first Amigas, which I think were something like 7.14Mhz. Or possibly they were thinking of the Apple II series which had a number of accelerator boards available?

Guest

Quote from: nikonikoI wonder if they're confusing the PET with the first Amigas, which I think were something like 7.14Mhz. Or possibly they were thinking of the Apple II series which had a number of accelerator boards available?
No, it was definitely the PET (CBM) line and it was definitely reporting a 6502 CPU.  When you start an emulator in MESS it gives you the vitals on CPU, Memory, Sound and Video chips it's emulating.

nikoniko

Oh, I don't doubt that it *says* 7Mhz. I just doubt that Commodore ever made any 8-bit machines that were that fast. :)

I can't find anywhere that mentions it, and places like Zimmers have those models listed at 1Mhz.

Christian Johansson

Some answers and comments:

* No, I haven't made any speed comparisons between the 8502 and the Z80 but I have read that people who have made speed comparions have written that everything goes faster with the 8502. With the Z80, you can do some things more easily. You can for example do 16-bit arithmetic and you can copy a block of data with just one instruction. However, even if more instructions are needed on the 8502, it is still faster to do these things on the 8502. I have read that a 4 MHz Z80 is roughly comparable in speed with a 1 MHz 6502. This is because of the pipelining technique used in the 6502. Since the Z80 in the C128 is only running at an effective speed of 2 MHz it means that it is slower than the 1 MHz 8502.

* The information about that the Z80 ROM exists with RAM bank 0 but not with RAM bank 2, I found the information at comp.sys.cbm and I have verified it myself. With RAM bank 0 and screen memory at $0400 (the normal place for screen memory), I just got garbage on the screen but with RAM bank 0 it worked, i.e. I got RAM instead of ROM at $0400.

* Regarding the PET running at over 7 MHz, could it perhaps be the dot clock that is meant? At least, in the VIC-II, I think think there is a dot clock operating at around 8 MHz that is divided down to about 1 MHz for the CPU to use.

* It takes too much time for me to explain right now how to switch between the Z80 and the 8502 but basically there are routines in RAM at $FFD0 and $FFE0 (if I remember correctly) that have been copied there at startup. If you want to switch to the Z80, you can jump to the routine at $FFD0. When it switches over to the Z80, the Z80 will wake up a bit into the Z80 routine to switch in the opposite direction (that begins at $FFE0). You can change instructions in the just mentioned two routines to jump to your own code. For example, the Z80 wakes up at a RST 8 instruction that you can change to a JP to jump to your own code if you don't want CP/M to be booted (which I think RST 8 results in).

* I can add that there are some things you can only do with the 8502 but not with the Z80. There is an NMI input on the Z80 but it is connected to ground so this means that you can't get interrupts to the Z80 when pressing RESTORE or from CIA #2. Furthermore, the I/O port registers at addresses 0 and 1 are 8502 specific so you can't change anything related to those bits when the Z80 is active, i.e. you can't decide if the CPU and the VIC should see color RAM from bank 0 or 1 (a little known feature), you can't use a Datassette, you can't detect if CAPS LOCK has been pressed and you can't switch out the character ROM shadow that by default exists in all 4 VIC banks on the C128. On the C64 you always have character ROM shadow in two of the VIC banks and never in the other two but with the C128 with the 8502 active you can select if you want to have character ROM shadow in all four banks or in none of the banks.

hydrophilic

Wow, what a post!  Thanks for checking the Bank 0, Bank 2 ROM thing.  I am working on a super memory map for the C128 and I would feel real stupid if I said Bank 2 is the same as Bank 0.  I need to go over the schematics again... I still don't see how the hardware would know bank 2 from bank 0.  Commodore never ceases to amaze me!

In my opinion (yes, there are lots!), the Z80 is slower because a memory access requires (at least) 3 clock cycles, while the 8502 only requires 1.

I noticed you are using IM 2.  I always thought that was completely stupid (no offense to you).  I think Commodore used it for compatibility with CP/M (duh) because common programs (SID and DDT, I think) use RST $38 for debugging.  I always used IM 1 for my Z80 programs which always jumps to $0038.  I used Bank 1 so I had complete control... I can't remember what's at that address in ROM Bank 0 right now (my memory map is on another PC)...

Any new Z80 programs you've made?

Christian Johansson

Actually, I tried to use IM 1 first but I didn't get it working so therefore I tried IM 2, which worked. Thanks to your post I now know that IM 1 should also work. I might try again later.

About new Z80 programs, I'm thinking about writing a VIC-II text scroller for the Z80 and post the source code here when I get it working.

hydrophilic

I don't know if its necessary but CP/M did it so I did it, which is when using Bank 1, disable common RAM (at least in the low area) and reprogram the MMU to use Bank 1 for zero page and page 1.  Like this:
LD BC, $D50A
LD A,1
OUT (BC),A ;page 1 use bank 1
DEC C
OUT (BC),A ;page 1 use page 1
DEC C
OUT (BC),A ;page 0 use bank 1
DEC C
XOR A
OUT (BC),A ;page 0 use page 0
DEC C
OUT (BC),A ;no common ram

Before the last line you can set A to something else; CP/M sets the top of RAM common.  I think the only requirement is the bottom must NOT be common.  I hope that helps.

Christian Johansson

Thank you for the information. I'll try that. I used common RAM in the low area and that might have been the problem.

hydrophilic

Just remember that code is for Bank 1 and if you use VIC in Bank 1 then before the last line you need LD A,$40.  I could only guess what it needs to be for Bank 2...

But if you use Bank 0, the instruction in ROM at address $0038 is JP $FDFD (I looked it up last night to be sure).  CP/M (and if I use bank 0) store a JP in that location so the routine can be anywhere.  Of course if KERNAL ROM is active that probably won't work.  I say probably because I assume the KERNAL would hide the JP instruction, but the MMU is wacky with Z80 active so who knows...

Christian Johansson

Now I've got IM 1 to work :) . The problem was not related to common RAM. I found that I don't have to write to the registers you mentioned, hydrophilic ($D506-$D50A), when using RAM bank 2 since those registers are already set up in a way that works with bank 2 by the C128 Kernal. The problem was that Power Assembler uses the BASIC editor. Unfortunately, BASIC seems to overwrite the memory location with address $38 in bank 0 (bank 2) whenever you RUN a program (you compile a Power Assembler program by issuing RUN) or when issuing a SYS command. I solved that by letting the Z80 program write a JP instruction to $38 in bank 2 before interrupts are enabled (by using EI) rather than putting the JP instruction there already at compilation time.

I now think I prefer IM1 since it is easier to set up than IM2. The issue with that you have to fill a whole page with the same address when using IM2 since you don't know from which address the Z80 will fetch the address to jump to at an IRQ doesn't appeal to me very much.

Here is the IM 1 version of the latest program I posted. Btw, do you know if you should return from the interrupt routine with RET or RETI when using IM 1? Both seem to work. I think I've read somewhere that RETI should only be used for interrupt mode 2 and that interrupt mode 1 works like a RST $38 instruction (for RST I think you return with RET) but I'm not sure.

50 REM START PROGRAM WITH BANK0:SYS65488 AFTER ASSEMBLING IT, THE NEXT LINE STARTS THE ASSEMBLER
100 SYS4000
150 .BANK 0 ;ASSEMBLE TO BANK 0
200 .ORG $3000 ;PUT THE PROGRAM AT $3000
250 .MEM ;ASSEMBLE TO MEMORY (NOT TO FILE)
300 LD SP,$2000 ;SET STACK POINTER, NOTE THAT THE Z80 HAS A 16-BIT STACK POINTER
420 LD A,195 ;OPCODE FOR THE JP INSTRUCTION
422 LD BC,$38 ;THE Z80 STARTS AT $38 AT IRQ IN INTERRUPT MODE 1
424 OUT (C),A
426 LD A,428 INC C
430 OUT (C),A
431 LD A,>IRQ'ROUTINE
432 INC C
434 OUT (C),A
450 LD A,0
500 LD BC,$D01A
550 OUT (C),A ;DISABLE VIC II IRQs
600 LD A,$25 ;LO BYTE OF 1/60 S FOR PAL, USE $95 INSTEAD FOR NTSC
650 LD BC,$DC04
700 OUT (C),A ;SET LO BYTE OF CIA #1 TIMER A
750 LD A,$40 ;HI BYTE OF 1/60 S FOR PAL, USE $42 INSTEAD FOR NTSC
800 INC C
850 OUT (C),A ;SET HI BYTE OF CIA #1 TIMER A
900 LD A,$81
950 LD BC,$DC0D
1000 OUT (C),A ;ENABLE CIA #1 TIMER A IRQ
1050 LD A,$01
1100 INC C
1150 OUT (C),A ;START CIA #1 TIMER A IN CONTINUOUS MODE
1170 IM 1 ;SET INTERRUPT MODE 1
1200 EI ;ENABLE Z80 INTERRUPTS
1250 ETERNAL'LOOP JP ETERNAL'LOOP
1350 IRQ'ROUTINE LD BC,$DC0D
1400 IN A,(C) ;CLEAR CIA TIMER A INTERRUPT
1420 EI ;Z80 INTERRUPTS HAVE TO BE ENABLED AGAIN IN THE IRQ ROUTINE, ELSE NO MORE INTERRUPTS WILL OCCUR
1450 LD BC,$D020
1500 IN A,(C) ;READ BORDER COLOR
1550 INC A ;INCREASE BORDER COLOR BY 1
1600 OUT (C),A ;WRITE NEW BORDER COLOR
1650 RET ;RETURN FROM IRQ
1700 * =$FFD2
1750 .BYT $BE ;CHANGE BANK 0 TO BANK 2 IN 8502->Z80 ROUTINE TO AVOID Z80 BOOT ROM AT ADR 0
1800 * =$FFEE
1850 JP $3000 ;THE Z80 RESUMES AT $FFEE, LET'S PUT A JUMP INSTRUCTION TO OUR CODE THERE

hydrophilic

Cool.  And IM 1 is faster too cause it doesn't have to lookup the vector from the Interrupt table.  I've read RETI is the same as RET but the former is designed to work with special Zilog interrupt chips to allow interrupt chaining/multiplexing.  Since there's none of them in the C128, RET is probably better because the opcode is 1 byte (faster).

I just noticed something.  You are storing the interrupt opcodes to RAM with the OUT instruction.  I never tried that.  People usually use LD (HL),A when storing to RAM and only use OUT for I/O chips.  Obviously it works, but it is slower.  LD(HL),A opcode is only 1 byte but OUT (BC),A is two.

Something I never tried.  There is undocumented OUT(BC),0 -- store zero to I/O port BC.  Maybe you might find it useful?  Its opcode ED 71 (or so I've read).