1#![deny(clippy::all)]
2#![warn(clippy::pedantic)]
3#![deny(clippy::as_conversions)]
4#![warn(clippy::nursery)]
5#![warn(clippy::cargo)]
6
7use std::{thread::sleep, time::Duration, vec};
8
9use anyhow::{bail, Context, Ok, Result};
10use log::debug;
11
12pub const TERMINAL_WIDTH: usize = 64;
14pub const TERMINAL_HEIGHT: usize = 32;
16pub const FPS: u64 = 60;
18const RAM_SIZE: usize = 4096;
19const PROGRAM_START: usize = 512;
20
21const FONT_ADDR: usize = 0x50;
23const FONT_SIZE: usize = 5;
24const FONTS: [u8; 80] = [
25 0xF0, 0x90, 0x90, 0x90, 0xF0, 0x20, 0x60, 0x20, 0x20, 0x70, 0xF0, 0x10, 0xF0, 0x80, 0xF0, 0xF0, 0x10, 0xF0, 0x10, 0xF0, 0x90, 0x90, 0xF0, 0x10, 0x10, 0xF0, 0x80, 0xF0, 0x10, 0xF0, 0xF0, 0x80, 0xF0, 0x90, 0xF0, 0xF0, 0x10, 0x20, 0x40, 0x40, 0xF0, 0x90, 0xF0, 0x90, 0xF0, 0xF0, 0x90, 0xF0, 0x10, 0xF0, 0xF0, 0x90, 0xF0, 0x90, 0x90, 0xE0, 0x90, 0xE0, 0x90, 0xE0, 0xF0, 0x80, 0x80, 0x80, 0xF0, 0xE0, 0x90, 0x90, 0x90, 0xE0, 0xF0, 0x80, 0xF0, 0x80, 0xF0, 0xF0, 0x80, 0xF0, 0x80, 0x80, ];
42
43#[derive(Debug, Default)]
45pub struct Chip8 {
46 clock: u64,
47 pixels: Vec<Vec<bool>>,
48 ram: Vec<u8>,
49 pc: usize,
50 i: usize,
51 stack: Vec<usize>,
52 registers: [u8; 16],
53 delay_timer: u8,
54 sound_timer: u8,
55 beeping: bool,
56 key_pressed: Option<u8>,
57 waiting_for_input: Option<usize>,
58}
59
60#[derive(Debug, Clone, Copy)]
62enum Instruction {
63 Cls00E0,
64 SetIndexRegisterANNN(usize),
65 SetVRegister6XNN(usize, u8),
66 Dxyn(usize, usize, usize),
67 Add7XNN(usize, u8),
68 Jump1NNN(u16),
69 SubroutineCall2NNN(u16),
70 SubroutineReturn00EE,
71 SkipEqual3XNN(usize, u8),
72 SkipNotEqual4XNN(usize, u8),
73 BinaryCodedDecimalConversionFX33(usize),
74 FontCharacterFX29(usize),
75 SetDelayTimerFX15(usize),
76 ReadDelayTimerFX07(usize),
77 GetKeyFX0A(usize),
78 SetSoundTimerFX18(usize),
79 AddToIndexFX1E(usize),
80 StoreRegistersToMemoryFX55(usize),
81 LoadRegistersFromMemoryFX65(usize),
82 RandomCXNN(usize, u8),
83 SkipIfKeyPressedEX9E(usize),
84 SkipIfKeyNotPressedEXA1(usize),
85 BinaryAnd8XY2(usize, usize),
86 RegisterAdd8XY4(usize, usize),
87 RegisterSet8XY0(usize, usize),
88 RegisterSub8XY5(usize, usize),
89 RegisterSubRev8XY7(usize, usize),
90 ShiftRight8XY6(usize, usize),
91 ShiftLeft8XYE(usize, usize),
92 SkipIfEqual5XY0(usize, usize),
93 SkipIfNotEqual9XY0(usize, usize),
94 Xor8XY3(usize, usize),
95}
96
97impl Instruction {
98 fn new(b1: u8, b2: u8) -> Result<Self> {
99 let i = b1 >> 4;
100 let x = b1 & 0xf;
101 let y = b2 >> 4;
102 let n = usize::from(b2 & 0xf);
103 let nn = b2;
104 let nnn = u16::from_ne_bytes([nn, x]);
105 let x = usize::from(x);
106 let y = usize::from(y);
107 let ins = match (i, x, y, n, nn, nnn) {
108 (0, 0, 0xE, 0, _, _) => Self::Cls00E0,
109 (0xA, _, _, _, _, nnn) => Self::SetIndexRegisterANNN(nnn.into()),
110 (1, _, _, _, _, nnn) => Self::Jump1NNN(nnn),
111 (6, x, _, _, nn, _) => Self::SetVRegister6XNN(x, nn),
112 (0xD, x, y, n, _, _) => Self::Dxyn(x, y, n),
113 (2, _, _, _, _, nnn) => Self::SubroutineCall2NNN(nnn),
114 (0, 0, 0xE, 0xE, _, _) => Self::SubroutineReturn00EE,
115 (3, x, _, _, nn, _) => Self::SkipEqual3XNN(x, nn),
116 (4, x, _, _, nn, _) => Self::SkipNotEqual4XNN(x, nn),
117 (5, x, y, 0, _, _) => Self::SkipIfEqual5XY0(x, y),
118 (9, x, y, 0, _, _) => Self::SkipIfNotEqual9XY0(x, y),
119 (7, x, _, _, nn, _) => Self::Add7XNN(x, nn),
120 (8, x, y, 3, _, _) => Self::Xor8XY3(x, y),
121 (0xF, x, 3, 3, _, _) => Self::BinaryCodedDecimalConversionFX33(x),
122 (0xF, x, 2, 9, _, _) => Self::FontCharacterFX29(x),
123 (0xF, x, 1, 5, _, _) => Self::SetDelayTimerFX15(x),
124 (0xF, x, 0, 7, _, _) => Self::ReadDelayTimerFX07(x),
125 (0xF, x, 0, 0xA, _, _) => Self::GetKeyFX0A(x),
126 (0xF, x, 1, 8, _, _) => Self::SetSoundTimerFX18(x),
127 (0xF, x, 1, 0xE, _, _) => Self::AddToIndexFX1E(x),
128 (0xF, x, 5, 5, _, _) => Self::StoreRegistersToMemoryFX55(x),
129 (0xF, x, 6, 5, _, _) => Self::LoadRegistersFromMemoryFX65(x),
130 (0xC, x, _, _, nn, _) => Self::RandomCXNN(x, nn),
131 (0xE, x, 9, 0xE, _, _) => Self::SkipIfKeyPressedEX9E(x),
132 (0xE, x, 0xA, 1, _, _) => Self::SkipIfKeyNotPressedEXA1(x),
133 (8, x, y, 2, _, _) => Self::BinaryAnd8XY2(x, y),
134 (8, x, y, 4, _, _) => Self::RegisterAdd8XY4(x, y),
135 (8, x, y, 0, _, _) => Self::RegisterSet8XY0(x, y),
136 (8, x, y, 5, _, _) => Self::RegisterSub8XY5(x, y),
137 (8, x, y, 6, _, _) => Self::ShiftRight8XY6(x, y),
138 (8, x, y, 0xE, _, _) => Self::ShiftLeft8XYE(x, y),
139 (8, x, y, 7, _, _) => Self::RegisterSubRev8XY7(x, y),
140 _ => {
141 std::thread::sleep(Duration::from_secs(5));
142 bail!("unimplemented instruction: {} {} {} {}", i, x, y, n)
143 }
144 };
145 Ok(ins)
146 }
147
148 const fn requires_pc_inc(self) -> usize {
149 match self {
150 Self::SubroutineCall2NNN(_) | Self::Jump1NNN(_) => 0,
151 _ => 2,
152 }
153 }
154}
155
156impl Chip8 {
157 #[must_use]
163 pub fn new(clock: u64) -> Self {
164 let mut ram = vec![0; RAM_SIZE];
165 ram[FONT_ADDR..FONT_ADDR + FONTS.len()].copy_from_slice(&FONTS);
166 Self {
167 clock,
168 pixels: vec![vec![false; TERMINAL_WIDTH]; TERMINAL_HEIGHT],
169 ram,
170 pc: PROGRAM_START,
171 ..Default::default()
172 }
173 }
174
175 pub fn tick(&mut self, graphics: &mut impl Graphics, audio: &mut impl Audio) {
183 self.decrease_timers();
184 for _ in 0..self.clock / FPS {
185 sleep(Duration::from_millis(1000 / self.clock));
186 if self.waiting_for_input.is_some() {
187 return;
188 }
189 let inst = self
190 .fetch_and_decode_next_instruction()
191 .expect("instruction failure");
192 self.execute_instruction(inst, graphics)
193 .unwrap_or_else(|_| panic!("failed to execute instruction: {inst:?}"));
194 self.pc += inst.requires_pc_inc();
195 if self.sound_timer > 0 && !self.beeping {
196 audio.start_beep();
197 self.beeping = true;
198 } else if self.sound_timer == 0 && self.beeping {
199 audio.stop_beep();
200 self.beeping = false;
201 }
202 }
203 }
204
205 fn decrease_timers(&mut self) {
207 self.delay_timer = self.delay_timer.saturating_sub(1);
208 self.sound_timer = self.sound_timer.saturating_sub(1);
209 }
210
211 fn fetch_and_decode_next_instruction(&mut self) -> Result<Instruction> {
212 let b1 = *self
213 .ram
214 .get(self.pc)
215 .unwrap_or_else(|| panic!("invalid memory address: {}", self.pc));
216 let b2 = *self
217 .ram
218 .get(self.pc + 1)
219 .unwrap_or_else(|| panic!("invalid memory address: {}", self.pc));
220 let inst = Instruction::new(b1, b2).context("failed to decode instruction")?;
221 Ok(inst)
222 }
223
224 #[allow(clippy::too_many_lines)]
226 fn execute_instruction(
227 &mut self,
228 inst: Instruction,
229 graphics: &mut impl Graphics,
230 ) -> Result<()> {
231 debug!(
232 "pc = {}, index = {}, registers = {:?}\n",
233 self.pc, self.i, self.registers
234 );
235 debug!("{:?}", inst);
236 match inst {
237 Instruction::Cls00E0 => {
238 for (y, row) in self.pixels.iter().enumerate() {
239 for (x, pixel) in row.iter().enumerate() {
240 if *pixel {
241 graphics.clear_pixel(x, y);
242 }
243 }
244 }
245 self.pixels = vec![vec![false; TERMINAL_WIDTH]; TERMINAL_HEIGHT];
246 }
247 Instruction::SetIndexRegisterANNN(nnn) => self.i = nnn,
248 Instruction::SetVRegister6XNN(x, nn) => self.registers[x] = nn,
249 Instruction::Dxyn(x, y, n) => {
250 let x_org = usize::from(self.registers[x]) % TERMINAL_WIDTH;
251 let mut y = usize::from(self.registers[y]) % TERMINAL_HEIGHT;
252 self.registers[15] = 0;
253 let mut collision = false;
254 let sprites = &self.ram[self.i..self.i + n];
255 for row in sprites {
256 let mut x = x_org;
257 for i in (0..8).rev() {
258 let pixel = (row >> i) & 1;
259 if pixel == 1 {
260 let is_pixel_on =
261 self.is_pixel_on(x, y).context("failed to check pixel")?;
262 self.pixels[y][x] = !is_pixel_on;
263 if is_pixel_on {
264 graphics.clear_pixel(x, y);
265 collision = true;
266 } else {
267 graphics.draw_pixel(x, y);
268 }
269 }
270 x += 1;
271 if x == TERMINAL_WIDTH {
272 break;
273 }
274 }
275 y += 1;
276 if y == TERMINAL_HEIGHT {
277 break;
278 }
279 }
280 if collision {
281 self.registers[15] = 1;
282 }
283 }
284 Instruction::Add7XNN(x, nn) => {
285 let (res, _) = self.registers[x].overflowing_add(nn);
286 self.registers[x] = res;
287 }
288 Instruction::Jump1NNN(nnn) => self.pc = nnn.into(),
289 Instruction::SubroutineCall2NNN(nnn) => {
290 self.stack.push(self.pc);
291 self.pc = usize::from(nnn);
292 }
293 Instruction::SubroutineReturn00EE => {
294 self.pc = self
295 .stack
296 .pop()
297 .context("failed to return from subroutine: stack underflow")?;
298 }
299 Instruction::SkipEqual3XNN(x, nn) => {
300 if self.registers[x] == nn {
301 self.pc += 2;
302 }
303 }
304 Instruction::SkipNotEqual4XNN(x, nn) => {
305 if self.registers[x] != nn {
306 self.pc += 2;
307 }
308 }
309 Instruction::BinaryCodedDecimalConversionFX33(x) => {
310 let val = self.registers[x];
311 self.ram[self.i] = val / 100;
312 self.ram[self.i + 1] = (val % 100) / 10;
313 self.ram[self.i + 2] = val % 10;
314 }
315 Instruction::FontCharacterFX29(x) => {
316 self.i = FONT_ADDR + (usize::from(self.registers[x]) * FONT_SIZE);
317 }
318 Instruction::SetDelayTimerFX15(x) => {
319 self.delay_timer = self.registers[x];
320 }
321 Instruction::ReadDelayTimerFX07(x) => self.registers[x] = self.delay_timer,
322 Instruction::SetSoundTimerFX18(x) => self.sound_timer = self.registers[x],
323 Instruction::AddToIndexFX1E(x) => {
324 let (res, overflow) = self.i.overflowing_add(self.registers[x].into());
325 self.i = res;
326 if overflow {
327 self.registers[15] = 1;
328 }
329 }
330 Instruction::StoreRegistersToMemoryFX55(x) => {
331 self.ram[self.i..=self.i + x].copy_from_slice(&self.registers[0..=x]);
332 }
333 Instruction::LoadRegistersFromMemoryFX65(x) => {
334 let data = &self.ram[self.i..=self.i + x];
335 self.registers[0..=x].copy_from_slice(data);
336 }
337 Instruction::RandomCXNN(x, nn) => {
338 let r: u8 = rand::random();
339 self.registers[x] = r & nn;
340 }
341 Instruction::SkipIfKeyPressedEX9E(x) => {
342 if self.key_pressed == Some(self.registers[x]) {
343 self.pc += 2;
344 }
345 }
346 Instruction::SkipIfKeyNotPressedEXA1(x) => {
347 if self.key_pressed != Some(self.registers[x]) {
348 self.pc += 2;
349 }
350 }
351 Instruction::BinaryAnd8XY2(x, y) => {
352 self.registers[x] &= self.registers[y];
353 }
354 Instruction::RegisterAdd8XY4(x, y) => {
355 let (res, carry) = self.registers[x].overflowing_add(self.registers[y]);
356 self.registers[x] = res;
357 self.registers[15] = u8::from(carry);
358 }
359 Instruction::RegisterSet8XY0(x, y) => {
360 self.registers[x] = self.registers[y];
361 }
362 Instruction::RegisterSub8XY5(x, y) => {
363 let (res, carry) = self.registers[x].overflowing_sub(self.registers[y]);
364 self.registers[x] = res;
365 self.registers[15] = u8::from(!carry);
366 }
367 Instruction::RegisterSubRev8XY7(x, y) => {
368 let (res, carry) = self.registers[y].overflowing_sub(self.registers[x]);
369 self.registers[x] = res;
370 self.registers[15] = u8::from(!carry);
371 }
372 Instruction::GetKeyFX0A(x) => self.waiting_for_input = Some(x),
373 Instruction::ShiftRight8XY6(x, _) => {
374 self.registers[15] = self.registers[x] & 1u8;
375 self.registers[x] >>= 1;
376 }
377 Instruction::ShiftLeft8XYE(x, _) => {
378 self.registers[15] = self.registers[x] & (1u8 << 7);
379 self.registers[x] <<= 1;
380 }
381 Instruction::SkipIfEqual5XY0(x, y) => {
382 if self.registers[x] == self.registers[y] {
383 self.pc += 2;
384 }
385 }
386 Instruction::SkipIfNotEqual9XY0(x, y) => {
387 if self.registers[x] != self.registers[y] {
388 self.pc += 2;
389 }
390 }
391 Instruction::Xor8XY3(x, y) => {
392 self.registers[x] ^= self.registers[y];
393 }
394 }
395 Ok(())
396 }
397
398 fn is_pixel_on(&self, x: usize, y: usize) -> Result<bool> {
402 check_coordinates(x, y)?;
403 Ok(self.pixels[y][x])
404 }
405
406 pub fn store_in_ram(&mut self, rom: impl AsRef<[u8]>) -> Result<()> {
412 let rom = &rom.as_ref();
413 if rom.len() + PROGRAM_START > RAM_SIZE {
414 bail!("data is too big to fit into the ram");
415 }
416 self.ram[PROGRAM_START..PROGRAM_START + rom.len()].copy_from_slice(rom);
417 Ok(())
418 }
419
420 pub fn handle_key_released(&mut self) {
424 self.key_pressed = None;
425 }
426
427 pub fn handle_key_pressed(&mut self, key: u8) {
436 self.key_pressed = Some(key);
437 if let Some(x) = self.waiting_for_input {
438 self.registers[x] = key;
439 self.waiting_for_input = None;
440 }
441 }
442}
443
444fn check_coordinates(x: usize, y: usize) -> Result<()> {
446 if x >= TERMINAL_WIDTH {
447 bail!("invalid X coordinate to draw: {}", x);
448 }
449 if y >= TERMINAL_HEIGHT {
450 bail!("invalid Y coordinate to draw: {}", y);
451 }
452 Ok(())
453}
454
455pub trait Graphics {
460 fn clear_pixel(&mut self, x: usize, y: usize);
462
463 fn draw_pixel(&mut self, x: usize, y: usize);
465}
466
467pub trait Audio {
472 fn start_beep(&mut self);
474
475 fn stop_beep(&mut self);
477}