UZI180 - Unix Z80 Implementation for the Z-180 (UZI180)

Adapted from UZI by Doug Braun and UZI280 by Stefan Nitschke

Copyright (C) 1998 by Harold F. Bower
Portions Copyright (C) 1995 by Stefan Nitschke

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License (file LICENSE.TXT) for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

UZI180 is a multi-tasking, multi-user operating for the Zilog Z180 family of microprocessors. It is a port of the original UZI incorporating many fixes from UZI280 and some additional features. Significant changes include an updated floppy disk driver based on a NEC765 derivative controller, a restructuring and reorganization of the various kernel modules to ease porting to different hardware platforms, a simpler loading structure from CP/M, and an integral (albeit minimal) CP/M 2.2 emulator. The task was undertaken to learn more about the Unix operating system and the C programming language with an eye to using it to replace several aging CP/M systems.

Like the original UZI, this system is based on Unix Edition 7 which was one of the direct predecessors of Unix System V. UZI180 incorporates the original system calls and the UZI280 extensions added by Stefan Nitschke. Some minor additions were also added to provide additional System V compatible features. As stated in the original documentation, there are only a few significant differences in the UZI kernel calls. The principal deviations are:


UZI180 makes extensive use of the Memory Management (MMU) of the Z180 core to provide as much process isolation as possible with the chip. Since the MMU granularity is 4k, this establishes the basic process structure. The top 4k of logical address space (one MMU slice) is defined as Common1 memory space and contains critical routines that either must always be resident in memory, or are associated with the currently-running process. Some of the key elements placed in the Common memory space are:

A full 64k is assigned to each process, with 60k available to application code and data, with the remaining 4k used for system storage. This organization permits one 64k bank for the kernel/common memory and seven process banks on a 512k system where all RAM is usable. The UZI180 memory map therefore appears as:
              First 64k          Subsequent 64k banks
    FFFF    +------------+         +------------+
Common      |   Common   |         | Task Store |+
    F000    +------------+         +------------+|+
            |            |         |            |+|+
            |   Kernel   |         |  Process   ||+|
Banked      |    Code    |         |    Code    |||+
            |            |         |   & Data   ||||
            |            |         |            ||||
    0100    +------------+         +------------+|||
            |  Reserved  |         |  Reserved  |+||
    0000    +------------+         +------------+|+|
Areas marked as "Reserved" are used for Restart and Interrupt vectors, and to host the required Page 0 storage elements for the CP/M Emulator. Constructing executable code modules starting at 100H also allows use of normal CP/M tools without conflicting memory use.

The architecture accomodates MMU base addresses beginning at the first available RAM address wherever that is. For YASBEC and P112 systems that shadow the ROM into RAM, the initial BBR (MMU Base Bank Register) reading is 08H, while on the MicroMint SB180 RAM begins at 40H. 64k Banks will therefore begin with BBR values of 08H, 18H, ..., 78H for the former; and 40H, 50H, ..., 70H for a 256k SB180.

For 256k systems that have all RAM available (i.e. do NOT shadow the ROM), the kernel and only three process banks will be available. While this may be minimally usable for a single-user system, a 512k system is the minimum recommended. For systems which DO shadow the system ROM, 32k is normally reserved (beginning at physical RAM address 0), thereby reducing available RAM by 32k. This leaves 480k available in a 512k system resulting in only six process areas after allowing 64k for the kernel and common bank. The last 32k will be unused.

The algorithm for computing true 20-bit addresses in a Z180 (for DMA) is:

BBR    7654 3210
Addr        hhhhhhhh llllllll
       bbbb xxxxxxxx yyyyyyyy
An initial check should be made for common area access (assumed to be the top 4k) which will be addresses of the form "1111hhhh llllllll" or FxxxH. For these addresses, the raw BBR value for the first 64k of usable RAM is added. As an example, the first byte of the Common Area is computed for DMA use on a P112 with 512k of memory (ROM shadowed) as:
BBR    0000 1000                     08
Addr        11110000 00000000         F000
       ----------------------        -----
       0001 01110000 00000000        17000
A kernel variable (osBank) for the Common Area holds the BBR value for the top 4k which is always accessible by the processor. It is used to specify the source address when swapping out, and for the destination address when swapping in. The BBR value associated with each process space is stored both in the kernel process table, and in each process' user data block. This value is used for the other address in process swaps.

Task switching consists of saving the current process status in the User stack and data area, block moving the user data area and stacks to the respective process area in the reserved top 4k (via DMA), restoring the new task's data into common memory (also via DMA), changing the Bank Base Register in the Z180 MMU to bring the new task in context, resetting the new process' stack pointer, restoring processor status and continuing as before. This results in relatively rapid response since the bulk of time required is due to two 768-byte DMA transfers which consume 1536 * 6 T-states or 9216 / 18,432,000 = .0005 Sec = 500 microseconds on a 18.432 MHz P112 or YASBEC upgraded with the Z8S180, and double this time, or 1000 microseconds (1 millisecond) on a standard 9.216 MHz YASBEC. This ignores any additional time due to insertion of memory wait states and the relatively insignificant overhead associated with housekeeping.

Time slices were initially set to 50 mS (20 ticks per second) based on one of the internal Z180 Programmable Reload Timers. At that rate, less than 4% of a time slice was consumed in swapping processes. The development systems have subsequently shortened the slice time to 20 mS to minimize data loss from polled serial ports. A disadvantage of shortening the time is that a greater percentage of CPU time is consumed during process swaps with a subsequent lengthening of task run times. This may be adjusted by appropriately setting parameters in the C and Assembly include files and re-building the kernel.

Since peripherals in small systems often are not capable of Interrupt-driven operation, UZI180 implements polling of such ports during timer interrupt cycles. Unfortunately, this can lead to inaccurate timekeeping if the timer is the sole source of Real Time in the system. A prime example of this is in the Floppy Disk Driver. Polled operation often dictates that interrupts be disabled during disk accesses to avoid data loss and/or corruption. During the comparively long time which may elapse (up to one revolution of 200 milliseconds for 3.5" drives), several clock ticks may be lost before timekeeping resumes.

Wall-Time is corrected automatically by reading the Real-Time Clock every time the number of ticks-per-second (set by a constant in include files) elapses and updating a global kernel variable. This variable is then used by kernel functions when necessary to perform such tasks as time stamping files and returning current time-of-day.

Polled I/O sampling on timer interrupts is not so easily handled and data loss may be significant. For systems which must operate in this manner, it is recommended that low-priority processes be placed on those interfaces. Also, use of peripherals that disable interrupts for long periods, such as polled Floppy Disk drivers, should be minimized.


The UZI180 kernel is Process 0 and executes in the lowest 64k of RAM in the system as a standalone CP/M executable program. Several stages of initialization occur when first started. The code and initial data which is destined for the Common Memory is moved into position, having been linked to an executable image during the link process. After initializing various kernel data elements, Process 1 is initialized in the lower 60k of the next higher 64k memory region. Normally Process 1 is "init" which logs in users and starts (forks) a shell or other user interface. Each consecutive fork without termination of a previous process is loaded into the lower 60k of successively higher 64k increments.

When a new process is initiated, data in the process' address is prepared with several pieces of data. Necessary arguments and environment variables are copied into high addresses along with the pointer arrays per standard UZI definitions. Location 0H is initialized with a jump to a TRAP (illegal instruction fetch) error handler, and location 0030H (RST 30H) with a jump to the kernel service handler. Both of these handlers reside in the Common Memory bank beginning at 0F000H. Version-specific locations may also be initialized at this phase such as the Interrupt Vector at 0038H (RST 38) and the Non-Maskable Interrupt at 0066H.

Process swaps are initiated by a simple timeout of a per-process counter which is decremented. When a process is initiated (via an execve kernel call) or the process is swapped into context, the counter is initiated to a defined priority value. Each "tick" of the periodic clock interrupt decrements the counter until it reaches zero. At that time, the User data and associated stacks (User, Swap and Kernel) are copied to the highest 4k of the respective process' memory space via DMA, data for the next runnable process is loaded into the Common area, and the new process is swapped into context by remapping the Bank Base value of the Z180's Memory Management Unit.

UZI180 kernel service calls are initiated by pushing necessary parameters and the desired function number on the stack and executing a Restart 30H. A jump to the service handler in common memory was placed at 0030H during process initialization. Since a Restart functions as a one-byte CALL, a return address is placed on the stack below the function number. The Service handler obtains the stack address, switches to a kernel stack in common memory, extracts information (function number and parameters) from the user stack, switches the kernel in context, and passes control to the kernel function process handler. When the function completes (except for _exit), status information is passed to the kernel exit code in common memory, the user process is restored to context along with the user stack, and the Restart "returns" to the user code. Parameters are pushed onto the stack in standard C form of right to left as specified in the function declaration (contrary to UZI and UZI280). This places the left-most declared parameter immediately above the return address on the user stack.

Executable programs in UZI display the CP/M roots of the system in that all programs begin execution at 100H. In melding the original Unix technique of testing the first character(s) of a file for "magic numbers" with CP/M, all executable programs in UZI require that the first instruction be a long jump (JP xxxx, opcode 0C3H). For native UZI applications, this header is contained in the crt.obj file linked with all executable files. UZI180 modifies this file to follow the jump with a text string containing the three letters 'UZI'. If these three letters are missing but the "magic number" 0C3H is present, then a CP/M executable is assumed and execution commences under a CP/M emulator described below.


In addition to the UZI kernel service calls used by native applications, a minimal CP/M 2.2 emulator is built into UZI180 to allow many existing CP/M and Z-System utilities to run under UZI180 without modification. The emulator intercepts CP/M BIOS and BDOS function requests and interprets them into equivalent UZI kernel calls, or processes them directly. To minimize the amount of memory consumed, only essential functions are included, and not all programs will function correctly. Some of the more significant limitations of the emulator are:

When needed, the emulator is moved into high process memory below the env and arg parameters. Instead of transferring execution to the program at 100H as an UZI executable, the process begins by executing the base code of the emulator. Code at this location is later overwritten by the CP/M stack since it is only used during process initialization. This sequence sets up CP/M vectors at 0H (Bios Warm Boot), 5H (Bdos service vector), catenates argv parameters into a capitalized command line tail in the default buffer starting at 0080H, and initializes other storage locations (Current Drive/User, FCBs, etc) before beginning execution at 100H. CP/M program termination by executing either a BIOS Warm Boot or BDOS function 0 will trigger an UZI _exit kernel function and terminate the process.

In the initial form, env parameters are not used by the emulator, but use of the PATH variable is planned to make the system more usable.


The bulk of UZI is programmed in the C language under the CP/M 2.2 or compatible operating system. Doug Braun's original UZI was written with the Codeworks' QC compiler which resulted in some odd interfaces since parameters were passed the system stack in backwards order (left-to-right) instead of the conventional right-to-left ordering used in the C programming language. UZI280 and UZI180 were both written for the Hi-Tech ANSI Standard compiler Version 3.09 released for non-commercial use in 1987 and included in Walnut Creek's CP/M CD-ROM. The Hi-Tech package also included a Z80/Z180 assembler, linker, librarian and source code for the CP/M runtime library. This system forms the complete development environment (less text editor) for UZI180.

The Hi-Tech Compiler requires a rather large Transient Program Area to compile the UZI sources. Consequently, users normally operating with the Z-System may need to remove several of the standard ZCPR3.x components to free up enough memory to compile the system. UZI180 was developed on the author's Banked and Portable (B/P) Bios system with banked ZSDOS2 and generated using autosizing of the system (BDOS and ZCPR) with no RCP or IOP. Since the compiler overwrites the Command Processor, the base of BDOS forms the upper limit of Transient Program Area which is 0E486H in the author's system. The BIOS Warm Boot vector is at 0EE03H. This is only slightly larger than a standard 62k CP/M system where the BDOS vector is at 0E405H and BIOS WB vector at 0F203H. A standard 62k CP/M system should be sufficient since the majority of initial work occured on a slightly smaller system.

Following Stefan Nitschke's lead with UZI280, assembly-language modules were added to several portions of kernel code to provide platform-dependant interfaces where necessary. Assembly code modules were also added to improve performance, particularly to minimize context switches and change parameter passing to the standard 'C' conventions of placing parameters on the stack from right-to-left. In some cases, these assembly modules were necessitated by apparent problems with the Hi-Tech compiler's ability to handle inline assembly with optimization turned on. To avoid severe performance penalties and code bloat, all modules were compiled with optimization turned ON.

When bringing up UZI180 for the first time, you must first extract all member files in the basic Kernel library module, and member files for the specific version for your platform. Begin by tailoring values in the version-specific files to reflect your needs, particularly the Hard Disk parameters in HDCONF.H. Check the Z180 initialization values in both Z180ASM.ASZ and ASMDEF.I to insure that such items as Serial Data rates and CPU clock frequency are accurate.

You must next Compile/Assemble all source code C and Assembly modules for the kernel. The simplest way to perform this is to execute the SUBmit script CMPILUZI.SUB. In selecting the software tools for this task, use either the original CP/M 2.2 SUBMIT.COM or an equivalent such as SUPERSUB. XSUB, EX and ZEX cannot be used since they install in high memory as a Resident System Extension (RSX) below the Command Processor, thereby reducing Transient Program Area space and creating problems for the compiler. Development of UZI180 used SUPERSUB under the B/P Bios/ZSDOS2 system described earlier. NOTE: with Operating Systems such as ZSDOS which do not re-log hard disk partitions on warm boots, the $$$.SUB temporary file is not re-detected to initiate the next line. Relogging of the hard disk can be forced in a ZSDOS system by executing the ZSDOS utility RELOG.COM after each line of the submit script. All but the last line of the script should reflect the appended command when placed in the Multiple Command Line buffer. An example compile command line from CMPILUZI.SUB is:

c -o -c -v process.c;relog
After each module is compiled/assembled, either manually or via the SUBmit script, the components must be linked into a loadable kernel image. A script is provided for this purpose which is read into the Hi-Tech linker. The script is set to produce an output file of UZI.COM which appears to CP/M as a normal COMmand file, but siezes complete control and becomes the UZI kernel when executed. This method of using the script with the Hi-Tech linker is needed to handle the case-sensitivity of segment names in the Hi-Tech relocatable format. Invoke the linker redirecting input from the script file as:
link <linkuzi
To aid in system maintenance and debugging, a symbol table (UZI.SYM) as well as the UZI.COM kernel image is also generated in this step.


Doug Braun provided several utilities which allow you to make an UZI filesystem and prepare it for use. These must be compiled and linked with modified versions of UZI kernel modules to form CP/M executable programs. Some of the kernel modules are simply compiled with the utility include files, while others such as the process and call modules are actually older versions of the kernel code with some modifications to run as CP/M programs instead of assuming total control of the computer. In the case of the core Floppy Disk driver, XFLOPASM.ASZ, hardware timeout via the Interrupt timer is removed for utilities in favor of countdown timers and letting the Floppy Disk motors remain running after the initial spinup.

The basic tools are:

MKFS - Make an UZI file system on a block device (HDx, FDx) and prepare it for use.

UDP - Display specified UZI File System Block Number on the Screen in Hex.

FSCK - Check an UZI file system on a block device for consistency.

UCP - Copy files between CP/M and an UZI storage device, type files to the CP/M Console, mount/umount another device into the UZI filesystem and delete files. If you are running ZSDOS with file date/time stamping, this program also adds date/time stamps to the UZI file system when copying files to the UZI file system.

Portions of the UCP utility were extracted into stand-alone UZI applications during UZI180 development to test various functions of the kernel, and to form the necessary core suite of utilities to make the system "feel" like a Unix system. These are also provided along with a rewritten LIBC library containing the UZI180 interfaces. A separate .DOC file details the steps needed to compile and link applications to form native UZI180 executables under CP/M. UCP may then be used to move the resulting executables into an UZI file system from where they may be executed.

THINGS TO DO  (Beta Notes, 18 August 1998 - HFB)