Porting ZXZVM
ZXZVM is composed of two modules: a machine-independent interpreter core and a machine-dependent front end. If you want to port it to another system, it should be sufficient to write a new front end.
In all existing systems, the interpreter core loads at 7000h (though this could easily be changed by reassembling vm.zsm at a different base address). If you do change this, then you'll have to adjust any addresses below in the 7000h-70FFh range.
The addresses used by the front ends differ depending on the host system.
The jump table
The key to the interface between the two components is the jump table.
This is described in the source file in_zxzvm.inc
; it is located
at address ZJT, which is 0E020h under ResiDOS and 04000h in the other
versions. The table has 41 functions, covering access to Z-machine
memory, screen I/O and file I/O.
Startup
In the Spectrum version, the startup code is in the form of a short
BASIC program that loads the two machine-code modules and jumps to
the interpreter entry point at 7000h. In the two PCW versions, the
startup code is incorporated in the front-end programs
(JOYCEIO.COM
and ANNEIO.PRG
respectively). Either
way, the startup code loads both modules into memory and makes a call to
7000h.
Memory from 705Ch-70FFh can be used to store parameters for the front end, such as the filename to load or (in the case of the Spectrum versions) which font to use. The front end initialisation will be called with DE holding the address of this area. Memory from 7003h-705Bh is reserved for parameters for the interpreter itself. Currently, only the byte at 7003h is used; it holds the system type. This is usually 7 (Commodore 128), though the PcW16 front end sets it to 2 (Apple IIe).
Pretty much the first thing the interpreter core does is call the code at ZJT, to initialise the front end. At this point the front end would load some or all of the Z-code file (either using the filename at 705Ch, or asking interactively), initialise the screen, and so on.
Z-Memory
The Z-machine has 64k of RAM and up to 448k of ROM. Representing all of this in the Z80's 64k address space along with the two ZXZVM modules and the host operating system can be something of a challenge.
The Spectrum +3 version allocates memory as follows:
- 0000-3FFF: BASIC ROM
- 4000-5AFF: Front end
- 5B00-6AFF: BASIC loader and Z80 stack
- 6B00-6FFF: 32-column fonts
- 7000-BFFF: Interpreter core and Z-machine stack
- C000-FFFF: Banked memory. Banks 0,1,3,4 hold the first 64k of Z-machine address space; Bank 6 holds the +3DOS sector cache; and Bank 7 holds the video RAM and +3DOS working buffers.
The ResiDOS version allocates memory as follows:
- 0000-3FFF: Banked memory - BASIC ROM, or buffered sections of the story file
- 4000-5AFF: Video RAM
- 5B00-6AFF: BASIC loader and Z80 stack
- 6B00-6FFF: 32-column fonts
- 7000-DFFF: Interpreter core and Z-machine stack
- E000-FFFF: Front end
The PCW10 version allocates memory as follows:
- 0000-00FF: CP/M Zero Page
- 0100-1FFF: Front end
- 2000-3FFF: Unused
- 4000-407A: Jump table
- 407B-6FFF: Unused
- 7000-(BDOS-768): Interpreter core and Z-machine stack.
- (BDOS-768)-BDOS: Z80 stack
- The entire story file is loaded into the PCW's banked memory (normally used to provide a RAMdisc); 16k blocks are paged into the C000-FFFF range as and when required.
The PcW16 version allocates memory as follows:
- 0000-3FFF: Reserved by operating system.
- 4000-6FFF: Front end.
- 7000-FFFF: Interpreter core and Z-machine stack.
- The entire story file is loaded into the PCW's banked memory, as for the PCW10.
Note that with all of these systems, there is at least 64k of extra banked memory containing the Z-machine's RAM. This isn't to say that all implementations must use banked memory; it's just that if banked memory isn't available, you'll have to find some other way of getting the same effect. Swapping to floppy may not perform well enough.
Screen model
The existing implementations all use screen drivers which can position the cursor, do reverse video (and in some cases other special effects), erase a line, and scroll a number of lines at the bottom of the screen while leaving the top untouched. In addition, the PCW10 and Spectrum front ends use software font redefinition to provide the Beyond Zork graphics font.
Colour support in ZXZVM is untested as none of the existing front ends implement it.
Timer
Versions 4 and later of the Z-machine allow text input to be interrupted by a timeout. Ideally your system should have a clock with 1/10 sec resolution; the PCW10 version uses the CP/M 3 clock and therefore has a 1 sec resolution.
Filesystem
ZXZVM's demands on the filesystem are fairly modest (apart from anything else, it has to support the PcW16, a system that allows at most two files open at any one time). For Z-machine file operations, it doesn't require anything fancy; just the ability to open a file and do sequential reads or writes.
If there isn't enough memory available to hold the whole gamefile, then you will have to write a virtual memory system capable of paging bits of it in and out as required. Under these circumstances, such features as random access will become necessary.
On porting ZXZVM to generic CP/M
From time to time, I've seen people discuss the lack of a version 5 Z-code interpreter for CP/M. And inevitably, someone points out that ZXZVM exists on the PCW, and reasons "surely it can't be that hard to port it to the CPC / Kaypro / Osborne / whatever platform we're discussing?"
Since no-one's done it in the last ten years, I think you can assume that it is that hard. It's not just a matter of tweaking a few escape codes to match whatever terminal your system has. The PCW front end makes no attempt at portability; it loads the entire story file into the PCW's RAMdisc, using direct hardware access to do so. (It also uses direct hardware access to redefine the font, but that's a less fundamental requirement).
As it is, therefore, the PCW front end can only be ported to a system with a similarly large amount of banked memory. If you're planning to port to another CP/M system, you may want to try one of the following strategies:
- If you've got large amounts of spare banked memory (~256k) it's just a matter of rewriting the memory paging code for your new target system.
- If you've got a moderate amount of spare banked memory (64k) then the best strategy is to behave like the Spectrum version: Load the first 64k of the Z-code file into that area, and load the remaining pages from disc when required.
- If you have no spare banked memory (ie, just the TPA) you'll need to reassemble ZXZVM and JOYCEIO so they sit tightly together at the bottom of memory, so that all free space in the TPA is contiguous. Then you have to hope that that amount of free space is big enough to hold the writeable area of the Z-machine's RAM. If it isn't, you're going to have to write an even more elaborate virtual memory system that writes changed pages out to a swapfile somewhere.
Exercises for the interested reader
- Add full colour support to the Spectrum 32-column version.
- Create other Spectrum output modes - perhaps 42-column like The Hobbit, or 51-column like +3 CP/M.
- Port to other processor architectures. I've experimented in the past with versions for the 8080 and 8086 processors, using automated conversion tools for most of the work.
John Elliott 2006-05-05