m68000 0.1.0

A Motorola 68000 interpreter, disassembler and assembler (code emitter)
docs.rs failed to build m68000-0.1.0
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: m68000-0.2.3

m68000

m68000 is a Motorola 68000 assembler, disassembler and interpreter written in Rust.

This library emulates the common user and supervisor instructions of the M68k ISA. It is configurable at compile-time to behave like the given CPU type (see below), changing the instruction's execution times and exception handling.

This library has been designed to be used in two different contexts:

  • It can be used to emulate a whole CPU, and the user of this library only have to call the interpreter methods and exception when an interrupt or reset occurs. This is the typical use case for an emulator.
  • It can also be used as a M68k user-land interpreter to run an M68k program, but without the requirement of having an operating system compiled to binary M68k. In this case, the application runs the program until an exception occurs (TRAP for syscalls, zero divide, etc.) and treat the exception in Rust code (or any other language using the C interface), so the application can implement the surrounding environment required by the M68k program in a high level language and not in M68k assembly.

Supported CPUs

The CPU type is specified at compile-time as a feature. There must be one and only one feature specified.

There are no default features. If you don't specify any feature or specify more than one, a compile-time error is raised.

  • MC68000 (feature "cpu-mc68000")
  • SCC68070 (feature "cpu-scc68070")

How to use

Include this library in your project and configure the CPU type by specifying the correct feature. It requires a nightly compiler as it uses the btree_drain_filter feature of the std.

Since the memory map is application-dependant, it is the user's responsibility to define it by implementing the MemoryAccess trait on their memory structure, and passing it to the core on each instruction execution.

The file src/bin/scc68070.rs is a usage example that implements the SCC68070 microcontroller.

Basic Rust example

const MEM_SIZE: u32 = 65536;
struct Memory([u8; MEM_SIZE as usize]); // Define your memory management system.

impl MemoryAccess for Memory { // Implement the MemoryAccess trait.
    fn get_byte(&mut self, addr: u32) -> Option<u8> {
        if addr < MEM_SIZE {
            Some(self.0[addr as usize])
        } else {
            None
        }
    }

    // And so on...
}

fn main() {
    let mut memory = Memory([0; MEM_SIZE as usize]);
    // Load the program in memory here.
    let mut cpu = M68000::new();

    // Execute instructions
    cpu.interpreter(&mut memory);
}

C interface

Build the C interface

This library has a C interface to generate a static library and use it in the language you want.

To generate the static library, simply build the project using the correct target toolchain.

cargo build --release --lib --features=cpu-scc68070

Change the CPU type you want to use by changing the last parameter of the previous command. To change the build toolchain, add +<toolchain name>. For example, to build it for windows targetting the MinGW compiler, type

cargo +nightly-x86_64-pc-windows-gnu build --release --lib --features=cpu-scc68070

To generate the C header file, it is recommended to use cbindgen, and to use the cbindgen.toml file provided in this repo. In a terminal, type the following command to generate the header file:

bindgen.exe --config .\cbindgen.toml --crate m68000 --output m68000.h

You can change the name of the file by changing the last parameter of the previous command.

Use the C interface

The complete documentation for the functions and structures can be found in the cinterface.rs module. See the C example below for a basic start.

Include the generated header file in your project, and define your memory access callback functions. These functions will be passed to the core through a M68000Callbacks struct.

The returned values are in a GetSetResult struct. Set GetSetResult.exception to 0 and set GetSetResult.data to the value to be returned on success. Set GetSetResult.exception to 2 (Access Error vector) if an Access Error occurs.

C example

#include "m68000.h"

#include <stdint.h>
#include <stdlib.h>

#define MEMSIZE (1 << 20) // 1 MB.


GetSetResult getByte(uint32_t addr, void* user_data)
{
    const uint8_t* memory = user_data;
    if(addr < MEMSIZE)
        return (GetSetResult){
            .data = memory[addr],
            .exception = 0,
        };

    // If out of range, return an Access (bus) error.
    return (GetSetResult){
        .data = 0,
        .exception = 2,
    };
}

GetSetResult getWord(uint32_t addr, void* user_data)
{
    const uint8_t* memory = user_data;
    if(addr < MEMSIZE)
        return (GetSetResult){
            .data = (uint16_t)memory[addr] << 8
                | (uint16_t)memory[addr + 1],
            .exception = 0,
        };

    // If out of range, return an Access (bus) error.
    return (GetSetResult){
        .data = 0,
        .exception = 2,
    };
}

GetSetResult getLong(uint32_t addr, void* user_data)
{
    const uint8_t* memory = user_data;
    if(addr < MEMSIZE)
        return (GetSetResult){
            .data = (uint32_t)memory[addr] << 24
                | (uint32_t)memory[addr + 1] << 16
                | (uint32_t)memory[addr + 2] << 8
                | memory[addr + 3],
            .exception = 0,
        };

    // If out of range, return an Access (bus) error.
    return (GetSetResult){
        .data = 0,
        .exception = 2,
    };
}

GetSetResult setByte(uint32_t addr, uint8_t data, void* user_data)
{
    uint8_t* memory = user_data;
    GetSetResult res = {
        .data = 0,
        .exception = 0,
    };

    if(addr < MEMSIZE)
        memory[addr] = data;
    else
        res.exception = 2;

    return res;
}

GetSetResult setWord(uint32_t addr, uint16_t data, void* user_data)
{
    uint8_t* memory = user_data;
    GetSetResult res = {
        .data = 0,
        .exception = 0,
    };

    if(addr < MEMSIZE)
    {
        memory[addr] = data >> 8;
        memory[addr + 1] = data;
    }
    else
        res.exception = 2;

    return res;
}

GetSetResult setLong(uint32_t addr, uint32_t data, void* user_data)
{
    uint8_t* memory = user_data;
    GetSetResult res = {
        .data = 0,
        .exception = 0,
    };

    if(addr < MEMSIZE)
    {
        memory[addr] = data >> 24;
        memory[addr + 1] = data >> 16;
        memory[addr + 2] = data >> 8;
        memory[addr + 3] = data;
    }
    else
        res.exception = 2;

    return res;
}

void reset(void* user_data) {}

int main()
{
    uint8_t* memory = malloc(MEMSIZE);
    // Check if malloc is successful, then load your program in memory here.
    // Next create the memory callback structure:
    M68000Callbacks callbacks = {
        .get_byte = getByte,
        .get_word = getWord,
        .get_long = getLong,
        .set_byte = setByte,
        .set_word = setWord,
        .set_long = setLong,
        .reset_instruction = reset,
        .user_data = memory,
    };

    M68000* core = m68000_new(); // Create a new core.
    // Now execute instructions as you want.
    m68000_interpreter(core, &callbacks);

    // end of the program.
    m68000_delete(core);
    free(memory);
    return 0;
}

License

m68000 is distributed under the terms of the LGPL-3.0 or any later version. Refer to the COPYING and COPYING.LESSER files for more information.