10 Nov 2014

New Adventures in 8-Bit Land

Alan Cox’ recent announcement of his Unix-like operating system for old home computers got me thinking: wouldn’t it be cool write programs for the KC85/3 in C, a language it never officially supported?

For youngsters and Westerners: the KC85 home computer line was built in the 80’s in Eastern Germany, the most popular version, the KC85/3 had a 1.75MHz Z80-compatible CPU, and 16kByte each of general RAM, video RAM and ROM (so 32kByte RAM and 16kByte ROM). The ROM was split in half, 8kByte BASIC, 8kByte OS. Display was 320x256 pixels, a block of 8x4 pixels could have 1-out-of-16 foreground and 1-out-of-8 background colors. No sprite support, no dedicated sound chip, and the video RAM layout was extra-funky and had very slow CPU access.

MAME/MESS has rudimentary support for the KC85 line (and many other computers built behind the Iron Curtain) and I dabbled with the KC85 emulation in JSMESS a while ago, as can be seen here: http://www.flohofwoe.net/history.html. So far this dabbling was all about running old games on old (emulated) machines.

New Code on Old Machines

But what about running new code on old machines? And not just Z80 assembler code, but ‘modern’ C99 code?

Good 8-bit C compilers are surprisingly easy to find, since the Z80 lived on well into the 2000s for embedded systems. I first started looking for a Z80 LLVM backend, but after some more googling I decided to go for SDCC, which looks like the ‘industry standard’ for 8-bit CPUs and is still actively developed.

On OSX, a recent SDCC can be installed with brew:

> brew install sdcc

After I played a few minutes with the compiler I decided that starting right with C is a few steps too far.

MESS

First I had to get MESS running again. MESS is the son of MAME, focusing on vintage computer emulation instead of arcade machines. Since I last used it, MESS had been merged back into MAME, and development has been moved onto github: https://github.com/mamedev/mame

So first, git-clone and compile mess:

> git clone git@github.com:mamedev/mame.git mame
> cd mame
> make TARGET=mess

This produces a ‘mess64’ executable on OSX. Next KC85/3 and /4 system ROMs are needed, these can be found by googling for ‘kc85_3.zip MESS’ (for what it’s worth, I consider these ROMs abandonware). With the compiled mess and the ROMs, a KC85/3 session can now be started in MESS:

>./mess64 kc85_3 -rompath . -window -resolution 640x512

And here we go:
enter image description here

Getting stuff into MESS

Next we need to figure out how to get code onto the emulator. The KC85 operating system ‘CAOS’ (for **C**assete **A**ided **O**perating **S**ystem - yes even East-German engineers had a sense for humor) didn’t have an ‘executable format’ like ELF, instead raw chunks of code and data were loaded from cassette tapes into memory. There was however a standardised format of how the data was stored on tape. Divided into chunks of 128 bytes, with the first chunk being the header with information at which address to load the following data. This tape format has survived as the ‘KCC file format’, where the first 128-byte chunk looks like this (taken from the kc85.c MESS driver source code):

struct kcc_header
{
    UINT8   name[10];
    UINT8   reserved[6];
    UINT8   number_addresses;
    UINT8   load_address_l;
    UINT8   load_address_h;
    UINT8   end_address_l;
    UINT8   end_address_h;
    UINT8   execution_address_l;
    UINT8   execution_address_h;
    UINT8   pad[128-2-2-2-1-16];
};

A .KCC file can be loaded into MESS using the -quik command line arg, e.g.:

>./mess64 kc85_3 -quik test.kcc -rompath . -window -resolution 640x512

So if we had a piece of KC85/3 compatible machine code, and put it into a file with a 128-byte KCC header in front, we should be able to load this into the emulator.

The canonical ‘Hello World’ program for the KC85/3 looks like this in Z80 machine code:

0x7F 0x7F 'HELLO' 0x01
0xCD 0x03 0xF0 
0x23
'Hello World\n\r' 0x00
0xC9

That’s a complete ‘Hello World’ in 27 bytes! Put these bytes somewhere in the KC85’s RAM, and after executing the command ‘MENU’ a new menu entry will show up named ‘HELLO’. To execute the program, type ‘HELLO’ and hit Enter:
enter image description here

How does this magic work? At the start is a special ‘7F 7F’ header which identifies these 27 bytes as a command line program called ‘HELLO’:

0x7F 0x7F 'HELLO' 0x01

Execution starts right after the 0x01 byte:

0xCD 0x03 0xF0 
0x23

The CD is the machine code of the Z80 subroutine-call instruction, followed by the call-target address 0xF003 (the Z80 is little-endian, like the x86), this is a call to a central operating system ‘jump vector’, the 0x23 identifies the operating system function, in this case the function is OSTR for ‘Output STRing’ (see page 43 of the system manual). This function outputs a string to the current cursor position. The string is not provided as a pointer, but directly embedded into the code after the call and terminated with a zero byte:

'Hello World\n\r' 0x00

After the operating system function has executed, it will resume execution after the string’s 0-terminator byte.

The final C9 byte is the Z80 RETurn statement, which will give control back to the operating system.

This was the point where I started to write a bit of Python code which take a chunk of Z80 code, puts a KCC header in front and writes it to a .kcc file. And indeed the MESS loader accepted such a self-made ‘executable’ without problems.

Mnemonics

Before tackling the C programming challenge I decided to start smaller, with Z80 assembly code. The SDCC compiler comes (among others) with a Z80 assembler, but I found this hard to use (for instance, it generates intermediate ASCII Intel HEX files instead of raw binary files).

After some more googling I found z80asm which looked solid and easy to use. Again this can be installed via brew:

> brew install z80asm

The simple Hello World machine code blob from above looks like this in Z80 assembly mnemonics:

    org 0x200 ; start at address 0x200
    db 0x7F,0x7F,"HELLO",1 
    call 0xF003
    db 0x23
    db "Hello World\r\n\0"
    ret

Much easier to read right? And even with comments! Running this file through z80asm yields a binary files with the exact same 27 bytes as the hand-crafted machine code version:

> z80asm hello.s -o hello.bin
> hexdump hello.bin
0000000 7f 7f 48 45 4c 4c 4f 01 cd 03 f0 23 48 65 6c 6c
0000010 6f 20 57 6f 72 6c 64 0d 0a 00 c9               
000001b

With some more Python plumbing I was then able to ‘cross-assemble’ new programs for the KC85 in a modern development environment. Very cool!

C99

But the real challenge remains: compiling and running C code! Compiling a C source through SDCC generates a lot of output files, but none of them is the expected binary blob of executable code:

> sdcc hello.c 
> ls
hello.asm   hello.ihx   hello.lst   hello.mem   hello.rst
hello.c     hello.lk    hello.map   hello.rel   hello.sym

There’s 2 interesting files: hello.asm is a human-readable assembler source file, and hello.ihx is the final executable, but in Intel HEX format. The .ihx file can be converted into a raw binary blob using the makebin program also coming with SDCC.

But even with a very simple C program there’s already a few things off:
- global variables are placed at address 0x8000 (32kBytes into the address space), on the KC85/3 this is video memory so the default address for data wouldn’t work
- if any global variables are initialized, then the resulting binary file is also at least 32 kBytes big, and has a lot of empty space inside
- there’s a few dozen bytes of runtime initialization code which isn’t needed in the KC85 environment (at least as long as we don’t want to use the C runtime)

Thankfully SDCC allows to tweak all this and allows to compile (and link) pieces of C code into raw blobs of machine code without any ‘runtime overhead’, it doesn’t even need a main function to produce a valid executable.

Currently I’m placing global data at address 0x200, and code at address 0x300 (so there’s 256 bytes for global data), and I’m disabling anything C-runtime related. And of course we need to tell the compiler to generate Z80 code, these are the important command line options for sdcc:

-mz80
--no-std-crt0 --nostdinc --nostdlib
--code-loc 0x300
--data-loc 0x200

With these compiler settings I’m getting the bare-bones Z80 code I want on the KC85. All that’s left now is some macros and system call wrapper functions to provide a KC-style runtime enviornment, and TADAA:

C99 programming on a 30 year old 8-bit home computer :D

Here’s the github link to the ‘kc85sdk’ (work in progress):

https://github.com/floooh/kc85sdk

Written with StackEdit.

5 comments:

stuaxo said...

Very nice ! I wonder if there is a stdlib you can use from one of the other 8 bit computers.

ZX Spectrum would be a good place to start since it also Z80

Tom Li said...

No, exactly it is not C99-programming.

SDCC is not a full-functional compiler, and lacks even the complete support of C99, or even ANSI C. It is just a compiler aimed at small devices for simplify programming on single-chip micro-controllers.

Some long-term issues were never been fixed, and I just crashed (SIGSEGV) the compiler after I wrote my second C program for my calculator.

The right way to go is a complete port of standard Unix toolchains: GCC, Binutils and LLVM. LLVM support is more valuable because once it finished it will be possible to run any language that built on LLVM.

Sure, it is not an easy task, but not impossible. AVR community has accomplished that, thus everyone has a good experience on Arduino development :)

Robert J said...

Is there any chance you will continue writing blog posts? I really enjoy reading your technical explanations.

akmal niazi khan said...

This blog awesome and i learn a lot about programming from here.The best thing about this blog is that you doing from beginning to experts level.

Love from

A A said...

The z80 world already has something akin to gcc and llvm through z88dk with sdcc as compiler. You can read a little about it here: http://www.z88dk.org/wiki/doku.php?id=libnew:target_embedded