Browse Source

Started writing Chip8 emulator

Chris Konstad 3 years ago
commit
2fe26f1ae0
5 changed files with 274 additions and 0 deletions
  1. 1 0
      .gitignore
  2. 4 0
      Cargo.lock
  3. 14 0
      Cargo.toml
  4. 227 0
      src/lib.rs
  5. 28 0
      src/main.rs

+ 1 - 0
.gitignore

@ -0,0 +1 @@
1
target

+ 4 - 0
Cargo.lock

@ -0,0 +1,4 @@
1
[root]
2
name = "chip8"
3
version = "0.1.0"
4

													

+ 14 - 0
Cargo.toml

@ -0,0 +1,14 @@
1
[package]
2
name = "chip8"
3
version = "0.1.0"
4
authors = ["Chris Konstad <chriskon149@gmail.com>"]
5

													
6
[lib]
7
name = "libchip8"
8
path = "src/lib.rs"
9

													
10
[[bin]]
11
name = "chip8"
12
path = "src/main.rs"
13

													
14
[dependencies]

+ 227 - 0
src/lib.rs

@ -0,0 +1,227 @@
1
use std::mem;
2
use std::vec::Vec;
3

													
4
const NMEM : usize = 4096;
5
const NREG : usize = 16;
6
const NPIXELS : usize = 64 * 32;
7

													
8
/*
9
 * From http://www.multigesture.net/articles/how-to-write-an-emulator-chip-8-interpreter/
10
 * MEMORY MAP:
11
 * 0x000-0x1FF: Chip 8 interpreter
12
 * 0x050-0x0A0: 4x5 pixel font set (0-F)
13
 * 0x200-0xFFF: Program ROM and RAM
14
 */
15
static FONTSET : [u8;80] = [
16
  0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
17
  0x20, 0x60, 0x20, 0x20, 0x70, // 1
18
  0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
19
  0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
20
  0x90, 0x90, 0xF0, 0x10, 0x10, // 4
21
  0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
22
  0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
23
  0xF0, 0x10, 0x20, 0x40, 0x40, // 7
24
  0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
25
  0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
26
  0xF0, 0x90, 0xF0, 0x90, 0x90, // A
27
  0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
28
  0xF0, 0x80, 0x80, 0x80, 0xF0, // C
29
  0xE0, 0x90, 0x90, 0x90, 0xE0, // D
30
  0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
31
  0xF0, 0x80, 0xF0, 0x80, 0x80  // F
32
];
33

													
34
pub struct Chip8 {
35
    drawFlag: bool,
36
    opcode: u16,
37
    memory: [u8; NMEM],
38
    reg: [u8; NREG],
39
    index: u16,
40
    pc: u16,
41
    graphics: [u8; NPIXELS],
42
    timer_delay: u8,
43
    timer_sound: u8,
44
    stack: [u16; 16],
45
    sp: u16,
46
    key: [u8; 16],
47
}
48

													
49
impl Chip8 {
50
    pub fn new() -> Self {
51
        let mut chip = Chip8 {
52
            drawFlag: false,
53
            opcode: 0,
54
            memory: [0; NMEM],
55
            reg: [0; NREG],
56
            index: 0,
57
            pc: 0x200,
58
            graphics: [0; NPIXELS],
59
            timer_delay: 0,
60
            timer_sound: 0,
61
            stack: [0; 16],
62
            sp: 0,
63
            key: [0; 16],
64
        };
65

													
66
        // Initialize the font set
67
        for i in 0..80 {
68
            chip.memory[i] = FONTSET[i];
69
        }
70

													
71
        chip
72
    }
73

													
74
    pub fn loadHex(&mut self, game: &Vec<u8>) {
75
        for c in 0..game.len() {
76
            self.memory[c + 512] = game[c];
77
        }
78
    }
79

													
80
    pub fn loadGame(&mut self, game: String) {
81
        // TODO Fill in memory starting at 0x200
82
        unimplemented!();
83
    }
84

													
85
    pub fn emulateCycle(&mut self) {
86
        // Fetch opcode
87
        self.fetchOpcode();
88

													
89
        // Decode and Execute opcode
90
        self.executeOpcode();
91

													
92
        // Update timers
93
        self.updateTimers();
94
    }
95

													
96
    pub fn fetchOpcode(&mut self) {
97
        self.opcode = (self.memory[self.pc as usize] as u16) << 8 | self.memory[(self.pc + 1) as usize] as u16;
98
    }
99

													
100
    pub fn executeOpcode(&mut self) {
101
        // TODO Fill in table
102
        println!("Opcode: {:#X}", self.opcode);
103
        match self.opcode & 0xF000 {
104
            0x1000 => {
105
                // 0x1NNN: Jump to address NNN
106
                self.pc = self.opcode & 0x0FFF;
107
            }
108
            0x2000 => {
109
                // 0x2NNN: Call subroutine at NNN
110
                self.stack[self.sp as usize] = self.pc;
111
                self.sp += 1;
112
                self.pc = self.opcode & 0x0FFF;
113
            },
114
            0x3000 => {
115
                // 0x3XNN: Skip next instruction if regX equals NN
116
                let x = (self.opcode & 0x0F00) >> 8;
117
                let nn = (self.opcode & 0x00FF) as u8;
118
                if self.reg[x as usize] == nn {
119
                    self.pc += 2;
120
                }
121
                self.pc += 2;
122
            },
123
            0x8000 => {
124
                match self.opcode & 0x000F {
125
                    0x0004 => {
126
                        // 0x8XY4: Add regY to regX, set carry if needed
127
                        if self.reg[((self.opcode & 0x00F0) >> 4) as usize] >
128
                            (0xFF - self.reg[((self.opcode & 0x0F00) >> 8) as usize]) {
129
                            self.reg[0xF] = 1;
130
                        } else {
131
                            self.reg[0xF] = 0;
132
                        }
133
                        self.reg[((self.opcode & 0x0F00) >> 8) as usize] +=
134
                            self.reg[((self.opcode & 0x00F0) >> 4) as usize];
135
                        self.pc += 2;
136
                    }
137
                    _ => panic!("Opcode {:#X} is bad", self.opcode),
138
                }
139
            },
140
            0xA000 => {
141
                // 0xANNN: Sets I to the address NNN
142
                self.index = self.opcode & 0x0FFF;
143
                self.pc += 2;
144
            },
145
            0xB000 => {
146
                // 0xBNNN: Jump to address NNN + reg0
147
                let address = self.opcode & 0x0FFF;
148
                self.pc = address + self.reg[0] as u16;
149
            }
150
            _ => panic!("Opcode {:#X} is bad", self.opcode),
151
        }
152
    }
153

													
154
    pub fn updateTimers(&mut self) {
155
        if(self.timer_delay > 0) {
156
            self.timer_delay -= 1;
157
        }
158

													
159
        if(self.timer_sound > 0) {
160
            if(self.timer_sound == 1) {
161
                // TODO beep
162
                unimplemented!();
163
            }
164
            self.timer_sound -= 1;
165
        }
166
    }
167
}
168

													
169
#[cfg(test)]
170
mod test {
171
    use super::Chip8;
172

													
173
    #[test]
174
    fn op1nnn() {
175
        let mut chip = Chip8::new();
176
        chip.loadHex(&vec![0x16, 0x66]);
177
        assert_eq!(chip.pc, 512);
178
        chip.emulateCycle();
179
        assert_eq!(chip.pc, 0x666);
180
    }
181

													
182
    #[test]
183
    fn op2nnn() {
184
        let mut chip = Chip8::new();
185
        chip.loadHex(&vec![0x26, 0x66]);
186
        assert_eq!(chip.pc, 512);
187
        chip.emulateCycle();
188
        assert_eq!(chip.pc, 0x666);
189
        assert_eq!(chip.stack[0], 512);
190
        assert_eq!(chip.sp, 1);
191
    }
192

													
193
    #[test]
194
    fn op3nnn() {
195
        let mut chip = Chip8::new();
196
        chip.loadHex(&vec![0x31, 0x66, 0x31, 0x67]);
197
        chip.reg[1] = 0x67;
198
        assert_eq!(chip.pc, 512);
199
        chip.emulateCycle();
200
        assert_eq!(chip.pc, 514);
201
        chip.emulateCycle();
202
        assert_eq!(chip.pc, 518);
203
    }
204

													
205
    #[test]
206
    fn opAnnn() {
207
        let mut chip = Chip8::new();
208
        chip.loadHex(&vec![0xA6, 0x66]);
209
        assert_eq!(chip.index, 0);
210
        assert_eq!(chip.pc, 512);
211
        chip.emulateCycle();
212
        assert_eq!(chip.index, 0x666);
213
        assert_eq!(chip.pc, 514);
214
    }
215

													
216
    #[test]
217
    fn opBnnn() {
218
        let mut chip = Chip8::new();
219
        chip.loadHex(&vec![0xB6, 0x66]);
220
        chip.reg[0] = 0x5;
221
        assert_eq!(chip.index, 0);
222
        assert_eq!(chip.pc, 512);
223
        chip.emulateCycle();
224
        assert_eq!(chip.index, 0);
225
        assert_eq!(chip.pc, 0x666 + 0x5);
226
    }
227
}

+ 28 - 0
src/main.rs

@ -0,0 +1,28 @@
1
extern crate libchip8;
2

													
3
use libchip8::Chip8;
4

													
5
fn main() {
6
    println!("Chip8 emulator in Rust");
7

													
8
    // TODO Setup the render system
9
    // setupGraphics();
10
    // setupInput();
11

													
12
    // Initialize the emulator and load the game
13
    let mut chip = Chip8::new();
14
    //chip.loadGame("pong");
15

													
16
    // Emulation loop
17
    loop {
18
        chip.emulateCycle();
19

													
20
        //if(chip.drawFlag) {
21
            // TODO
22
            // drawGraphics();
23
        //}
24

													
25
        // Store key press state
26
        //chip.setKeys();
27
    }
28
}