9 breaking releases

Uses new Rust 2021

0.14.0 Dec 30, 2021
0.12.0 Nov 6, 2021

#16 in Emulators

Download history 63/week @ 2022-04-26 53/week @ 2022-05-03 153/week @ 2022-05-10 34/week @ 2022-05-17 52/week @ 2022-05-24 84/week @ 2022-05-31 22/week @ 2022-06-07 2/week @ 2022-06-14 6/week @ 2022-06-21 2/week @ 2022-06-28 12/week @ 2022-07-05 22/week @ 2022-07-12 13/week @ 2022-07-19 23/week @ 2022-07-26 44/week @ 2022-08-02 7/week @ 2022-08-09

88 downloads per month
Used in teletype

MIT license

160KB
3.5K SLoC

intel8080

Current Crates.io Version Current docs Version Downloads badge

Yet another Intel 8080 Emulator. It passes the TST8080, 8080PRE, CPUTEST and 8080EXM tests.

Example for a small loop:

use intel8080::CPU;
let mut c = CPU::new();
c.pc = 0x0100;                      // sets pc to $100
// Here we create a small machine code program for demo purpose.
// Usually you will rather load an assembled code in memory (see below).
c.bus.write_byte(0x0100, 0x3e);     // MVI A,$0F
c.bus.write_byte(0x0101, 0x0F);
c.bus.write_byte(0x0102, 0x3d);     // DCR A
c.bus.write_byte(0x0103, 0xc2);     // JNZ $0102
c.bus.write_word(0x0104, 0x0102);
c.bus.write_byte(0x0106, 0xc9);     // RET
loop {
    c.execute();
    if c.pc == 0x0000 { break }
}

You can load assembled programs from disk to memory:

c.pc = 0x0100;                                      // sets pc to $100
c.bus.load_bin("bin/loop.bin", 0x100).unwrap();     // loads file at address $100
loop {
    c.execute();
    if c.pc == 0x0000 { break }
}

It's easy to create an interrupt request:

c.bus.load_bin("bin/interrupt.bin", 0).unwrap();
c.int = (true, 0xcf);               // we create an interrupt request : flag set to true
loop {                              // and its associated RST command
    c.execute();                    // test program is designed to never leave a loop
    if c.pc == 0x0000 { break }     // if it does not execute the interrupt routine
}

Starting with 0.8.0, a more stabilized I/O system:

c.bus.write_byte(0x0000, 0x3e);     // MVI A,$55
c.bus.write_byte(0x0001, 0x55);
c.bus.write_byte(0x0002, 0xd3);     // OUT 1
c.bus.write_byte(0x0003, 0x01);
loop {
    c.execute();
    // Data sent from CPU to device 1 (OUT) ? let's handle it
    if let Some(v) = c.bus.get_io_out(1) {
        assert_eq!(v, 0x55);
        // OUT handled ? let's clear it
        c.bus.clear_io_out();
    }
    if c.pc == 0x0004 { break }
}

The I/O system has been improved in 0.14.0, you can now use callbacks:

c.set_cb_out(out_callback);
...
fn out_callback(c: &mut CPU, device: u8, data: u8) -> Option<u8> {
    your callback code here
}

The 0.8.0 I/O system still works if no callback is set.

In version 0.13.0, a field has been added to define a read-only area in the address space:

use intel8080::{CPU, memory::ROMSpace};
let mut c = CPU::new();
c.bus.rom_space = Some(ROMSpace{start: 0xfff0, end: 0xffff});

Debug mode outputs CPU state and disassembled code to an internal string after each execute():

3E 0f     MVI A,$0f
PC : 0x0003	SP : 0xff00	S : 0	Z : 0	A : 0	P : 0	C : 0
B : 0x00	C : 0x00	D : 0x00	E : 0x00	H : 0x00	L : 0x00 ...

Includes a "cpmloader" which loads and executes basic CP/M programs:

cargo run --release --example cpmloader -- bin/helloworld.bin

You can also check my Altair 8800 / 88-SIO / teletype emulator.

The provided source code examples can be assembled with Retro Assembler.

License: MIT

Dependencies

~33KB