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:
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.
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.
```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
// 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);
} ```
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.
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
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;
} ```
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.