1use rand::{Rng, rng, rngs::ThreadRng};
89use std::{fmt, io, time::Duration};
90
91pub const CPU_TICK: Duration = Duration::from_micros(1430);
94
95pub const TIMER_TICK: Duration = Duration::from_micros(16667);
98
99pub const SCREEN_WIDTH: usize = 64;
101
102pub const SCREEN_HEIGHT: usize = 32;
104
105pub const SCREEN_AREA: usize = SCREEN_WIDTH * SCREEN_HEIGHT;
107
108const FONTSET_SIZE: usize = 80;
111const FONT_ADDR: u16 = 0x050;
112
113const FONTSET: [u8; FONTSET_SIZE] = [
116 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, ];
133
134const RAM_SIZE: usize = 4096;
135const NUM_REGS: usize = 16;
136const STACK_SIZE: usize = 16;
137const NUM_KEYS: usize = 16;
138const VF: usize = 15;
139const START_ADDR: u16 = 0x200;
140
141#[derive(Debug)]
142struct Opcode(u8, u8, u8, u8);
143
144#[derive(Debug)]
149pub struct Oxid8 {
150 pc: u16, ram: [u8; RAM_SIZE], screen: [bool; SCREEN_AREA], v_reg: [u8; NUM_REGS], i_reg: u16, sp: u16, stack: [u16; STACK_SIZE], keys: [bool; NUM_KEYS], stored_key: Option<usize>, dt: u8, st: u8, rng: ThreadRng, }
163
164impl Opcode {
166 fn new(byte1: u8, byte2: u8) -> Self {
168 Self(
169 (byte1 & 0xF0) >> 4,
170 byte1 & 0x0F,
171 (byte2 & 0xF0) >> 4,
172 byte2 & 0x0F,
173 )
174 }
175
176 fn full(&self) -> u16 {
178 (self.0 as u16) << 12 | (self.1 as u16) << 8 | (self.2 as u16) << 4 | (self.3 as u16)
179 }
180
181 fn nnn(&self) -> u16 {
183 (self.1 as u16) << 8 | (self.2 as u16) << 4 | (self.3 as u16)
184 }
185
186 fn n(&self) -> u8 {
188 self.3
189 }
190
191 fn x(&self) -> u8 {
193 self.1
194 }
195
196 fn y(&self) -> u8 {
198 self.2
199 }
200
201 fn kk(&self) -> u8 {
203 self.2 << 4 | self.3
204 }
205}
206
207impl fmt::Display for Opcode {
209 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
210 write!(f, "({}, {}, {}, {})", self.0, self.1, self.2, self.3)
211 }
212}
213
214impl Oxid8 {
216 pub fn new() -> Self {
218 Oxid8::default()
219 }
220
221 pub fn reset(&mut self) {
224 *self = Oxid8::default();
225 }
226
227 pub fn next_frame(&mut self) -> Result<(), String> {
253 for _ in 0..10 {
254 self.run_cycle()?;
255 }
256 self.dec_timers();
257
258 Ok(())
259 }
260
261 pub fn run_cycle(&mut self) -> Result<(), String> {
281 let opcode = Opcode::new(
282 self.ram[self.pc as usize], self.ram[self.pc as usize + 1], );
285
286 let pc_at_err = self.pc;
287 self.pc += 2;
288
289 let invalid = || -> Result<(), String> {
290 Err(format!(
291 "Invalid Instruction: {:04X} at {}",
292 opcode.full(),
293 pc_at_err,
294 ))
295 };
296
297 match opcode.0 {
298 0x0 => match opcode.kk() {
299 0xE0 => self.cls(),
300 0xEE => self.ret(),
301 _ => invalid()?,
302 },
303 0x1 => self.jp_nnn(opcode.nnn()),
304 0x2 => self.call(opcode.nnn()),
305 0x3 => self.se_xkk(opcode.x() as usize, opcode.kk()),
306 0x4 => self.sne_xkk(opcode.x() as usize, opcode.kk()),
307 0x5 => self.se_xy(opcode.x() as usize, opcode.y() as usize),
308 0x6 => self.ld_xkk(opcode.x() as usize, opcode.kk()),
309 0x7 => self.add_xkk(opcode.x() as usize, opcode.kk()),
310 0x8 => match opcode.n() {
311 0x0 => self.ld_xy(opcode.x() as usize, opcode.y() as usize),
312 0x1 => self.or(opcode.x() as usize, opcode.y() as usize),
313 0x2 => self.and(opcode.x() as usize, opcode.y() as usize),
314 0x3 => self.xor(opcode.x() as usize, opcode.y() as usize),
315 0x4 => self.add_xy(opcode.x() as usize, opcode.y() as usize),
316 0x5 => self.sub_xy(opcode.x() as usize, opcode.y() as usize),
317 0x6 => self.shr(opcode.x() as usize, opcode.y() as usize),
318 0x7 => self.subn_xy(opcode.x() as usize, opcode.y() as usize),
319 0xE => self.shl(opcode.x() as usize, opcode.y() as usize),
320 _ => invalid()?,
321 },
322 0x9 => self.sne_xy(opcode.x() as usize, opcode.y() as usize),
323 0xA => self.ld_innn(opcode.nnn()),
324 0xB => self.jp_0nnn(opcode.nnn()),
325 0xC => self.rnd(opcode.x() as usize, opcode.kk()),
326 0xD => {
327 self.drw(
328 opcode.x() as usize, opcode.y() as usize, opcode.n(), );
332 }
333 0xE => match opcode.kk() {
334 0x9E => self.skp(opcode.x() as usize),
335 0xA1 => self.sknp(opcode.x() as usize),
336 _ => invalid()?,
337 },
338 0xF => match opcode.kk() {
339 0x07 => self.ld_xdt(opcode.x() as usize),
340 0x0A => self.ld_xk(opcode.x() as usize),
341 0x15 => self.ld_dtx(opcode.x() as usize),
342 0x18 => self.ld_stx(opcode.x() as usize),
343 0x1E => self.add_ix(opcode.x() as usize),
344 0x29 => self.ld_fx(opcode.x() as usize),
345 0x33 => self.ld_bx(opcode.x() as usize),
346 0x55 => self.ld_ix(opcode.x() as usize),
347 0x65 => self.ld_xi(opcode.x() as usize),
348 _ => invalid()?,
349 },
350 _ => invalid()?,
351 }
352
353 Ok(())
354 }
355
356 pub fn dec_timers(&mut self) {
361 if self.dt > 0 {
362 self.dt -= 1;
363 }
364 if self.st > 0 {
365 self.st -= 1;
366 }
367 }
368
369 #[must_use]
371 pub fn sound(&self) -> bool {
372 self.st != 0
373 }
374
375 pub fn set_key(&mut self, k: usize, val: bool) {
382 self.keys[k] = val;
383 }
384
385 pub fn clear_keys(&mut self) {
387 self.keys = [false; NUM_KEYS];
388 }
389
390 #[must_use]
392 pub fn screen_ref(&self) -> &[bool; SCREEN_AREA] {
393 &self.screen
394 }
395
396 pub fn load_font(&mut self) {
398 self.ram[FONT_ADDR as usize..(FONT_ADDR as usize + FONTSET_SIZE)] .copy_from_slice(&FONTSET);
400 }
401
402 pub fn load_rom(&mut self, path: impl AsRef<std::path::Path>) -> io::Result<()> {
408 use std::fs;
409
410 let rom_data: Vec<u8> = fs::read(path)?;
411 self.load_rom_bytes(rom_data.as_slice())
412 }
413
414 pub fn load_rom_bytes(&mut self, rom_data: &[u8]) -> io::Result<()> {
420 let len = rom_data.len();
421 if len > (RAM_SIZE - START_ADDR as usize) {
422 return Err(io::Error::new(
423 io::ErrorKind::FileTooLarge,
424 format!("ROM too large: {}", len),
425 ));
426 }
427
428 self.ram[START_ADDR as usize..(START_ADDR as usize + len)] .copy_from_slice(rom_data);
430
431 Ok(())
432 }
433
434 fn push(&mut self, val: u16) {
440 match self.sp as usize {
441 0..STACK_SIZE => {
442 self.stack[self.sp as usize] = val;
443 self.sp += 1;
444 }
445 _ => panic!("ERROR::Emulator Stack Overflow"),
446 };
447 }
448
449 fn pop(&mut self) -> u16 {
455 match self.sp as usize {
456 1..=STACK_SIZE => {
457 self.sp -= 1;
458 self.stack[self.sp as usize]
459 }
460 _ => panic!("ERROR::Emulator Stack Underflow"),
461 }
462 }
463}
464
465impl Default for Oxid8 {
466 fn default() -> Self {
467 Self {
468 pc: START_ADDR,
469 ram: [0; RAM_SIZE],
470 screen: [false; SCREEN_WIDTH * SCREEN_HEIGHT],
471 v_reg: [0; NUM_REGS],
472 i_reg: 0,
473 sp: 0,
474 stack: [0; STACK_SIZE],
475 keys: [false; NUM_KEYS],
476 stored_key: None,
477 dt: 0,
478 st: 0,
479 rng: rng(),
480 }
481 }
482}
483
484impl Oxid8 {
498 fn cls(&mut self) {
500 self.screen = [false; SCREEN_WIDTH * SCREEN_HEIGHT];
501 }
502
503 fn ret(&mut self) {
505 self.pc = self.pop();
506 }
507
508 fn jp_nnn(&mut self, nnn: u16) {
510 self.pc = nnn;
511 }
512
513 fn call(&mut self, nnn: u16) {
515 self.push(self.pc);
516 self.pc = nnn;
517 }
518
519 fn se_xkk(&mut self, x: usize, kk: u8) {
521 if self.v_reg[x] == kk {
522 self.pc += 2;
523 }
524 }
525
526 fn sne_xkk(&mut self, x: usize, kk: u8) {
528 if self.v_reg[x] != kk {
529 self.pc += 2;
530 }
531 }
532
533 fn se_xy(&mut self, x: usize, y: usize) {
535 if self.v_reg[x] == self.v_reg[y] {
536 self.pc += 2;
537 }
538 }
539
540 fn ld_xkk(&mut self, x: usize, kk: u8) {
542 self.v_reg[x] = kk;
543 }
544
545 fn add_xkk(&mut self, x: usize, kk: u8) {
547 self.v_reg[x] = self.v_reg[x].wrapping_add(kk);
548 }
549
550 fn ld_xy(&mut self, x: usize, y: usize) {
552 self.v_reg[x] = self.v_reg[y];
553 }
554
555 fn or(&mut self, x: usize, y: usize) {
557 self.v_reg[x] |= self.v_reg[y];
558 }
559
560 fn and(&mut self, x: usize, y: usize) {
562 self.v_reg[x] &= self.v_reg[y];
563 }
564
565 fn xor(&mut self, x: usize, y: usize) {
567 self.v_reg[x] ^= self.v_reg[y];
568 }
569
570 fn add_xy(&mut self, x: usize, y: usize) {
572 let (vx, carry) = self.v_reg[x].overflowing_add(self.v_reg[y]);
573 self.v_reg[x] = vx;
574 self.v_reg[VF] = carry as u8;
575 }
576
577 fn sub_xy(&mut self, x: usize, y: usize) {
579 let (vx, borrow) = self.v_reg[x].overflowing_sub(self.v_reg[y]);
580 self.v_reg[x] = vx;
581 self.v_reg[VF] = !borrow as u8;
582 }
583
584 fn shr(&mut self, x: usize, _y: usize) {
586 let vx = self.v_reg[x];
587 self.v_reg[x] = vx >> 1;
588 self.v_reg[VF] = vx & 1;
589 }
590
591 fn subn_xy(&mut self, x: usize, y: usize) {
593 let (vx, borrow) = self.v_reg[y].overflowing_sub(self.v_reg[x]);
594 self.v_reg[x] = vx;
595 self.v_reg[VF] = !borrow as u8;
596 }
597
598 fn shl(&mut self, x: usize, _y: usize) {
600 let vx = self.v_reg[x];
601 self.v_reg[x] = vx << 1;
602 self.v_reg[VF] = (vx >> 7) & 1;
603 }
604
605 fn sne_xy(&mut self, x: usize, y: usize) {
607 if self.v_reg[x] != self.v_reg[y] {
608 self.pc += 2;
609 }
610 }
611
612 fn ld_innn(&mut self, nnn: u16) {
614 self.i_reg = nnn;
615 }
616
617 fn jp_0nnn(&mut self, nnn: u16) {
619 self.pc = nnn + self.v_reg[0] as u16;
620 }
621
622 fn rnd(&mut self, x: usize, kk: u8) {
624 self.v_reg[x] = self.rng.random_range(0..=0xFF) as u8 & kk;
625 }
626
627 fn drw(&mut self, x: usize, y: usize, n: u8) {
630 let (x, y) = (
632 self.v_reg[x] as usize % SCREEN_WIDTH, self.v_reg[y] as usize % SCREEN_HEIGHT, );
635 self.v_reg[VF] = 0; let start_pixel: usize = (y * SCREEN_WIDTH) + x;
637 let start_addr: usize = self.i_reg as usize;
638
639 for i in 0..n as usize {
641 if y + i >= SCREEN_HEIGHT {
642 break; }
644 let pixel_posn: usize = start_pixel + (SCREEN_WIDTH * i);
645 let sprite_row: u8 = self.ram[start_addr + i];
646
647 for j in 0..8 {
649 if x + j >= SCREEN_WIDTH {
650 break; }
652 let ref mut pixel_ref = self.screen[pixel_posn + j];
653 let old_pixel = *pixel_ref;
654
655 let sprite_pixel = (sprite_row >> (0x7 - j)) & 0x1;
656 *pixel_ref ^= sprite_pixel != 0;
657
658 if !(*pixel_ref) && old_pixel {
659 self.v_reg[VF] = 1; }
661 }
662 }
663 }
664
665 fn skp(&mut self, x: usize) {
667 if self.keys[self.v_reg[x] as usize] {
668 self.pc += 2;
669 }
670 }
671
672 fn sknp(&mut self, x: usize) {
674 if !self.keys[self.v_reg[x] as usize] {
675 self.pc += 2;
676 }
677 }
678
679 fn ld_xdt(&mut self, x: usize) {
681 self.v_reg[x] = self.dt;
682 }
683
684 fn ld_xk(&mut self, x: usize) {
686 match self.stored_key {
687 Some(k) => {
688 if !self.keys[k] {
690 self.v_reg[x] = k as u8;
691 self.stored_key = None;
692 return;
693 }
694 }
695 None => {
696 for (k, &pressed) in self.keys.iter().enumerate() {
698 if pressed {
699 self.stored_key = Some(k);
700 break;
701 }
702 }
703 }
704 }
705 self.pc -= 2;
707 }
708
709 fn ld_dtx(&mut self, x: usize) {
711 self.dt = self.v_reg[x];
712 }
713
714 fn ld_stx(&mut self, x: usize) {
716 self.st = self.v_reg[x];
717 }
718
719 fn add_ix(&mut self, x: usize) {
721 self.i_reg = self.i_reg.wrapping_add(self.v_reg[x] as u16);
722 }
723
724 fn ld_fx(&mut self, x: usize) {
726 self.i_reg = FONT_ADDR + (self.v_reg[x] as u16 * 5);
727 }
728
729 fn ld_bx(&mut self, x: usize) {
731 let i = self.i_reg as usize;
732 let v = self.v_reg[x];
733 self.ram[i] = (v / 100) % 10;
734 self.ram[i + 1] = (v / 10) % 10;
735 self.ram[i + 2] = v % 10;
736 }
737
738 fn ld_ix(&mut self, x: usize) {
740 let i = self.i_reg as usize;
741 self.ram[i..=(i + x)].copy_from_slice(&self.v_reg[0..=x]);
742 }
743
744 fn ld_xi(&mut self, x: usize) {
746 let i = self.i_reg as usize;
747 self.v_reg[0..=x].copy_from_slice(&self.ram[i..=(i + x)]);
748 }
749}
750
751#[cfg(test)]
752mod tests {
753 use super::*;
754
755 #[test]
756 fn test() {
757 let a: [u8; 5] = [255, 155, 100, 55, 5];
759 let i: u16 = 0;
760 assert_eq!(255, a[i as usize]);
761 assert_eq!(155, a[i as usize + 1]);
762 }
763
764 #[test]
765 fn opcode_new() {
766 let opcode = Opcode::new(0x12, 0x34);
767 assert_eq!(opcode.0, 0x1);
768 assert_eq!(opcode.1, 0x2);
769 assert_eq!(opcode.2, 0x3);
770 assert_eq!(opcode.3, 0x4);
771 }
772
773 #[test]
774 fn opcode_decode() {
775 let opcode = Opcode::new(0x12, 0x34);
776 assert_eq!(opcode.full(), 0x1234);
777 assert_eq!(opcode.nnn(), 0x234);
778 assert_eq!(opcode.n(), 0x4);
779 assert_eq!(opcode.x(), 0x2);
780 assert_eq!(opcode.y(), 0x3);
781 assert_eq!(opcode.kk(), 0x34);
782 }
783
784 #[test]
785 fn invalid_opcode() {
786 let mut emu = Oxid8::new();
787 emu.ram[START_ADDR as usize] = 0xFF;
788 emu.ram[START_ADDR as usize + 1] = 0xFF;
789 assert!(emu.run_cycle().is_err_and(|msg| msg
790 == format!(
791 "Invalid Instruction: FFFF at {}", START_ADDR )))
794 }
795
796 #[test]
797 fn push_pop() {
798 let mut emu = Oxid8::new();
799 assert_eq!(emu.sp, 0); emu.push(1); assert_eq!(emu.sp, 1); assert_eq!(emu.stack[0], 1); assert_eq!(emu.pop(), 1); assert_eq!(emu.sp, 0); }
806
807 #[test]
808 #[should_panic(expected = "Stack Overflow")]
809 fn push_panic() {
810 let mut emu = Oxid8::new();
811 for _ in 0..=STACK_SIZE {
812 emu.push(1);
813 }
814 }
815
816 #[test]
817 #[should_panic(expected = "Stack Underflow")]
818 fn pop_panic() {
819 let mut emu = Oxid8::new();
820 emu.pop();
821 }
822
823 #[test]
824 fn load_font() {
825 let mut emu = Oxid8::new();
826 emu.load_font();
827 assert_eq!(
828 emu.ram[FONT_ADDR as usize..(FONT_ADDR as usize + FONTSET_SIZE)],
829 FONTSET
830 );
831 }
832
833 #[test]
834 fn draw_basic() {
835 let sprite = [
838 0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81, ];
843
844 let screen = [
845 true, false, false, false, false, false, false, true, false, true, false, false, false, false, true, false, false, false, true, false, false, true, false, false, false, false, false, true, true, false, false, false, false, false, false, true, true, false, false, false, false, false, true, false, false, true, false, false, false, true, false, false, false, false, true, false, true, false, false, false, false, false, false, true, false, true, false, false, false, false, true, false, false, false, true, false, false, true, false, false, false, false, false, true, true, false, false, false, false, false, false, true, true, false, false, false, false, false, true, false, false, true, false, false, false, true, false, false, false, false, true, false, true, false, false, false, false, false, false, true, ];
861
862 let mut emu = Oxid8::new();
863
864 emu.i_reg = START_ADDR;
865 let start = START_ADDR as usize;
866
867 emu.ram[start..start + sprite.len()].copy_from_slice(&sprite);
868 emu.drw(0, 0, sprite.len() as u8);
869
870 for i in 0..15 {
871 let offset1: usize = i * SCREEN_WIDTH;
872 let offset2: usize = i * 8;
873 assert_eq!(
874 emu.screen[offset1 + 0..offset1 + 8],
875 screen[offset2 + 0..offset2 + 8]
876 );
877 }
878 }
879}