Z80/8502 switchover
Italian version will come…
Commodore C128 is a double 8-bit processor machine. it has:
- a MOS 8502 (derived from 6510 and 6502) which is used to run C128 Basic v7 mode and C64 Basic v2 mode
- a Zilog Z80 which is used to run CP/M.
Commodore says that is’t like having 3 pc in one. Aside from commercial, 8502 and Z80 share all components of this machine and also share data bus and address bus. This sharing makes impossible to run simultaneously so a processor can run while the other is inactive.
Booting process
(from Mapping the Commodore 128)
After power up, the Z80 microprocessor has control before the 8502 is allowed to take over. There are only a few signs of this: two short routines are copied into bank 0 RAM. One, at $FFD0-$FFDF, is an 8502 ML routine that surrenders control to the Z80;
FFD0 SEI
FFD1 LDA #$3E
FFD3 STA $FF00
FFD6 LDA #$B0
FFD7 STA $D505
FFDB NOP
FFDC JMP $1100
FFDF NOP
the other, at $FFE0-$FFEF, is a Z80 ML routine that surrenders control to the 8502.
FFE0 DI
FFE1 LD A, 3EH
FFE3 LD (FF00H), A
FFE6 LD BC, D505H
FFE9 LD A, B1H
FFEB OUT (C),A
FFED NOP
FFEE RST 08H
There are no routines in any of the 128 mode ROMs to perform this initialization. These routines have no use in 128 mode-they can be used only in CP/M mode-but they are recopied to block 0 during each reset.
Bart van Leeuwen made some research and spent a lot of time about boot process and found some interesting information. I’ll copy from our info exchange on FB.
Booting CP/M is always done from 128 mode. You can verify this: when powering-on (or resetting) a C128 with a CP/M boot disk inserted, you will get the BASIC V7 copyright message before CP/M starts. The 'trick' to this is not in the CP/M BIOS but in the boot record of a CP/M disk. So, even with a CP/M disk inserted, the machine will power-on/reset to Z80 mode first, do its copying to ram, and then always call the 'switch to 8502' routine it copied to ram. This in turn will suspend the Z80, and start the 8502. Because the 8502 has been reset, it will start reading the 6502 reset vector and start executing the C128 kernal. The kernal will check for a C64 cartridge, if none found, check for an autostart function rom (autostart byte == 1), and when none found, do the c128 kernal and basic initialization, after which phoenix will run. Phoenix first checks for function roms with an autostart byte >0 and run it if found. When that returns, or none is found, it will read the first block of the drive at id 8, and check for a boot signature. The CP/M boot disk has a regular C128 boot signature, and some code which sets up the system to load CP/M, and then switches to the Z80.
Reactivating Z80
(from Mapping the Commodore 128)
Switching processors is not for the faint of heart. When you activate the Z80, it will begin executing instructions at whatever address is currently in its program counter registers. The address in those internal processor registers can’t be changed from 128 mode, so you’re stuck with having the Z80 take up wherever it left off when the system was switched to 128 mode. This address is usually $FFEE, the location following the one where 128 mode was activated at the end of the Z80’s reset routine. In block 0 RAM, that location is initialized with a Z80 instruction (RST1) to perform a warm start of CP/M mode. If you don’t have a valid Z80 machine language instruction there when you activate the Z80 (for example, if the system is in a memory configuration such as bank 15 where 128 Kernal ROM is seen at that address) you’ll probably experience an immediate system lockup.
(from Power Assembler Instruction Manual)
Bit 0 at $D505 (54533) controls the micro processor mode. If it is turned on then the 8502 becomes active; if it is off then the Z80 takes over. You can’t just poke it off. A little housekeeping is first in order:
- disable 8502 interrupts via SEI because you are going to switch to a memory configuration in which Kernal ROM is not visible. To do this, store a $3E (62) at $FF00 (the configuration register). This leaves I/O RAM intact but switches everything else to RAM 0.
The Z80 PC register holds $FFED after 128 initialization. There is a NOP ($00) there. The first actual Z80 command goes at $FFEE. If you look through the monitor you will see a $CF there. This is an RST 8 opcode byte which will cause the Z80 to jump (ReSTart) to its own ROM routine at 0008. You do not want this. After moving some 8502 code into place at $3000, the Z80 would return control to the 8502. The 8502 wakes up exactly where it left off after you switched to the Z80. If you followed this switch with a NOP (lets not wake It up to fast) and then a JMP $3000 (like the operating system does) you would go into the 128’s boot CP/M routine. This is pretty useless from a programming standpoint, so don’t bother. Instead, put your own Z80 code at $FFEE.
The last thing the U80 will have to do is to turn the 8500 back on. There are two ways to do this:
LD A,$B1
LD ($D505),A
This is inferior. There is a bleed through condition in the Z80 mode using this type of store. A $B1 will also be written to underlying RAM. Here is the proper way:
LD BC,$D505
LD A,$B1
OUT (C),A
Bleed through not occur using OUT storage and all I/O memory between $D000 and $DFFF can be written to.
Exploiting
So, this is the situation. Z80 started but didn’t find CP/M so it’s program counter stopped at $FFED, reenabled 8502 and then go to sleep.
Z80 can be reactivated for running a script. The first thing is to set where is code we want to run. We can let Z80 run it by setting a jump to that address just after the NOP. For Z80, a jump is made by JP instruction which opcode is C3.
LDA $C3
STA $FFEE // JP opcode set on FFEE
LDA (lo-byte-address-of-z80-routine)
STA $FFEF
LDA (hi-byte-address-of-z80-routine)
STA $FFF0
After this, 8502 can be activated by setting bit 0 fo MMUMCR ($D505).
Also Z80 code must be prepared for running: there are some instruction before and some other at the end to correctly handle environment.
Before Z80 routine, some opcode must be provided:
LD A, 3Fh
LD (FF00H),A
This is needed for Mmu configuration. After Z80 routine, some other opcode must be provided:
JP FFE0H
This tells Z80 to jump to FFE0, which contains, as seen before, some instruction to disable Z80 and reactvate 8502.
Z80-8502 switch can be repeated many times because at the end of every Z80 code, Z80 program counter is set to $FFED so we always have the same end situation.
Show me the code
* = $2000
lda #$c3 // Set JP opcode into $ffee
sta $ffee
lda #<$3000 // Set JP address into $ffef (lo-byte)
sta $ffef
lda #>$3000 // Set JP address into $fff0 (hi-byte)
sta $fff0
* = $3000
.byte $3E, $3F // LD A, #$3F -- load up the #$3f byte, for mmu cr
.byte $32, $00, $FF // LD ($FF00),A -- set the mmu configuration
// register mirror with #$3f
.byte $3E, $08 // LD A, #$08 -- load up the #$08 (H) byte
.byte $32, $00, $04 // LD ($0400),A -- write on screen
.byte $3C // INC A
.byte $32, $01, $04 // LD ($0401),A -- write on screen
.byte $3E, $21 // LD A, #$21 -- load up the #$21 (!) byte
.byte $32, $02, $04 // LD ($0402),A -- write on screen
.byte $c3, $e0, $ff // JP $FFE0 - jump to the bootlink routine in the
// Z-80 ROM, 8502 is switched on there.
This simple snippet, which can be run wit SYS 8192, will print “HI!” in the top left position of the screen. This string will be print by Z80 and then safely control will be granted to 8502.
Basic
Z80-8502 switch can be achived also from Basic. On this Reddit resource (https://www.reddit.com/r/c128/comments/9nxhcc/8502_z80_switchover_a_simple_example/), you can switch from one processor to another with this simple Basic code. It’s the same behavior, done with POKE:
10 bank 0
20 rem ffee c3 00 30 - jp 3000 (z80)
30 poke 65518,195
40 poke 65519,0
50 poke 65520,48
60 rem ffdc 58 - cli (8502)
70 poke 65500,88
80 rem ffdd 60 rts (8502)
90 poke 65501,96
100 for i=12288 to 12298
110 read a
120 poke i,a
130 s=s+a
140 next i
150 if s <> 1691 then print "Virhe Datoissa!": end: rem not clear this...
160 sys dec("ffd0")
170 goto 160
180 data 1, 32,208,237,120, 61
190 data 237,121,195,224,255
References
- Starting sequence info on reference
- Mapping the Commodore 128
- Power Assembler instruction manual
- https://www.reddit.com/r/c128/comments/9nxhcc/8502_z80_switchover_a_simple_example/