initial commit
This commit is contained in:
commit
4a22467df5
|
@ -0,0 +1 @@
|
|||
/target
|
|
@ -0,0 +1,16 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "eis"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "eis"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.4.3"
|
|
@ -0,0 +1,9 @@
|
|||
# Binary program format
|
||||
|
||||
The current binary format is super simple, because all CPU state (registers) is part of the memory.
|
||||
That means you can think of it like this: There is a header (containing the registers), and a memory block.
|
||||
My suggestion is to just put the program instructions right after the header, and put stack and heap wherever you want (by setting the registers).
|
||||
Don't forget to point `ip` to the start of your program!
|
||||
|
||||
In the future, I might consider some sort of mapping format, but honestly, why make it complicated?
|
||||
(For ASLR, I will need to define a range or something...)
|
|
@ -0,0 +1,54 @@
|
|||
# ISA
|
||||
|
||||
## Current impl
|
||||
|
||||
| opcode | instruction |
|
||||
| --- | --- |
|
||||
| 0x10 | Input |
|
||||
| 0x11 | Output |
|
||||
| 0x20 | Mov |
|
||||
| 0x90 | Nop |
|
||||
|
||||
## Basics
|
||||
|
||||
Input, Output, Mov
|
||||
|
||||
## Math
|
||||
|
||||
Add, Sub, Mov, Div (will need flags...)
|
||||
|
||||
## Conditions
|
||||
|
||||
Cmp, Jcc
|
||||
|
||||
## Stack
|
||||
|
||||
Push, Pop
|
||||
|
||||
## Funcion stuff
|
||||
|
||||
Call, Ret
|
||||
|
||||
## x86
|
||||
|
||||
| opcode | instruction |
|
||||
| --- | --- |
|
||||
| 0x00 - 0x05 | ADD |
|
||||
| 0x06 | PUSH |
|
||||
| 0x07 | POP |
|
||||
| 0x08 - 0xD | OR |
|
||||
| 0x0E | PUSH |
|
||||
| 0x0F | POP |
|
||||
| 0x10 - 0x15 | ADC |
|
||||
| 0x16 | PUSH |
|
||||
| 0x17 | POP |
|
||||
| 0x18 - 0x1D | SBB |
|
||||
| 0x1E | PUSH |
|
||||
| 0x1F | POP |
|
||||
| 0x20 - 0x25 | AND |
|
||||
| 0x26 | ??? |
|
||||
| 0x27 | DAA |
|
||||
| 0x28 - 0x2D | SUB |
|
||||
| 0x2E | ??? |
|
||||
| 0x2F | DAS |
|
||||
| ... | ... |
|
|
@ -0,0 +1,176 @@
|
|||
use byteorder::{ReadBytesExt, LE};
|
||||
use std::io::Result;
|
||||
|
||||
type Word = u16;
|
||||
//type Memory = [Word; Word::MAX as usize + 1];
|
||||
|
||||
struct Memory {
|
||||
words: [Word; Word::MAX as usize + 1],
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
fn get(&self, index: Word) -> Word {
|
||||
self.words[index as usize]
|
||||
}
|
||||
|
||||
fn get_ref(&self, index: Word) -> Word {
|
||||
// equivalent to get(get(index))
|
||||
self.words[self.words[index as usize] as usize]
|
||||
}
|
||||
|
||||
fn put(&mut self, index: Word, value: Word) {
|
||||
self.words[index as usize] = value;
|
||||
}
|
||||
|
||||
//fn put_ref(&mut self, index: Word, value: Word) {}
|
||||
|
||||
fn new(path_str: &str) -> Memory {
|
||||
let mut file = match std::fs::File::open(path_str) {
|
||||
Result::Ok(x) => x,
|
||||
Result::Err(_) => panic!("could not open program"),
|
||||
};
|
||||
let mut mem = Memory {
|
||||
words: [0x0; u16::MAX as usize + 1],
|
||||
};
|
||||
let mut index = 0;
|
||||
loop {
|
||||
match file.read_u16::<LE>() {
|
||||
Result::Ok(word) => {
|
||||
mem.words[index] = word;
|
||||
//println!("DEBUG: WORD: {:#06x}", word);
|
||||
index += 1;
|
||||
}
|
||||
Result::Err(error) => match error.kind() {
|
||||
std::io::ErrorKind::UnexpectedEof => {
|
||||
//println!("DEBUG: EOF");
|
||||
break;
|
||||
}
|
||||
other_error => panic!("Other error: {:?}", other_error),
|
||||
},
|
||||
}
|
||||
}
|
||||
mem
|
||||
}
|
||||
}
|
||||
|
||||
struct Cpu {
|
||||
//ax: Word, // Accumulator
|
||||
//bx: Word, // Base
|
||||
//cx: Word, // Counter
|
||||
//dx: Word, // Data
|
||||
|
||||
//si: Word, // Source Index
|
||||
//di: Word, // Destination Index
|
||||
//bp: Word, // Base Pointer
|
||||
//sp: Word, // Stack Pointer
|
||||
ip: Word, // Instruction Pointer
|
||||
|
||||
// Segment & Status registers ignored for now
|
||||
memory: Memory, // for convenience, the CPU has ownership of the memory
|
||||
|
||||
//registers are part of the memory now...
|
||||
|
||||
// TODO proper input handling (probably read from command line)
|
||||
input: Vec<u8>,
|
||||
input_index: usize,
|
||||
}
|
||||
|
||||
impl Cpu {
|
||||
fn _read_input(&mut self) -> Word {
|
||||
// TODO length check or something...
|
||||
let byte = self.input[self.input_index] as Word;
|
||||
self.input_index += 1;
|
||||
//println!("DEBUG: read byte {}", byte);
|
||||
byte
|
||||
}
|
||||
fn _first_arg(&self) -> Word {
|
||||
self.memory
|
||||
.get(match self.memory.get(self.ip).checked_add(1) {
|
||||
Some(x) => x,
|
||||
None => panic!("malformed instruction at {}", self.memory.get(self.ip)),
|
||||
})
|
||||
}
|
||||
|
||||
fn _second_arg(&self) -> Word {
|
||||
self.memory
|
||||
.get(match self.memory.get(self.ip).checked_add(2) {
|
||||
Some(x) => x,
|
||||
None => panic!("malformed instruction at {}", self.memory.get(self.ip)),
|
||||
})
|
||||
}
|
||||
|
||||
fn step(&mut self) {
|
||||
//println!("DEBUG: IP: {}", self.memory.get(self.ip));
|
||||
// fetch and decode instruction
|
||||
let (op, len) = match self.memory.get_ref(self.ip) {
|
||||
0x10 => (Op::Input(self._first_arg()), 2),
|
||||
0x11 => (Op::Output(self._first_arg()), 2),
|
||||
0x20 => (Op::Mov(self._first_arg(), self._second_arg()), 3),
|
||||
0x90 => (Op::Nop, 1),
|
||||
0xFF => (Op::Exit(self._first_arg()), 2),
|
||||
x => panic!("unknown instruction {} at {}", x, self.memory.get(self.ip)),
|
||||
};
|
||||
|
||||
// increment ip
|
||||
self.memory
|
||||
.put(self.ip, self.memory.get(self.ip).wrapping_add(len));
|
||||
|
||||
// execute instruction
|
||||
match op {
|
||||
Op::Input(address) => {
|
||||
//println!("DEBUG: INPUT: {}", address);
|
||||
let x = self._read_input();
|
||||
self.memory.put(address, x);
|
||||
}
|
||||
Op::Mov(dest, src) => self.memory.put(dest, self.memory.get(src)),
|
||||
Op::Nop => {}
|
||||
Op::Output(address) => {
|
||||
//println!("DEBUG: OUTPUT: {}", address);
|
||||
print!("{} ", self.memory.get(address));
|
||||
}
|
||||
Op::Exit(status) => std::process::exit(status as i32),
|
||||
}
|
||||
}
|
||||
|
||||
fn new(memory: Memory) -> Cpu {
|
||||
// TODO number registers, leave 0 for NULL
|
||||
// TODO either put at end of address space
|
||||
// or offset programs
|
||||
Cpu {
|
||||
ip: 9,
|
||||
memory,
|
||||
input: vec![54, 65, 73, 74], // TODO proper input from command line or stdin
|
||||
input_index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Op {
|
||||
Input(Word),
|
||||
Mov(Word, Word),
|
||||
Nop,
|
||||
Output(Word),
|
||||
Exit(Word),
|
||||
}
|
||||
|
||||
/*
|
||||
impl Op {
|
||||
fn length(&self) -> Word {
|
||||
match *self {
|
||||
Op::Nop => 1,
|
||||
Op::Input(_) | Op::Output(_) => 2,
|
||||
Op::Mov(_, _) => 3,
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let memory = Memory::new(&args[1]);
|
||||
let mut cpu = Cpu::new(memory);
|
||||
|
||||
for _ in 0..=255 {
|
||||
cpu.step();
|
||||
}
|
||||
println!();
|
||||
}
|
Binary file not shown.
Loading…
Reference in New Issue