Raffaele Intorcia

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

  • Mapping the Commodore 128
  • Power Assembler instruction manual
  • https://www.reddit.com/r/c128/comments/9nxhcc/8502_z80_switchover_a_simple_example/
Share on: