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:

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.

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

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

impl MemoryAccess for Memory { // Implement the MemoryAccess trait. fn getbyte(&mut self, addr: u32) -> Option { if addr < MEMSIZE { 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. sh 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 sh 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: sh 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

```c

include "m68000.h"

include

include

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

GetSetResult getByte(uint32t addr, void* userdata) { const uint8t* memory = userdata; 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(uint32t addr, void* userdata) { const uint8t* memory = userdata; if(addr < MEMSIZE) return (GetSetResult){ .data = (uint16t)memory[addr] << 8 | (uint16t)memory[addr + 1], .exception = 0, };

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

}

GetSetResult getLong(uint32t addr, void* userdata) { const uint8t* memory = userdata; if(addr < MEMSIZE) return (GetSetResult){ .data = (uint32t)memory[addr] << 24 | (uint32t)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(uint32t addr, uint8t data, void* userdata) { uint8t* memory = user_data; GetSetResult res = { .data = 0, .exception = 0, };

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

return res;

}

GetSetResult setWord(uint32t addr, uint16t data, void* userdata) { uint8t* 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(uint32t addr, uint32t data, void* userdata) { uint8t* 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() { uint8t* memory = malloc(MEMSIZE); // Check if malloc is successful, then load your program in memory here. // Next create the memory callback structure: M68000Callbacks callbacks = { .getbyte = getByte, .getword = getWord, .getlong = getLong, .setbyte = setByte, .setword = setWord, .setlong = setLong, .resetinstruction = 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.