Emu8080: an HTML5 App to Emulate a Complete CP/M Machine

by Stefan Tramm

There are a lot of web-based computer emulators out there for ancient machines – the Commodore 64 and MSX home computers or the more exotic Space Invaders arcade machines. Such emulators are nice to play with but they are incomplete – they lack an Input/Output subsystem.

Emu8080 is the first known Javascript-based emulator which adds a floppy disk system, a fast paper tape reader/writer, a line printer and a VT100 terminal emulator to the mix. Modern web browsers offer a local WebSQL database, which is used to implement block storage devices. Drag-and-Drop is used to mount a desktop file onto the virtual tape device. Together with CPU and RAM emulation, you have a complete machine inside your web browser…

Want to try out? Use Chrome6+ (or Safari5) and visit http://www.tramm.li/i8080

Now you are working inside a classical machine monitor for an 8080 microcomputer from the 70’s. Available commands are:

  • m to modify memory
  • d to dump memory on the screen
  • l to disassemble a program (in Z80 Syntax)
  • g to start execution
  • <Enter> to perform a single step
  • x to show and modify CPU registers
  • h for help

So far this is pretty normal machine emulation.

The first important command is “r”. Without any parameters, it lists the available files on the tramm.li server. “r file” reads an Intel HEX file into memory, for example “r basic” will load the well-known Microsoft 8K BASIC interpreter for the Intel 8080.

But as already mentioned, a disk subsystem is also available. The first step in using it is to either insert a pre-formatted disk or to format an empty disk. To insert a pre-formatted CP/M disk, issue a “r 0 cpma” command.

This command expects the image of an 8-inch IBM formatted SS/SD diskette and writes it sector-wise into the browser-local WebSQL database. This needs to be done only once, causing the disk to now be loaded into your browser as Emu8080 drive 0. With the “b” command, the boot sector is loaded into “main memory”, starting at address 0. Now issue a “g” command to start execution. Ta da!, CP/M 2.2 has been loaded and started!

To go back to the monitor, you have two choices: Ctrl-. or via the “bye” executable.

Emu8080 supports four drives, so you can either format the other drives (“F” command) or search for other images on the internet and load them yourself. But how do we mount these drives? The “dsk” commands comes to rescue. It opens a new window, showing the current status of each drive. Every disk is also a drop-area, so you can mount disk images per drag-and-drop from your desktop into the emulator. Technically the FileReader API of Javascript is used, and this API is currently (November 2010) implemented only by Chrome (or Opera). Yes, you do not need the server anymore to transport data into a Javascript application.

Now you can mount disks and boot CP/M. You can edit files with ED.COM or Wordmaster, or write programs in 8080 assembler. But how to get the modified disk images out of the emulator again? The “dsk [n]” commands opens a new window, which allows to post the binary disk image to an arbitrary web server, e.g. localhost. (btw: there is no FileWriter API in HTML5). Just change the target URL and the form will post it where ever you want.

Sometimes mounting and posting whole images is overkill, transmitting a single file into the CP/M machine or getting it out may be all you want to do. And here comes the emulated paper tape device to rescue. Just drag a file onto the terminal’s main window and it will be mounted. Inside CP/M, just use the PIP command: “pip file.txt=rdr:” and you will have the tape’s content in FILE.TXT. Please keep in mind that CP/M is not 8-bit clean.

To get a single file out of the emulator, the paper tape puncher is used. Inside CP/M just use PIP again: “pip ptp:=rew.asm” to punch the file out. Then enter the monitor again (Ctrl-. or “bye”) and use the “ptp” command, which opens a window, where you either post the tape content to an arbitrary web server or simply copy-paste the content.

The final device is a line printer. It just opens a separate window where every character is shown. No escape sequences are implemented – therefore no bold or underlined characters.

In the unlikely case that the user has no clue about CP/M and its commands, the emulator includes a PDF of the original documentation by Digital Research.

Now, lets take a short look into the technical details:

  1. WebSQL: The emulator uses the browser local SQL DB as block storage for the four disk drives. The storage is persistent and writable.
  2. Drag-and-Drop / FileReader API: The emulator uses drag-and-drop and the FileReader API to read paper tape or disc images from your local machine.
  3. HTML5 manifest: The emulator supports the browser local database and browser local file reading. To make it also runnable without an active internet connection, it is built as a HTML 5 App by providing an emu8080.manifest file. This file tells the browser which resources (HTML, JS, CSS, PNG, HEX, CPM, PDF files) must be cached. This means that if you bookmark http://www.tramm.li/i8080/emu8080.html, you can start the emulator and access the disk drives even without an internet connection — just like a local application.
  4. ShellInABox: Without the fabulous VT100 emulation of ShellInABox, this emulator would not have been possible. The provided example of a BASIC interpreter was the starting point for the 8080 emulator.
  5. z80pack: The C based implementation of a Z80 machine was the basis for the IO-subsystem implementation, the custom BIOS, and the boot disk image. The trick for reducing host CPU cycles in tight IO-Loops (eg. when CP/M is waiting for a key press) was taken from this package.
  6. js8080: The Space Invaders emulator js8080 gifted the core CPU emulation.

One last word on performance. On a usual x86 processor the emulator performs like a 2 MHz 8080. With the monitor command “instr” you can tune the performance, by increasing the number of executed 8080 instructions per Javascript timeslice. With today’s hardware, you can expect the performance of a 8 MHz CPU (which was never available as real silicon).

And for Microsoft BASIC fans, try “r basic” and “g” afterwards instead of booting CP/M – welcome to 8K BASIC. Use “SAVE” to save your programs on the paper tape 🙂 and “LOAD” to read saved programs.

Neozeed provided the Zork adventure game disk image.

And now — happy hacking.

Other links:

12 thoughts on “Emu8080: an HTML5 App to Emulate a Complete CP/M Machine”

  1. I don’t know if it’s used in the project but last time I checked js8080 had buggy parity flag implementation.

    Reply
  2. Artur Piwko is correct. The implementation of parity test in js8080 is just plain wrong. Parity indicates if the number of set bits is even or odd. (http://en.wikipedia.org/wiki/Parity_flag). The js8080 implementation indicates if the number itself is even or odd.
    Counting bits in JavaScript can be a bit slow. However, since the 8080 parity test operates on an 8-bit value, it’s pretty easy to just create a 256 entry table of true/false values and use the value you’re testing as an index into the table:

    var parityTable = [
    true,false,false,true,false,true,true,false,false,true,true,false,true,false,false,true,
    false,true,true,false,true,false,false,true,true,false,false,true,false,true,true,false,
    false,true,true,false,true,false,false,true,true,false,false,true,false,true,true,false,
    true,false,false,true,false,true,true,false,false,true,true,false,true,false,false,true,
    false,true,true,false,true,false,false,true,true,false,false,true,false,true,true,false,
    true,false,false,true,false,true,true,false,false,true,true,false,true,false,false,true,
    true,false,false,true,false,true,true,false,false,true,true,false,true,false,false,true,
    false,true,true,false,true,false,false,true,true,false,false,true,false,true,true,false,
    false,true,true,false,true,false,false,true,true,false,false,true,false,true,true,false,
    true,false,false,true,false,true,true,false,false,true,true,false,true,false,false,true,
    true,false,false,true,false,true,true,false,false,true,true,false,true,false,false,true,
    false,true,true,false,true,false,false,true,true,false,false,true,false,true,true,false,
    true,false,false,true,false,true,true,false,false,true,true,false,true,false,false,true,
    false,true,true,false,true,false,false,true,true,false,false,true,false,true,true,false,
    false,true,true,false,true,false,false,true,true,false,false,true,false,true,true,false,
    true,false,false,true,false,true,true,false,false,true,true,false,true,false,false,true,
    ];

    // set the Parity flag
    function PF(v) {
    if( parityTable[ (v & 0xFF) ] )
    r8[iF] |= iPF;
    else
    r8[iF] &= ~(iPF);
    }

    Reply
  3. BDOS EQU 5
    WRITESTR EQU 9
    READSTR EQU 10

    ORG 0100H

    JMP PR$LGN
    PR$ERR:
    LXI D,ERR
    MVI C,WRITESTR
    CALL BDOS
    PR$LGN:
    LXI D,LGN
    MVI C,WRITESTR
    CALL 5

    LXI D,BUFFER
    MVI C,READSTR
    CALL BDOS

    MVI C,6
    LXI H,BUFFER+2
    MVI B,0DFh
    UCASE$LOOP:
    MOV A,M
    ANA B
    MOV M,A
    INX H
    DCR C
    JNZ UCASE$LOOP

    LDA BUFFER+3
    CPI ‘O’
    JNZ PR$ERR

    LDA BUFFER+1
    CPI 6
    JNZ PR$ERR

    MVI C,6
    MVI A,0
    LXI H,BUFFER+2

    CHK$LOOP:
    MOV B,M
    ADD B
    RLC
    INX H
    DCR C
    JNZ CHK$LOOP

    CPI 0B7H
    JNZ PR$ERR

    MVI A,’8′
    MOV M,A
    INX H

    MVI A,’0′
    MOV M,A

    LXI H,RCC
    MVI C,213
    MVI D,0

    DEC$LOOP:
    PUSH H
    LXI H,BUFFER+2
    MOV A,L
    ADD D
    MOV L,A
    MVI A,0
    ADC H
    MOV H,A

    MOV A,M

    POP H

    MOV B,M
    XRA B

    MOV M,A

    INR D
    MVI A,7
    ANA D
    MOV D,A

    INX H
    DCR C
    JNZ DEC$LOOP

    LXI D,RCC
    MVI C,WRITESTR

    CALL BDOS

    JMP 0 ;DONE

    LGN:

    DB 00Dh, 00Ah, 04Ch, 06Fh, 067h, 069h, 06Eh, 03Ah
    DB 020h, 024h
    ERR:

    DB 00Dh, 00Ah, 049h, 044h, 045h, 04Eh, 054h, 049h
    DB 046h, 049h, 043h, 041h, 054h, 049h, 04Fh, 04Eh
    DB 020h, 04Eh, 04Fh, 054h, 020h, 052h, 045h, 043h
    DB 04Fh, 047h, 04Eh, 049h, 053h, 045h, 044h, 020h
    DB 042h, 059h, 020h, 053h, 059h, 053h, 054h, 045h
    DB 04Dh, 00Dh, 00Ah, 02Dh, 02Dh, 043h, 04Fh, 04Eh
    DB 04Eh, 045h, 043h, 054h, 049h, 04Fh, 04Eh, 020h
    DB 054h, 045h, 052h, 04Dh, 049h, 04Eh, 041h, 054h
    DB 045h, 044h, 02Dh, 02Dh, 024h
    RCC:

    DB 047h, 045h, 014h, 01Ah, 010h, 004h, 06Ch, 079h
    DB 004h, 008h, 000h, 068h, 005h, 013h, 077h, 076h
    DB 00Fh, 01Ch, 000h, 007h, 007h, 061h, 07Eh, 071h
    DB 006h, 004h, 016h, 006h, 07Bh, 04Ch, 032h, 063h
    DB 002h, 00Eh, 01Fh, 004h, 075h, 016h, 07Dh, 010h
    DB 01Ah, 003h, 012h, 011h, 075h, 000h, 018h, 077h
    DB 00Bh, 002h, 016h, 077h, 058h, 04Bh, 061h, 07Fh
    DB 01Fh, 01Dh, 073h, 01Ch, 014h, 013h, 07Fh, 075h
    DB 01Eh, 06Fh, 01Ah, 01Bh, 075h, 000h, 06Ch, 010h
    DB 019h, 07Ch, 060h, 068h, 061h, 073h, 016h, 005h
    DB 07Fh, 078h, 073h, 00Dh, 064h, 074h, 008h, 010h
    DB 078h, 07Ah, 07Dh, 07Dh, 064h, 078h, 035h, 03Ah
    DB 00Fh, 017h, 007h, 01Ah, 014h, 061h, 070h, 079h
    DB 004h, 01Bh, 069h, 068h, 004h, 003h, 072h, 071h
    DB 06Ah, 016h, 011h, 002h, 075h, 003h, 079h, 010h
    DB 002h, 00Ch, 006h, 01Eh, 00Ch, 018h, 018h, 076h
    DB 01Ch, 01Eh, 001h, 045h, 05Fh, 015h, 070h, 075h
    DB 06Ah, 00Ch, 012h, 00Bh, 01Dh, 004h, 018h, 073h
    DB 005h, 001h, 007h, 009h, 01Ch, 00Fh, 06Bh, 010h
    DB 00Bh, 06Fh, 010h, 009h, 007h, 005h, 018h, 067h
    DB 003h, 01Bh, 01Bh, 068h, 014h, 061h, 068h, 078h
    DB 018h, 00Eh, 000h, 00Dh, 058h, 04Bh, 077h, 07Eh
    DB 06Ah, 006h, 007h, 068h, 001h, 009h, 079h, 064h
    DB 06Ah, 002h, 01Ah, 00Fh, 01Dh, 015h, 018h, 073h
    DB 005h, 002h, 016h, 068h, 01Ch, 00Fh, 018h, 078h
    DB 00Bh, 001h, 017h, 011h, 075h, 00Dh, 079h, 064h
    DB 00Fh, 01Dh, 05Eh, 042h, 071h
    BUFFER: DB 255
    DB 0
    DS 255

    END

    Reply

Leave a Comment